📌 CI(Continuous Integration)란?
CI는 '지속적 통합(Continuous Integration)'이라는 뜻으로, 개발자가 작성한 코드를 자주 메인 브랜치에 통합하고, 그 과정에서 자동으로 테스트를 수행하는 프로세스를 말한다.
👀 왜 CI가 필요했을까?
애플리케이션 개발은 보통 여러 개발자가 병렬적으로 동시에 작업하는 방식으로 이루어진다. 각자 맡은 기능을 구현하고 나중에 코드를 한꺼번에 통합하는 방식은, 변경사항 간 충돌이나 예상치 못한 버그를 유발하기 쉽다. 그리고 이로 인해 디버깅에 많은 시간과 에너지가 소모된다.
CI는 작업 단위를 작게 나누고, 변경된 코드를 자주 통합하며, 그때마다 자동화된 테스트를 수행함으로써 이런 문제를 최소화한다.
즉, CI의 핵심은 '짧은 통합 주기 -> 자동화된 테스트 -> 빠른 피드백' 이 흐름을 통해 개발 속도를 높이고, 코드 품질을 안정화하는 데 있다.
👀 개인 프로젝트는 CI 적용 안 해도 된다
종종 push 전에 테스트 실행을 까먹을 때도 있고, 시간이 오래 걸리는 작업도 아니니까 배포 전에 CI 적용을 해 봤는데 테스트가 계속 실패했다 😞
내 컴퓨터에서는 잘 돌아간다니까? <- 의 위험성을 다시 한 번 깨닫게 되었다.
오히려 동료 개발자가 없으니, 더더욱 자동화된 프로세스로 코드를 점검해 실수를 줄여야 한다. 개인 프로젝트라도 테스트, 빌드 자동화 덕분에 반복 작업이 줄고 생산성이 높아진다는 장점은 여전히 챙길 수 있다.
📌 CI 적용하기
🛠️ CI 도구 선택하기
1. GitHub Actions
GitHub에 내장된 CI/CD 도구로, 지정된 폴더에 YAML 파일만 작성하면 쉽게 워크플로우를 구성할 수 있다. GitHub 저장소와의 연동이 자연스럽고, 개인 프로젝트나 오픈소스 프로젝트에서 널리 사용된다.
2. GitLab CI/CD
GitLab에 내장된 CI 도구 .gitlab-ci.yml 파일 하나로 CI/CD 파이프라인을 설정할 수 있다. GitLab만의 통합된 환경 덕분에, 하나의 플랫폼에서 모든 작업을 처리할 수 있다는 장점이 있다.
3. Jenkins
가장 오래되고 널리 쓰이는 오픈소스 CI 도구이다. 다양한 플러그인, 커스터마이징이 가능하지만, 독립 서버 설치가 필요하다. 복잡한 파이프라인 구성이 가능한 만큼 초기 설정이 까다로운 편이다.
현재 GitHub 저장소에서 코드를 관리하고 있고, 현재는 복잡한 파이프라인 구성이 필요하지 않아서 러닝 커브가 낮은 Github Actions를 선택했다.
🤖 GitHub Actions 도입
레포지토리 Actions 탭에 들어가면 위와 같이 깃허브에서 현재 레포에 적용할 워크플로우 템플릿을 추천해 준다.
이 중 Java with Gradle을 선택해서 작업했다.
요런 파일이 생기는데 GitHub Actions의 yml 파일 구조에 대해 먼저 좀 알아야 할 것 같다.
name: Hello CI # 워크플로우 이름
on: push # 워크플로우 트리거 설정
jobs: # 수행할 작업의 집합
say-hello: # 작업 이름
runs-on: ubuntu-latest # 실행 환경
steps: # 하나의 작업 안에서 순서대로 실행될 작업 목록
- run: echo "Hello, GitHub Actions!" # 직접 실행할 셸 명령어
이 예시는 push를 할 때마다 "Hello, GitHub Actions!"를 출력하는 워크플로우다.
이 중 runs-on은 GitHub가 지원하는 환경으로 위와 같이 설정할 경우, Ubuntu 가상 머신을 자동으로 띄워줘서 그 위에서 steps에 적은 명령어들이 순서대로 실행된다.
uses를 사용하는 경우 외부 액션 또는 GitHub 공식 액션을 편리하게 사용할 수 있다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
- name: Build with Gradle Wrapper
run: ./gradlew build
다시 원래 파일로 돌아와서 주석과 불필요한 dependency-submission job을 지우면 다음 코드가 남는다.
자바 버전과 작업 디렉토리를 수정해서 저장소에 코드를 올리면 아무 문제 없이 성공하
지 않고, 실행 권한이 없어서 실패한다.
- name: Grant execute permission for gradlew
run: chmod +x gradlew
다시 권한을 수정해 주고 워크플로우를 실행하면
이번엔 테스트가 실패한다.
- name: Upload Test Report
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-report
path: ./server/build/reports/tests/test
테스트 실행 결과가 많이 생략돼서 아예 테스트 결과를 아티팩트로 다운로드받을 수 있도록 step을 추가했다.
그러면 이렇게 테스트 결과 파일을 아티팩트에서 다운받아서 확인할 수 있다.
실패한 테스트 클래스 목록이 익숙하다 했더니, 개발할 때 많이 본 예외 메시지가 있었다.
OpenAI를 사용하기 위한 API key가 설정되지 않았다고 하는데, 서브 모듈에 분리한 yml을 읽어들이지 못한 것 같다.
checkout action의 리드미를 확인해 보니 submodules 옵션을 설정할 수 있었다.
steps:
- name: Checkout repository including submodules
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.PAT }}
PAT 변수에 토큰을 추가해 주고 다시 실행했는데, 계속 똑같은 오류가 발생했다.
혹시 서브모듈 설정이 제대로 안 된 건가 싶어서 application-secret.yml 파일이 실제로 있는지 확인하는 step을 추가했다.
그리고 로컬 환경과 같은 경로에 해당 파일이 존재했다.
아오 그럼 도대체 왜 테스트가 실패하는 거냐고 🤦
😶🌫️ 머리가 나쁘면 컴퓨터가 고생한다
이때부터 화나서 커밋 메시지도 안 바꾸고 계속 이것저것 시도해 보며 냅다 워크플로우를 돌렸다.
사실은 내가 맞고 컴퓨터가 틀린 거라고 희망 회로를 돌리고 다시 좌절하기를 삼백구십육 번째...
.gitignore에 포함되어 파일명이 흐릿해진 yml 하나가 눈에 띄었다. 서브모듈로 application-secret.yml을 옮겨서 resources 최상단에 있는 저 파일은 GitHub 저장소에 업로드되지 않았다.
그래서 저장소와 마찬가지로 저 파일을 삭제했더니
로컬에서도 워크플로우와 똑같은 결과가 나타났다. 원인은 테스트의 application.yml에 있었다.
spring:
application:
name: server
profiles:
include: secret
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password:
h2:
console:
enabled: true
sql:
init:
mode: never
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate.format_sql: true
dialect: org.hibernate.dialect.MySQL8InnoDBDialect
테스트에서 init.sql을 실행하지 않기 위해 yml을 분리했는데, secret 파일을 불러오는 부분에서 문제가 있었다.
Spring Boot에서 기본적으로 classpath 루트 경로와 /config에 있는 application.yml들을 읽어들인다.
그리고 위 내용에 따라 애플리케이션 실행 시 secret 프로필을 활성화하면 application-secret.yml의 설정이 자동으로 적용된다.
그러나 서브모듈을 적용하면서 yml을 nexterview-config 폴더로 옮겼고 이에 따라 main의 application.yml을 다음과 같이 수정했었다.
그리고 test의 application.yml은 깜빡하고 수정을 안 해서 nexterview-config 아래 yml 설정이 하나도 적용되지 않고 있었다. 그 와중에 기존에 resources 최상단에 있던 application-secret.yml은 삭제를 안 해서, GitHub에는 관리되지 않고 있는 파일이 로컬에 존재해서 로컬에서만 테스트가 계속 통과했다는 허무한 이야기 🫠
🥔 감자의 실수는 끝이 없고 똑같은 욕심을 반복하지
원인을 찾은 후, 문제 해결과 재발 방지를 위해 테스트의 application.yml을 삭제했다. 그리고 application-test.yml과 @ActiveProfiles를 활용해 테스트 시에 수정이 필요한 설정만 덮어쓰도록 변경했다.
우당탕탕 삽질 끝에 만난 첫 번째 초록색 실행 결과 🥔🥔
📌 투 비 컨티뉴드....
'감자-갱생-프로젝트' 카테고리의 다른 글
Spring Security는 사드세요... 제발 - Authentication 편 (2) | 2025.04.25 |
---|---|
Spring Security는 사드세요... 제발 - Architecture 편 (1) | 2025.04.14 |
동시성의 이름으로 날 용서하지 않겠다 - Distributed Lock 편 (2) | 2024.12.03 |
동시성의 이름으로 날 용서하지 않겠다 - Optimistic Lock 편 (1) | 2024.12.02 |
동시성의 이름으로 날 용서하지 않겠다 - Pessimistic Lock 편 (2) | 2024.11.19 |