본문 바로가기

Project

현재 프로젝트에 CI 파이프라인 적용하기!

안녕하세요 왕란입니다. 오늘도 개발하기 좋은 흐린 주말이군요!!

 

이번엔 프로젝트에 간단한 CI 파이프라인을 적용하는 방법에 대한 포스팅을 하려고 합니다.

(사실 혼자 개발하는 작은 프로젝트에 필요할까 싶기도 하지만, 테스트코드도 많이 작성하면서 좀 완벽한 프로젝트를 지향하고 있어서 하는 거예요!)

 

보통 빌드, 테스트 및 배포 자동화 파이프라인을 구축하기 위해 CI/CD를 함께하는 경우가 많습니다. 하지만 저는 지금은 배포까지의 과정은 필요하지 않기 때문에 CI 파이프라인만 구축할 예정입니다.

 

CI 란 뭘까?

CI(Continuous Integration, 지속적 통합)란 개발자들이 작업한 코드를 자주, 그리고 반복적으로 중앙 저장소(main)에 통합(merge) 하고, 그 과정에서 자동으로 빌드(build), 테스트(test 코드확인),  정적 분석(lint) 등을 수행하는 개발 방식입니다.

 

협업을 하다 보면 반드시 한 번씩 이런 경험 있으실 겁니다.

 

어 뭔가 로컬에선 문제없이 테스트도 되고 잘 동작한 거 같은데 main에서 안돼...

 

주로 merge 후 코드가 통합되면서 예상치 못한 충돌이나 오류로 인해 이런 경우가 발생하게 됩니다. 이걸 merge 이후에 바로 발견해서 hotfix 할 수 있다면 괜찮을 텐데...

 

"하루종일 열심히 코드 작성하고 테스트 후 이거만 merge 하고 퇴근하고 싶어!!"

라고 생각하면서 merge 하고 퇴근했다가, 밤에 코드가 제대로 동작하지 않았다는 사실을 보고 당황한 적이 몇 번 있습니다.

(운영환경에서 이러면 안 되는데 말이죠..)

 

이런 경우를 막기 위해 CI 파이프라인을 구축해 두면

 

1. 최소한의 컴파일, 런타임에러를 잡을 수 있습니다. 또한

2. 테스트코드를 실행시켜 보면서 코드가 의도한 대로 동작하는지 테스트하는 과정도 가능하고,

3. 협업하면서 틈틈이 챙기면 좋은 Lint 과정도 추가하여 코드 컨벤션이 잘 지켜있나 확인도 가능합니다.

 

협업에서 효율이 향상되는 것이죠! 저는 이번에 Github Actions로 파이프라인을 구축할 계획입니다. 목표는 PR 작성 시 PR에 포함된 코드를 불러오고, ktlint로 코드 스타일 검사를 실행한 뒤, 빌드 및 테스트를 진행해서 merge 해도 안전한가를 확인하게 할 계획입니다.

 

step 1. ci.yml 작성하기!

Github Actions는 프로젝트 루트에 . github/workflows라는 경로가 있는지 확인하고 자동으로 안에 작성된 CI/CD 내용을 수행해 줍니다. 따라서 원하는 기능을 수행하는 yml 파일을 작성한 뒤 Github 에 추가만 해주면 간단하게 적용할 수 있습니다. 저는 ci.yml 이라는 이름으로 Github 에서 직접 파일을 추가했습니다.

 

.github/workflows 라는 경로가 없으면 그냥 셀프로 추가해 주시고 작성해도 됩니다!!

 

경로: `myproject/. github/workflows/ci.yml`

name: CI Pipeline

on:
  pull_request:
    branches:
      - main
      # - develop # (선택) develop 브랜치도 체크하고 싶으면 추가

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    env:
      JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        distribution: 'temurin' # OpenJDK 빌드
        java-version: '17'      # Kotlin/SpringBoot 3.x 기본 JDK 버전

    - name: Cache Gradle packages
      uses: actions/cache@v4
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          gradle-${{ runner.os }}-

    - name: Grant execute permission for Gradle Wrapper
      run: chmod +x ./gradlew

    - name: Run Ktlint Check
      run: ./gradlew ktlintCheck

    - name: Build and Test with Gradle
      run: ./gradlew clean build

 

흐름을 가볍게 설명해 보겠습니다!

 

1. main 브랜치로 merge 될 예정인 PR이 작성될 때 이 CI가 실행되게 하기

on:
  pull_request:
    branches:
      - main
      # - develop # (선택) develop 브랜치도 체크하고 싶으면 추가

 

저는 현재 배포도 하지 않았기에 feature 브랜치에서 작업 후 main으로 바로 merge 하고 있으나, 추후 배포 후 dev 브랜치에서 작업하게 된다면 주석을 해제하여 dev 나 main 둘 당에서 확인할 수 있습니다!

 

