CI & CD 전략 선택하기 ( 부제 : CodePipeline 사용기 )
CI & CD 의 전략, CodePipeline 사용
이번 내용은 우테코 프로젝트 내에서 배포를 진행하며 알게 된 인프라 및 구현 & 사용 방법에 대해 작성하려고 한다. joyson5582@gmail.com
이나 댓글로 궁금함이나 의견을 나타내면 최선을 다해 설명하겠습니다.
일반적으로 코드 배포를 위해선 다양한 방법들이 존재한다. 이 다양한 방법들에 대해 소개하기에 앞서 CI & CD 가 무엇인지 간략하게 그리고 흐름이 어떻게 동작하는지에 대해서 알아보자.
CI
- Continuous Integration ( 지속적 통합 )
- 새로운 코드 변경 사항을 빌드 및 테스트하여 기존 코드에 통합시키는 것이다.
CD
- Continuous Delivery or Deploy ( 지속적 배달 or 배포 )
- 변경된 코드를 기반으로 자동으로 코드를 새로 배포한다.
매우 명확하고 간단한 설명이다. 그러면, 이들의 흐름은 어떻게 될까?
흐름
- 코드 변경을 감지해서 CI 가 동작한다.
- CI 가 통과하면 기존 코드에 통합이 되고, 통합된 코드를 기반으로 빌드를 한다.
- 빌드된 결과물을 저장한다.
- 빌드된 결과물을 기반으로 CD 가 동작한다.
- CD에 명세된 대로 서버를 동작시킨다.
단순히 순서만 보면 헷갈릴 수 있는데 말로 풀어쓰면 명확하다.
변경된 코드가 괜찮은가? -> 코드를 통합하고 빌드한다 ( CI ) 빌드가 성공적으로 됐는가? -> 빌드 파일로 서버를 배포한다 ( CD )
아래에 있는 모든 방법들은 해당 틀에서 벗어나지 않는다.
그러면 다양한 방법들에 대해 보겠다.
CI
Docker Hub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Build Gradle
- name: Build Gradle
run: |
chmod +x ./gradlew
./gradlew test
shell: bash
# Docker 이미지 build 및 push
- name: docker build and push
run: |
docker login -u $ -p $
docker build -t corea/backend:latest ./backend
docker push corea/backend:latest
docker push corea/backend:latest
흐름에서 2번과 3번이다. 매우 간단하다.
Docker hub 에 로그인 -> 빌드된 결과를 기반으로 이미지 빌드 -> 이미지 Push
1
2
3
4
5
6
7
8
9
10
11
12
# Build Gradle
- name: Build Gradle
run: |
chmod +x ./gradlew
./gradlew test
shell: bash
# 빌드 파일 압축
- name: Create deploy package
run: |
mkdir -p scripts
zip -r deployment-package.zip build/libs/corea-backend.jar
이 역시도 CI의 과정이라 할 수 있다. 빌드된 결과물을 압축해서 Github Action에서 저장하므로 2번과 3번이라 할 수 있다.
CodeBuild
AWS 인프라가 제공해주는 빌드 시스템이다. 나는 이 CodeBuild 를 사용했고, 자세히 설명하겠다.
Github Repository 내 소스 버전(브랜치)를 기반으로 빌드를 한다.
- Git clone 깊이 : 이력을 어디까지 받을지 지정 -> 빌드에는 전체 이력( Commit Log ) 이 필요없다. 일반적으로 1이면 충분 ( 빌드 내에서 5개 전 커밋 이력이 필요한가? )
- Git 하위 모듈 : SubModule 을 포함시킬 지 여부이다. 나머지는 당장 중요하지 않다고 생각
빌드 하는 컴퓨터에 대해서 정의하는 부분이다. 운영체제, 이미지를 선택 ( 이미지의 버전 선택 ) 을 선택한다.
- 추가 구성으로, 컴퓨팅의 사양을 올릴수도 있다.
빌드용으로 만든 도커 이미지가 있다면 그것 역시 사용 가능하다. ( Docker Hub 에 올린 Image )
소스 코드 내 buildspec.yml
에 있는 내용을 기반으로 파일을 빌드해준다. ( AWS CodeBuild 내부에서도 관리 가능 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: 0.2
phases:
build:
commands:
- echo Build Starting on `date`
- cd backend
- chmod +x ./gradlew
- ./gradlew build
cache:
paths:
- '/root/.gradle/caches/**/*'
artifacts:
files:
- 'appspec.yml'
- 'build/libs/backend-0.0.1-SNAPSHOT.jar'
- 'scripts/**'
base-directory: backend
build 할 때 commands 를 지정한다. (phases 에는 install 이나 pre_build 같은것도 존재 하나 당장 불필요 )
root/.gradle/cahces 에 있는 모든 경로&파일들을 캐시로 저장 -> 다음 빌드는 이전 빌드에 저장된 캐시를 복원한다.
artifacts-files 를 통해 빌드할 파일들을 포함한다. ( base-directort 를 통해 기본 디렉토리 )
위에 지정한 파일들을 어디에 저장할지 지정한다. 2024-artifacts
버킷 내 2024-corea/corea-backend-deployment 파일명으로 압축으로 저장이 된다. ( 폴더로 저장해도 상관없으나 차후 Code Deploy 시 압축 파일이 필요로 한다. )
빌드 출력 로그는 왠만하면 사용하자. 업로드 안하면 빌드 진행 중이나 실패한 이유를 볼 수가 없다.
결과
결과를 출력해주고
보고서도 보여준다.
CD
Self Hosted Runner
1
2
3
4
5
6
7
8
9
deploy:
runs-on: self-hosted
steps:
- name: Pull Docker image from DockerHub
run: docker pull $/$:$
- name: Run Docker container
run: docker run -d --name backend-application -p 8080:8080 $/corea/backend:latest
이렇게 하면? Docker Image 를 Pull 받고 서버에서 배포를 실행을 한다. 서버에 대한 명시가 없는 이유는 self-hosted runner 스스로가 명령어를 실행하기 때문이다.
EC2에 직접 접속해서 해당 명령어를 실행하면 인스턴스가 직접 깃허브 액션을 수행한다.
이떄의 단점이라면?
- 결국 주기적으로 통신해 새로운 작업이 있는지 확인한다 - Polling 방식
- HTTPS 를 통해 통신, 443 포트 이용해 작업
- 결국 자신이 직접 run 시키므로, 리소스 낭비가 될 수 있다. 등이 있다.
우테코 프로젝트 사람들은 대부분 self-hosted runner 를 사용했는데 AWS Secret Key 가 없어서 Credentials 를 통해 할 수 없어서 self-hosted runner 로 이렇게 동작을 시켰다.
사실, 해당 부분에서 Docker 가 필수적이라고 생각할 수 있는데 빌드된 파일을 EC2 내부까지 옮길수만 있다면 크게 상관 없다. ( S3 등등 )
ssh-action
1
2
3
4
5
6
7
8
9
10
11
12
...
uses: appleboy/ssh-action@master
with:
host: $
username: $
key: $
port: $
script: |
...
docker run -d --name backend-application -p 8080:8080 $/corea/backend:latest
서버에 SSH 연결을 통해 명시한 스크립트를 실행해서 배포한다. self-hosted runner 와 비슷하다고 생각할 수 있으나
- ssh-action : 우리가 직접 접속해서 실행
- self-hosted runner : 변화를 Polling 해서 서버가 자체적으로 실행
의 차이가 있다.
우테코 프로젝트는 SSH 연결이 막혀있어서 해당 방식 불가능
위와 똑같이 Docker 가 중요한게 아닌, 서버 접속해 직접 실행한다는게 중요하다
AWS Credentials + S3 + Code Deploy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: $
- name: Upload to S3
run: |
aws s3 cp deployment-package.zip s3://$/deployment-package.zip
- name: Deploy to CodeDeploy
run: |
aws deploy create-deployment \
--application-name $ \
--deployment-group-name $ \
--s3-location bucket=$,key=deployment-package.zip,bundleType=zip
아마존 credentials 로 직접 인증 받아서 빌드 업로드 + 업로드 파일 기반 배포를 진행한다.
우테코 프로젝트는 Secret Key를 발급 못 받아서 인증 불가능
Code Deploy
AWS 인프라가 제공해주는 배포 시스템이다. Build 와 똑같이 이를 사용했으니, 자세히 설명한다.
애플리케이션에서 본인이 배포할 컴퓨팅 플랫폼에 대해서 정한다. ( EC2/온프레미스, AWS Lambda, ECS ) 그후, 배포 그룹을 생성한다. - 이때 그룹에 따라서 개발용 / 운영용 등 분리 가능
현재 배포 전략에 대해선 중요하지 않고, Auto Scaling / Load Balancer 역시 중요하지 않다. -> 개발 서버 온전히 구동만 하면 OK
태그의 Key 값을 통해 인스턴스를 명시한다.
이때 에이전트를 구성할거냐고 묻는데 몹시몹시 중요하다!! 중요한 이유는 배포 생성까지 끝내고 설명한다.
그룹을 생성하면 배포를 생성한다. 배포 그룹 지정 + 저장된 S3 파일을 지정한다.
에이전트가 설치되어 있지 않으면 EC2 는 파일 전송을 받지 못하고
이렇게 not able to receive the lifecycle event.
를 발생시킨다. 해당 에러가 뜨면 Code Deploy Agent 가 작동하는지 확인해보자.
서비스 동작 확인
1
sudo service codedeploy-agent status
동작했는데 아예 없다면?
1
2
3
4
5
6
7
8
#!/usr/bin/env bash
sudo apt-get update -y
sudo apt-get install -y ruby
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
를 통해 설치하자.
서비스 동작 권한 부족
설치하고 배포를 재 실행했는데 위와 같이 실패가 또 뜬다면?
cat /var/log/aws/codedeploy-agent/codedeploy-agent.log
를 통해 로그를 보자.
1
2
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials
- please check if this instance was started with an IAM instance profile
해당 로그가 뜨면 EC2 IAM 이 CodeDeploy 를 수행할 권한이 있는지 확인하자. -> 권한을 부여했는데 안된다면?
1
sudo service codedeploy-agent restart
로 재시작후 다시 확인해보자
이렇게 성공하면 끝이다.
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
version: 0.0
os: linux
files:
- source: /build/libs
destination: /home/ubuntu/build
- source: /scripts
destination: /home/ubuntu/scripts
- source: appspec.yml
destination: /home/ubuntu/build
permissions:
- object: /
pattern: "**"
owner: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
ValidateService:
- location: scripts/healthCheck.sh
timeout: 30
runas: ubuntu
CodeBuild 와 유사하게 appspec.yml
에 있는 내용을 기반으로 서버를 배포한다.
파일을 저장시킬 곳을 지정하고, 파일들에 권한을 주고 각 hooks 마다 실행할 스크립트들을 지정한다.
이때 EC2 에는 우리가 직접 자바 등을 설치해줘야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 자바 설치
wget https://download.oracle.com/java/17/archive/jdk-17.0.11_linux-aarch64_bin.tar.gz -O /tmp/openjdk-17_linux-x64_bin.tar.gz
2. 압축 해제
sudo tar xfvz /tmp/openjdk-17_linux-x64_bin.tar.gz --directory /usr/lib/jvm
3. 자바 압축 파일 삭제
rm -f /tmp/openjdk-17_linux-x64_bin.tar.gz
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-17.0.11/bin/java 100
4. 자바 설정
sudo update-alternatives --set java /usr/lib/jvm/jdk-17.0.11/bin/java
5. 자바 환경 변수 설정
export JAVA_HOME=/usr/lib/jvm/jdk-17.0.11
export PATH=$PATH:/usr/lib/jvm/jdk-17.0.11/bin
서버를 8080에 연다면?
1
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
IP 포트포워딩도 진행하자
이렇게 CodeDeploy 를 활용해 배포가 끝이났다.
Code Pipeline
코드 파이프라인은 CodeBuild 와 CodeDeploy 를 합쳐놓은 것으로 정말 무중단 배포가 가능하게 해주는 인프라다.
파이프라인 설정
대기를 할 시, 코드 커밋이 계속 일어날 시 차례대로 배포를 진행한다. ( 1.0.0 진행 -> 1.0.1 진행 ) 추가로, 밑에 고급 설정에서 아티팩트 스토어에서 기존 버킷을 지정할 수 있다. ( 기본 위치시 자동으로 버킷 생성 )
소스 스테이지
WebHook 을 통해 Github 가 자신의 변경을 AWS CodePipeline 에게 알려준다. -> 이를 기반으로 파이프라인이 동작한다.
빌드 스테이지
생성된 빌드를 재사용 가능하다.
배포 스테이지
역시 배포 & 배포 그룹 재사용 가능하다
이때 세부 정보들을 보면
Source 는 출력으로 SourceArtifact
-> Build 는 입력으로 SourceArtifact
-> BuildArtfiact
Deploy 는 입력으로 BuildArtifact
가 들어오고 배포를 하며 끝이난다.
이때 버킷을 들어가보면?
이렇게 스스로 폴더 + Build/SourceArtifact 를 만든다. ( 내부에도, 소스 아티팩트와 빌드된 결과물 존재 )
=> 이 결과 우리는 소스를 Github 에 Push 하면 배포가 자동으로 이루어진다. ( Github Action 에 의존적이지 않음 )
마무리
이 밖에도 수많은 CI 방법과 CD 방법이 존재할 것이다.
- 각자의 팀에 적용할 수 있는지
- AWS 인프라에 의존을 해도 상관이 없는지
- 빌드된 결과물을 어떻게 관리할지
- 서버에 배포를 얼마나 편리하게 할지 ( 로드 밸런싱, 오토 스케일링, 블루-그린 전략 )
이런 내용들을 생각하며 각자 팀에 맞는 CI / CD 를 수립하는게 좋은거 같다.
우리 팀은 AWS SecretKey 가 없으나 AWS 인프라를 사용해야 하는 점 + Self-Hosted Runner 를 사용하기 싫은점 때문에 AWS 에 완벽하게 의존을 하는 CodePipeline 을 사용하기로 했다.
이때, 배포가 성공/실패 했는지 또는 배포된 주소로 바로 들어가기 위해선 AWS Chatbot
이나 AWS CloudWatch + AWS Lambda + Slack Webhook
을 통해서 가능하다고 하다. 이는 차차 도입해볼 예정
그리고, CI-CD 가 무조건 하나로 이어질 필요는 없다.
내가 CodeBuild 를 굳이 사용할 필요가 없다고 판단해 Github Action 에서 직접 빌드하고 파일을 전달하는 것도 가능하다.
CodeBuild는 사용하더라도 CodeDeploy 를 쓰지않고 우리가 빌드된 파일로 직접 실행해도 괜찮지 않을까?
빌드가 되었다고, 꼭 배포가 자동으로 될 필요는 없을수도 있을테니까.
https://github.com/woowacourse-teams/2024-corea 해당 프로젝트에 도입했으며 다시 한번, 부족한 지식이 포함되어 있는 글이기에 댓글은 언제나 환영한다.
참고
https://jojoldu.tistory.com/282 https://github.com/jaeyeonling/reaction-game/tree/main