안녕하세요 왕란입니다. 오늘도 개발하기 좋은 흐린 주말이군요!!
이번엔 프로젝트에 간단한 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 부분의 내용도 포스팅해 보겠습니다!!
읽어주셔서 감사합니다!
'Project' 카테고리의 다른 글
[트러블 슈팅] 인증/인가 캐싱처리로 DB접근 줄이기 (4) | 2025.05.05 |
---|---|
Join Fetch 를 사용하여 N+1 문제 해결하기 (1) | 2025.05.03 |
[코틀린 프로젝트] - 새로운 프로젝트 구상(4/13~4/26) (2) | 2025.04.27 |
[프로젝트] 식당 예약 및 웨이팅 플랫폼 개발 - 주제선정 (2) | 2024.12.11 |
[개인 프로젝트] - 레거시 코드 리팩토링 (0) | 2024.11.21 |