2. `runs-on: ubuntu-latest`:

Github에서 제공하는 우분투 가상 환경에서 ci 작업을 수행합니다.

 

3. `env: JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}`

놓치면 안 되는 중요한 부분입니다. Github Actions에서는 내부적으로 새로운 우분투 환경을 키고 CI 과정을 진행하기 때문에, 로컬이나 원격에서 환경변수로 선언해놓은 JWT 시크릿 키와같은 부분은 따로 주입해주어야 합니다.

 

프로젝트의 Settings - Secrets and variables - Actions 에 선언해 놓고 사용하면 됩니다. 

 

 

저는 위 사진처럼 Secrets를 미리 설정해 두었습니다!

(추가 정보를 드리자면, 한번 설정하고 나면 값을 어떻게 설정했는지 다시 볼 수 없습니다. 내부적으로 협업하면서도 유출되는 것을 막기 위함 같습니다. 그래서 우측의 Edit 버튼을 누르면 빈 입력란이 나오게 되는데 이때 "앗 실수로 지난번에 Secrets를 입력할 때 공백으로 입력한 거 아니야?"라는 오해는 하지 않으셔도 됩니다!!)

 

4. 코드 체크아웃

- uses: actions/checkout@v4

 

현재 PR의 코드를 Github 저장소에서 가져옵니다.

 

5. JDK 17 설치

- uses: actions/setup-java@v4

 

Kotlin과 Spring Boot에서 권장하는 Temurin 기반 JDK 17을 설치합니다.

 

6. Gradle 캐시 적용

- uses: actions/cache@v4

 

~/. gradle 디렉터리를 캐시 하여 의존성 다운로드 시간을 절약합니다. `hasFiles`를 기준으로 캐시키를 설정합니다.

(파일이 바뀌면 새 캐시 생성)

 

7. Gradle 실행 권한 부여

chmod +x ./gradlew

 

리눅스 환경에서 `gradlew` 가 실행되려면 실행권한도 부여해야겠죠?!

 

8. Ktlint 코드스타일 검사, 빌드 및 테스트

./gradlew ktlintCheck
./gradlew clean build

 

Kotlin 코드에 대한 코드 스타일 검사를 수행하고, 포맷 규칙을 위반하면 CI 가 실패합니다.

그다음으로 전체 프로젝트를 빌드(clean 포함)하고, JUnit 테스트를 실행하여 코드가 정상적으로 동작하는지 확인합니다.

(테스트코드를 잘 작성해놓은 안정감 좋은 백엔드 개발자라면 CI 과정만 해도 믿을 수 있는 코드를 merge 할 수 있습니다!!)

 

이렇게 작성하고 main 브랜치로 머지될 PR을 작성하면 아래 사진과 같은 CI 가 실행됩니다.

초반엔 설정을 제대로 못한 게 있어서 실패한 게 보이지만 넘어가주세요..!😅

 

성공하면 기분 좋은 초록색 체크 표시가 뜨게 되고, 통과한 PR의 경우에는 

여기도 ✔ 표시가 뜨게 됩니다.

 

실패한 경우도 보겠습니다.

 

 

초반 Set up 은 잘되었고, 코드를 가져온 뒤 Ktlint로 스타일 검사도 잘 통과했는데 빌드 부분에서 문제가 발생했군요.

 

 

열어서 읽어보니 테스트 단계에서 `MyprojectApplicationTests > contextLoads()` 테스트가 실행되는데 콘텍스트 로딩 중 Bean 생성에 실패해서 그런 것 같습니다. 그래서 HibernateException 이 발생한 상황입니다.

 

이렇게 로그로 문제를 파악해 주니 해결하기도 용이합니다. 아티클을 찾아보거나, GPT에게 물어보면 금방 해결할 수 있습니다.

 

지금 Hibernate 문제가 발생한 이유는 Github Actions 에는 로컬 DB가 없기 때문에 DB 연결에서 오류가 날 수 있습니다. 저는 CI 과정에서 하는 테스트에서 사용할 H2 인메모리 DB를 추가하여 테스트용 DB 설정을 추가하겠습니다.

 

위와 같이 application-test.yml 파일을 추가해 주고 이렇게 임시로 설정한 다음 main에 적용해 놓았습니다. 이후 테스트에선 과연?!

 

 

문제없이 통과하는 것을 보실 수 있습니다. 현재는 기본적인 기능을 추가하고 프로젝트 설정에 아직 집중하고 있는 단계라 테스트코드를 작성하지 않은 상태이지만 테스트 코드 작성 이후에는 좀 더 믿을 만한 코드를 merge 할 수 있을 거라 기대됩니다.

 

추후에 좀 완성도 있는 프로젝트를 배포하게 된다면 CD 부분의 내용도 포스팅해 보겠습니다!!

읽어주셔서 감사합니다!

728x90
반응형