이번 프로젝트는 Jenkins 대신 Circle CI를 사용했다. 이전 프로젝트에서는 젠킨스를 사용하기 위해 추가적인 인스턴스를 만들어서 사용하기에는 비용 문제가 있어서, 어플리케이션을 배포하고 있는 프리티어 서버에 도커 컨테이너를 올려서 사용했다. 부족한 메모리를 해결하기 위해 스왑메모리를 설정했지만, 프리티어 서버가 느려지거나 멈추는 문제가 가끔 발생했었다. 그래서 클라우드 환경을 제공해주는 Circle CI를 사용하게 됐다.
프로젝트에선 테스트를 위해 TestContainers 를 사용하고 있다. TestContainers는 테스트 환경에 필요한 컨테이너들의 생명주기를 관리해준다.
처음에는 다른 래퍼런스를 따라서 jdk17 Executor를 사용했고, https://www.atomicjar.com/2022/12/testcontainers-with-circleci/
Testcontainers with CircleCI - AtomicJar
Learn how to run Testcontainers based tests on CircleCI and how Testcontainers Cloud simplifies and speeds up the tests execution.
www.atomicjar.com
블로그를 통해 docker image 실행기를 통해서는 TestContainers 사용이 불가능하기 때문에 TestContainers 클라우드를 통해 연결을 하도록 스크립트를 작성했다.
version: 2.1
orbs:
gradle: circleci/gradle@3.0.0
tcc: atomicjar/testcontainers-cloud-orb@0.1.0
slack: circleci/slack@4.4.4
aws-cli: circleci/aws-cli@4.0
aws-ecr: circleci/aws-ecr@9.0.0
executors:
jdk17:
docker:
- image: cimg/openjdk:17.0.8
jobs:
build-test:
executor: jdk17
steps:
- checkout
- run:
name: run build and test
command: gradle build
- run:
name: send report sonarqube
command: gradle sonar
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
deploy:
executor: jdk17
steps:
- checkout
- run:
name: set short sha
command: |
echo 'export SHORT_SHA=${CIRCLE_SHA1:0:7}' >> "$BASH_ENV"
source "$BASH_ENV"
- aws-cli/setup
- aws-ecr/ecr_login:
public_registry: true
- run:
name: push Image to ECR
command: gradle jib
- run:
name: connect ec2 and deploy
command: |
ssh -o StrictHostKeyChecking=no $EC2_USERNAME@$EC2_HOST SHORT_SHA=$SHORT_SHA AWS_ECR_URL=$AWS_ECR_URL sh < deploy.sh
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
workflows:
build-and-deploy:
jobs:
- build-test:
pre-steps:
- tcc/setup
context:
- slack-secret
- prod-deploy
- SonarCloud
- deploy:
requires:
- build-test
context:
- slack-secret
- prod-deploy
filters:
branches:
only:
- main
흐름은 다음과 같다.
Job
- build-test : 빌드와 테스트를 하는 작업
- executor : 작업을 수행할 실행기
- checkout : repository 다운로드
- run : 실행할 작업 build, sonar
- slack/notify : 작업의 결과에 따라 슬랙 메세지 전송
- deploy : 배포
- executor : 작업을 수행할 실행기
- checkout : repository 다운로드
- run : 도커 빌드 이미지에 지정할 태그 값 구하기 -> jib 이미지 빌드 후 ECR 전송 -> 인스턴스 서버 접속 후 배포 스크립트 실행
- slack/notify : 작업의 결과에 따라 슬랙 메세지 전송
- requires : build-test가 선행되어야함
- filters : main 브랜치에 머지되었을때만 실행
첫 파이프라인은 문제 없이 진행되었지만 바로 다음 파이프라인에서 TestContainers cloud 에 연결이 되지 않는 문제가 생겼다. 무료플랜에서는 작업간의 약 10 ~ 15 분 정도의 필요하기 때문이었다. 그리고 Spring Rest API 를 적용하면서 문서를 만들기 위해서 테스트 -> 빌드가 필수적이기 때문에 deploy job 에서도 gradle build (test -> jacoco -> asciidoc 순으로 진행) 가 추가되어야 했고, 무료플랜으로는 위의 스크립트를 실행할 수 없었다.
그래서 testContainers cloud 사용을 중단하고 linux ubuntu machine 을 사용하는 방식으로 변경했다.
version: 2.1
orbs:
slack: circleci/slack@4.4.4
aws-cli: circleci/aws-cli@4.0
aws-ecr: circleci/aws-ecr@9.0.0
executors:
linux:
machine:
image: ubuntu-2004:2023.07.1
docker_layer_caching: true
jobs:
build-test:
executor: linux
steps:
- checkout
- run:
name: run build and test
command: gradle build
- run:
name: send report sonarqube
command: gradle sonar
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
deploy:
executor: linux
steps:
- checkout
- run:
name: run build and test
command: gradle build
- run:
name: set short sha
command: |
echo 'export SHORT_SHA=${CIRCLE_SHA1:0:7}' >> "$BASH_ENV"
source "$BASH_ENV"
- aws-cli/setup
- aws-ecr/ecr_login:
public_registry: true
- run:
name: push Image to ECR
command: gradle jib
- run:
name: connect ec2 and deploy
command: |
ssh -o StrictHostKeyChecking=no $EC2_USERNAME@$EC2_HOST SHORT_SHA=$SHORT_SHA AWS_ECR_URL=$AWS_ECR_URL sh < deploy.sh
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
workflows:
build-and-deploy:
jobs:
- build-test:
context:
- slack-secret
- prod-deploy
- SonarCloud
- deploy:
requires:
- build-test
context:
- slack-secret
- prod-deploy
filters:
branches:
only:
- main
이제 파이프라인간 수행 속도를 비교해 보겠다.
1. 변경 전
2. 변경 후
TC (testContainers cloud) 와 연결을 수행하는 시간과 변경 후 내부에서 도커 컨테이너를 실행시키는 시간 때문에 전체적인 CI 시간은 2:41 과 2:36 으로 그게 다르진 않다. 하지만 배포과정에서 테스트와 빌드과정이 추가되었기 때문에 배포 시간은 1분 이상 걸리게 됐다.
CircleCI의 문서를 보면 다음과 같은 것을 알 수 있다.
- 작업공간은 단일 워크플로의 작업 간에 데이터를 유지합니다.
- 캐싱은 서로 다른 워크플로 실행의 동일한 작업 간에 데이터를 유지합니다.
그래서 build-test job에서 생성된 빌드 파일을 deploy에 전송해 배포과정에서 불필요한 빌드를 생략하고 빌드 과정시 gradle의 의존성을 캐싱하면 시간이 단축될 것이라고 생각했다. 그래서 수정한 스크립트는 다음과 같다.
version: 2.1
orbs:
slack: circleci/slack@4.4.4
aws-cli: circleci/aws-cli@4.0
aws-ecr: circleci/aws-ecr@9.0.0
executors:
linux:
machine:
image: ubuntu-2004:2023.07.1
docker_layer_caching: true
jobs:
build-test:
executor: linux
steps:
- checkout
- restore_cache:
keys:
- v1-gradle-cache-{{ checksum "build.gradle" }}
- run:
name: run build and test
command: gradle build
- run:
name: send report sonarqube
command: gradle sonar
- save_cache:
paths:
- ~/.gradle/caches
key: v1-gradle-cache-{{ checksum "build.gradle" }}
- persist_to_workspace:
root: .
paths:
- .
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
deploy:
executor: linux
steps:
- attach_workspace:
at: .
- run:
name: set short sha
command: |
echo 'export SHORT_SHA=${CIRCLE_SHA1:0:7}' >> "$BASH_ENV"
source "$BASH_ENV"
- aws-cli/setup
- aws-ecr/ecr_login:
public_registry: true
- run:
name: push Image to ECR
command: gradle jib
- run:
name: connect ec2 and deploy
command: |
ssh -o StrictHostKeyChecking=no $EC2_USERNAME@$EC2_HOST SHORT_SHA=$SHORT_SHA AWS_ECR_URL=$AWS_ECR_URL sh < deploy.sh
- slack/notify:
event: fail
template: basic_fail_1
- slack/notify:
event: pass
template: basic_success_1
workflows:
build-and-deploy:
jobs:
- build-test:
context:
- slack-secret
- prod-deploy
- SonarCloud
- deploy:
requires:
- build-test
context:
- slack-secret
- prod-deploy
filters:
branches:
only:
- main
- save_cache : gradle 의존성의 캐시파일은 .gradle/caches/ 에 생성되는데 현재 build.gradle 파일의 정보를 기준으로 캐시의 키를 결정해 저장한다.
- restore_cache: 저장되어있는 캐시파일을 불러오는 작업이다. build.gradle이 변경되었다면 키에 해당하는 캐시를 불러오지 못한다.
- persist_to_workspace: 워크플로우 작업간에 공유할 데이터를 저장한다.
- attach_workspace: 저장된 데이터를 불러온다.
스크립트를 수정함에 따라 deploy 작업에서 checkout 과 build test 과정이 생략됐고 의존성을 캐싱하게 되었다.
결과
의존성이 캐싱되었고 빌드 시간이 1분52초 -> 1분26초로 26초 개선, 배포 작업은 2분30초 -> 1분16초로 1분14초, 전체 1분 40초 개선된 것을 확인 할 수 있다!