일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 설계
- 구글 플레이 비공개 테스트
- 커밋 메시지
- 운영체제 #CS지식
- 플레이 스토어 20명
- 객체지향설계
- 클린코드
- 프리코스
- git
- 우테코
- 객체지향
- 구글 플레이 스토어 배포 방법
- 구글 비공개 테스트 20명
- 플레이스토어 비공개 테스트
- 기능명세서
GYUD-TECH
[뉴젯] 도커를 활용한 CI 구축 본문
SW 마에스트로 개발팀에서는 Git-flow 전략을 사용하였습니다. 초기 MVP 개발을 진행하였기 때문에 dev, feature, main 브랜치를 주로 사용하였고, 배포는 수동으로 진행하였습니다. 하지만 수동으로 서비스를 배포하다보니, 배포를 잘못 하거나 생략한 경우도 있었고, 코드 테스트도 제대로 이루어지지 않았습니다. 따라서 이를 해결하기 위해 새로운 Spring 서버에서는 CI를 구축하고자 합니다.
이번 글에서는 CI 구축 과정에서 공부한 내용들과 어려움들에 대해 소개합니다.
(CD는 추후 배포 단계에서 구축할 예정입니다.)
event 및 branch rule 설정
우선 dev, main 브랜치의 push 작업을 막기 위하여 git branch rule을 설정하여 push를 막고, pr로만 merge가 가능하도록 github branch rule을 설정하였습니다. 다만 CI 과정 테스트를 과정에서는 dev 브랜치의 경우 push를 허용해 빠르게 workflow를 테스트 할 수 있도록 하였습니다.
이후에는 workflow event를 아래와 같이 작성하여 dev, main 브랜치의 pull request 이벤트가 있을 때, 테스트 코드를 실행하고자 하였습니다.
on:
pull_request:
branches:
- main
- dev
types:
- opened
- synchronize
추후 개발이 완료되고, CD도 구현하게 되면 closed 이벤트 역시 추가하여 분기를 통해 deploy하는 과정을 추가할 예정입니다.
workflow 작성
workflow의 job은 테스트 환경 구축을 위한 부분과 실제 빌드하는 부분으로 나뉘어집니다.
먼저 환경 구축을 위해서 checkout으로 repository 코드를 가져온 후, jdk 17을 설치하고 테스트를 위한 환경을 구축하였습니다.
또한, 테스트 환경에서 통합 테스트를 실행하기 위해서 docker compose 파일을 추가로 작성해 데이터베이스를 docker로 실행시키도록 하였습니다. 다만 docker-compose 파일과 application.yml 파일은 데이터베이스 접근이나 서버 설정에 관한 데이터가 포함되어 있기 때문에 github action secret으로 등록하여 주입하는 방식을 사용하였습니다.
- name: Inject application.yml
run: |
echo "${{ secrets.TEST_APPLICATION_YML }}" > src/main/resources/application.yml
echo "${{ secrets.TEST_DOCKER_COMPOSE_YML }}" > docker-compose.yml
local 환경에서는 다른 개발용 database를 사용하고 있고, 개발 편의성을 위해 application.yml의 ddl-auto 설정도 update로 사용하고 있습니다. 하지만 test 환경에서는 test용 데이터베이스가 필요하고, ddl-auto 설정도 create-drop으로 설정해야 했기 때문에 별도의 docker-compose 파일과 application.yml 파일을 관리하였습니다.
설정 파일 주입이 완료되면 docker compose 명령을 통해 container를 실행시키고, build 과정을 수행하도록 이어서 step을 작성하였습니다.
- name: Start Container
run: docker-compose -f docker-compose.yml up -d
- name: build code and test
run: ./gradlew clean build
- name: Quit Container
run: docker-compose -f docker-compose.yml down
CI 테스트 및 오류 해결
한번에 깔끔하게 실행됐으면 좋았겠지만 그럴리 없이 많은 오류들을 마주하였습니다.
가장 먼저 application.yml을 주입하는 과정에서 'No such file or directory' 오류가 발생하였습니다.
ls -R src/ 명령어를 활용해 파일 구조를 확인한 결과, src/main 내 resources 디렉터리가 존재하지 않는다는 것을 깨달았습니다. 로컬에 있는 yaml 파일들을 모두 gitignore 처리해주었고, 별도의 static 파일이 없었기 때문에 디렉터리가 존재하지 않아 디렉터리 생성 명령어를 추가하여 이를 해결하였습니다.
- name: Inject application.yml
run: |
mkdir -p src/main/resources
echo "${{ secrets.TEST_APPLICATION_YML }}" > src/main/resources/application.yml
echo "${{ secrets.TEST_DOCKER_COMPOSE_YML }}" > docker-compose.yml
두번째로 만난 오류는 docker-compose 명령어가 없다는 오류입니다.
GitHub Actions의 러너가 실행되는 ubuntu-latest 환경에 Docker Compose가 기본적으로 설치되어 있지 않기 때문에 해당 명령어를 실행할 수 없습니다. 따라서 docker 명령어에 내장된 docker compose 플러그인을 사용하는 방식으로 변경하여 문제를 해결하였습니다.
run: docker compose -f docker-compose.yml up -d
run: docker compose -f docker-compose.yml down
세번째로 만난 오류는 'Top-level object must be a mapping' 오류입니다. (이것 때문에 고생 많이 했습니다)
해당 오류는 docker-compose 파일이 yml 형식에 어긋나 docker 실행이 불가능해 발생하는 오류입니다.
이 오류를 해결하기 위해서 yml 파일을 모두 검사하기도 하고, 개행문자를 모두 지우기도 하고, 개행문자 스타일을 \n \r로 바꿔보기도 하고, 마지막 개행 문자를 없에고 인코딩을 하며 다양하게 시도하였지만 에러를 해결할 수 없었습니다. 검색을 통해 " 를 ' 로 바꾸는 시도와 version: key를 지우고 실행을 해보아도 모두 실패하였습니다.
검색을 통해 yml 파일의 구문 검증을 위해서 yamlint를 활용할 수 있다는 것을 알게 되어 아래 코드를 통해 구문 검증을 진행하였습니다.
- name: Validate YAML
run: |
yamllint docker-compose.yml
결과적으로 파일의 중간 개행문자가 \n 이 아니고, 마지막 개행문자가 없다는 로그를 확인할 수 있었습니다.
이전에 했던 시도들이 다르게 엇갈리면서 문제가 해결되지 않았던 것을 알 수 있었습니다. 문제 해결을 위해 decoding시 줄바꿈 스타일을 바꾸어주는 코드를 추가하고, 파일의 끝에 개행 문자를 추가하여 다시 실행한 결과...!
다른 오류가 발생하였습니다.
파일을 확인하였을 때, 데이터베이스 설정은 문제가 없었기에 역시 application.yml에도 개행문자 변경을 적용해 실행하였습니다.
- name: Inject application.yml
run: |
mkdir -p src/main/resources
echo "${{ secrets.TEST_APPLICATION_YML }}" | base64 -d | sed 's/\r$//' > src/main/resources/application.yml
echo "${{ secrets.TEST_DOCKER_COMPOSE_YML }}" | base64 -d | sed 's/\r$//' > docker-compose.yml
또한 database 실행을 기다리고, connection을 확인하는 작성하고, docker 프로세스 확인하는 로그 코드도 작성하여 문제 발생 시 바로 에러 부분을 확인할 수 있도록 하였습니다.
- name: Wait for database
run: |
while ! docker exec newzet-test-postgres pg_isready; do
sleep 1
done
timeout-minutes: 1
- name: Check container status
run: docker ps
- name: Test database connection
run: docker exec newzet-test-postgres psql -U [USER] -d [DB] -c "\l"
실행 결과 드디어 초록불을 만날 수 있었습니다!
application.yml의 개행 문자가 주요한 원인이었고, 이를 제거함으로써 문제를 해결할 수 있었습니다.
jacoco 설정
CI 구축 이후 jacoco 라이브러리를 통해 최소 테스트 커버리지를 제한하고자 하였습니다. 자료를 참고하여 jcocoTestReport와 jacocoTestCoverageVerification을 작성하였고, 아래와 같이 테스트 커버리지 제한을 설정하였습니다.
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.90
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 200
}
제한을 통해 테스트코드를 의무적으로 작성하고, 클래스를 작게 분리하도록 강제하여 유지 보수성을 높이고 확장 가능한 코드를 작성할 수 있도록 하였습니다.
수많은 오류를 보면서 github action의 다양한 기능을 사용해 볼 수 있었습니다.
또한 에러를 만났을 때 무작정 해결하려고 하기 보다는 에러 로그를 상세하게 찍어 하여 문제를 명확히 파악하고, 해결과정을 탐색하는 것이 좋다는 것도 배울 수 있었습니다.
다음 글 부터는 구축한 CI 위에서 test 코드를 작성해가며 기존 코드를 spring 서버로 옮기는 작업을 소개할 예정입니다. 공부한 객체지향의 내용에 맞추어서 도메인 주도 코드를 작성하는 과정에서 학습한 내용에 대해 공유하도록 하겠습니다.
참고자료
'프로젝트' 카테고리의 다른 글
[뉴젯] nGrinder 테스트 환경 구축 (0) | 2025.02.02 |
---|---|
[뉴젯] Redis 도입과 분산 락을 활용한 동시성 문제 해결 (0) | 2025.02.01 |
[뉴젯] 유저 지표에서 시작된 쿼리 성능 최적화 (1) | 2025.01.06 |
[뉴젯] 메일 수신 처리 속도 및 AWS Throttling 지표 개선 (1) | 2025.01.03 |
[해피에이징] 수평적 권한 상승 문제 (0) | 2024.01.11 |