사내에서는 EC2 에 직접 접속하여 Git 을 통해 새로운 소스 코드를 pull 받고, Docker Image 로 컨테이너를 띄워 배포하는 방식을 사용하고 있었습니다.
하지만 매번 EC2 에 접속해 직접 Git, Docker 명령을 타이핑하여야 한다는 점은 굉장히 번거로웠고, 이는 배포에 대한 부담감으로 이어졌습니다.
따라서 몇 달 간, 번거로운 배포 과정을 간소화하기 위해 CI/CD 태스크를 담당해 진행하였고 얼마 전, 프로젝트에 성공적으로 적용하였습니다.
이번 포스팅에서는 GitHub Actions, AWS CodeDeploy 를 활용하여 사내 프로젝트에 CI/CD 를 적용했던 과정에 대해 소개하고, 적용된 CI/CD 시스템의 플로우에 대해 정리해보도록 하겠습니다.
Task 소개
먼저, 이번 CI/CD 태스크에 대해 간단히 소개하도록 하겠습니다.
기존의 배포 프로세스에 CI/CD 를 적용하고, 이를 파이프라인화하여 목표 배포 프로세스로 개선하여야 합니다.
CI/CD 적용 전, 기존의 배포 프로세스는 아래와 같습니다.
소스 코드 작업이 끝나면, GitHub Pull Request 를 통해 Merge 합니다. 이 때, 코드 병합 후 어떤 테스트도 수행되지 않습니다.
이후 EC2 에 직접 접속하여 병합된 소스 코드를 Pull 하고, Docker Image 파일을 빌드하여 컨테이너로 배포합니다.
이 모든 과정은 수동으로 이루어지며, 각각의 환경 별로 반복해서 작업해주어야 합니다.
아래는 CI/CD 적용 후, 개선된 배포 프로세스입니다.
GitHub Pull Request 를 통해 Merge 하는 과정까지는 동일합니다.
하지만 이후 AWS EC2 접속부터 Deployment 까지의 네 단계가 CI/CD Pipeline 이라는 하나의 단계로 대체되었습니다.
그 이유는 소스 코드 Merge 이후 수동으로 작업하던 배포 과정이 모두 자동화되었기 때문입니다.
다시 말해 CI/CD 가 적용된 이후에는 더 이상 EC2 에 접속하여 Git 과 Docker 명령을 터미널에 직접 입력할 필요가 없습니다.
Merge 이벤트 혹은 Git Push 이벤트가 발생하면, 새로운 소스 코드는 자동으로 EC2 에서 배포될 것입니다.
CI/CD 적용을 통한 기대 효과는 크게 아래와 같습니다.
먼저 기존 배포 프로세스의 대부분의 과정을 자동화하여 번거로운 배포 과정을 간소화하는 것이 가장 큰 목적입니다.
이렇게 되면 배포에 대한 부담감이 줄어들고, 따라서 작은 단위의 소스 코드 변경사항들이 더욱 잦은 빈도로 배포될 수 있을 것입니다.
또한 기존에 수동으로 이루어지던 배포 프로세스를 자동화하여 휴먼 에러를 방지할 수 있습니다.
잘못된 브랜치의 소스 코드를 배포하거나, 다른 서버에 소스 코드가 배포되는 등 배포 과정에서 발생할 수 있는 다양한 휴먼 에러들을 예방하고, 배포 작업의 일관성을 보장할 수 있습니다.
Branch 전략
본격적인 CI/CD 구축 과정을 살펴보기 전에, 현재 채택하여 사용하고 있는 Git 브랜치 전략에 대해 간단히 정리해보겠습니다.
사내에서는 각각의 브랜치를 기준으로 환경을 구분하고 있고, 이는 아래와 같습니다.
🔥 main
main 은 현재 서비스 중인 버전의 브랜치로 Service 환경이라고도 하며,
검수가 완료된 후 release 브랜치로부터 Merge 되어 배포됩니다.
🚧 release
release 는 현재 검수 중인 브랜치로 Staging 환경이라고도 하며, 바로 다음 배포에 main 브랜치로 Merge 됩니다.
Merge 후 제거되는 일회성 브랜치이며, 브랜치 이름에는 release/v* 의 형식으로 배포되는 소스코드의 버전이 항상 포함되어 있습니다.
🔨 develop
현재 개발 중인 Feature 들이 상시로 Merge 되는 브랜치입니다.
한 배포 단위의 Feature 들이 모두 Merge 되면, release 브랜치로 분기됩니다.
CI/CD 설계
위처럼, 사내에서 사용하는 환경은 Development 환경(develop), Staging 환경(release), Service 환경(main) 이렇게 세 가지이며, 이 중 배포가 필요한 환경은 Staging 환경(release)과 Service 환경(main) 두 가지입니다.
Staging 환경 배포는 개발된 버전의 애플리케이션이 사용자에게 전달되기 이전에 내부 테스트를 위해 필요하며,
Service 환경 배포는 내부 테스트, 즉 검수가 완료된 애플리케이션을 사용자에게 전달하기 위한 배포를 의미합니다.
따라서 CI/CD 또한 아래와 같이 두 가지 환경에 모두 적용되어야 합니다.
🔥 main
검수가 완료된 소스 코드가 사용자에게 배포되는 프로세스입니다.
release 브랜치에서 main 브랜치로 Merge 이벤트가 발생하면, 지정된 CI/CD 프로세스가 수행되어 자동으로 배포되어야 합니다.
🚧 release
내부 검수를 위해 소스 코드가 Staging 환경에 배포되는 프로세스입니다.
release 브랜치에서 Push 이벤트가 발생하면, 지정된 CI/CD 프로세스가 수행되어 자동으로 배포되어야 합니다.
이제 CI/CD Pipeline 에서 수행되어야 할 작업들을 정의해주어야 합니다.
기본적으로 두 환경 모두 CI/CD Pipeline 에서 수행하여야 할 작업은 동일합니다.
CI 에서는 주로 소스 코드 빌드와 같은 각종 테스트를 거치게 되며, 이 모든 과정이 통과되었을 경우, 코드를 Merge 합니다.
이후, Merge 된 소스 코드는 CD 프로세스를 통해 Repository 로 릴리즈되며, 서비스 환경에 배포됩니다.
위와 같은 CI/CD 프로세스를 사내 프로젝트 환경에 맞추어 설계해보면, 아래와 같습니다.
🟢 CI Steps
📦 CD Steps
CI 프로세스에서는 빌드 테스트를 통해 Merge 될 소스 코드 버전이 정상적으로 빌드되는지를 확인합니다.
🚨 참고로 사내에서는 현재 테스트 코드를 작성하고 있지 않기 때문에 빌드 테스트만을 수행하지만,
단위 테스트와 통합 테스트가 포함되지 않은 CI 는 CI 로서의 의미가 떨어진다고 생각합니다. 🚨
이후, 빌드 테스트를 통과한 소스 코드는 Merge 되고, CD 프로세스가 수행됩니다.
CD 프로세스의 첫 단계에서는 Merge 된 소스 코드가 각각의 배포 환경, 즉 각각의 EC2 서버로 전달되고, 마지막으로 전달된 소스 코드의 Docker Image 가 자동으로 컨테이너화되면서 배포되어 CI/CD Pipeline 은 종료됩니다.
이 과정은 GitHub Actions 와 AWS CodeDeploy 로 간단하게 구현이 가능하며, 구체적인 흐름은 아래와 같습니다.
🟢 CI Steps with GitHub Actions
📦 CD Steps with GitHub Actions & AWS CodeDeploy
먼저 GitHub Actions 를 활용해 사전에 정의된 이벤트 발생 시 빌드 테스트를 수행하고, 테스트를 통과한 경우에 코드가 Merge 될 수 있도록 구현할 수 있습니다.
또한, Merge 된 소스 코드를 AWS S3 에 업로드하고, AWS CodeDeploy 에 배포 요청을 전달하도록 구현합니다.
배포 요청을 전달받은 AWS CodeDeploy 는 AWS S3 에 업로드된 소스 코드를 사전에 정의된 EC2 로 가져오며, 작성된 스크립트에 따라 해당 파일을 배포하고 CI/CD Pipeline 은 종료됩니다.
CI/CD 구축
아래에서는 앞서 설계한 CI/CD 프로세스를 기반으로 직접 GitHub Actions 스크립트를 작성하고, AWS CodeDeploy 서비스를 구성해보도록 하겠습니다.
1. GitHub Actions 작성 (1)
먼저, 프로젝트 루트 경로에 /.github/workflows/ 디렉터리를 생성하고 두 개의 GitHub Actions 파일을 작성하여야 합니다.
📌 두 개의 파일로 분리한 이유 📌
이는 각 브랜치를 기준으로 서로 다른 GitHub Actions 파일을 사용하기 위함입니다.
main 브랜치는 실서비스 배포 목적의 브랜치이고, release 브랜치는 검수 목적의 브랜치이기 때문에 추후 두 브랜치 간 Workflow 에 차이가 벌어질 가능성이 높다고 판단했기 때문입니다.
예를 들어, 검수 목적의 CD 프로세스에서는 실서비스와는 다른 환경 변수를 참조해야하거나, S3 혹은 EC2 인스턴스 및 세부적인 환경이 변경되어야 하는 경우가 있을 수 있습니다.
이 때, 실서비스 배포와 검수용 서비스의 배포 프로세스가 하나의 파일에 함께 작성되어 있다면 수정이나 확장에서 불리하고, 자칫 하나의 파일에 너무 많은 내용의 코드가 작성될 우려가 있어 이와 같이 분리하여 작성하였습니다.
1-1) Event 지정
각각의 파일 내에는 스크립트가 트리거될 이벤트를 작성합니다.
name: 🚀 Deploy workflow on production environment
on:
pull_request:
branches: [main]
types: [closed]
jobs:
deploy:
if: github.event.pull_request.merged == true
name: 🚀 Deploy workflow on test environment
on:
push:
branches: ['release/v**']
jobs:
deploy:
1-2) GitHub Repository Environment 생성
배포를 위해서는 새로운 버전의 소스 코드 뿐만 아니라, 애플리케이션 로드에 필요한 환경변수 파일을 생성하여 함께 전달해주어야 합니다.
이 때, 환경변수는 코드와 함께 버전 관리될 수 없기 때문에 GitHub Repository Secrets 로부터 환경변수를 읽어와 파일을 생성할 수 있습니다.
중요한 점은 각각의 파일이 배포 환경에 따라 서로 다른 환경변수를 참조하여야 한다는 것입니다.
이를 위해, GitHub Repository 에 production 과 test 라는 Environment 를 각각 생성해주었습니다.
Repository Settings → Environments → New environment
Environment 클릭 → Environment secrets → Add secret
GitHub Actions environment 지정
jobs:
deploy:
if: github.event.pull_request.merged == true
environment: production
jobs:
deploy:
environment: test
2. AWS S3 생성
GitHub Actions 로부터 소스 파일이 업로드될 AWS S3 버킷을 생성합니다.
2-1) S3 버킷 생성
3. AWS EC2 IAM 생성
EC2 는 CodeDeploy 를 통해 S3 버킷에 업로드된 소스 파일을 가져와 배포하게 되는데, 이를 위해 CodeDeploy 와 S3 버킷에 모두 접근할 수 있는 IAM 권한을 생성하여 EC2 에 부여해주어야 합니다.
3-1) EC2 IAM 생성
3-2) EC2 IAM 연결
앞서 생성한 IAM 역할을 EC2 와 연결합니다.
4. AWS CodeDeploy 생성
이제 CodeDeploy 애플리케이션을 생성하고, IAM 역할을 할당해주어야 합니다.
4-1) CodeDeploy IAM 생성
4-2) CodeDeploy 애플리케이션 생성
5. IAM 사용자 생성
GitHub Actions 는 소스코드를 S3 버킷에 업로드하고, CodeDeploy 의 특정 배포 그룹에 대한 배포 요청을 전달하여야 합니다.
이를 위해서는 권한이 필요하고, 해당 권한을 가진 사용자를 생성하여 GitHub Actions 가 인증해 권한을 사용할 수 있도록 설정해주어야 합니다.
5-1) IAM 사용자 추가
5-2) IAM 사용자 프로그래밍 방식 액세스 키 생성
GitHub Actions 가 사용자로 인증하기 위해 사용할 액세스 키를 생성합니다.
5-3) GitHub Repository Secrets 액세스 키 저장
GitHub Actions 가 CD 프로세스 내에서 앞서 만든 키를 사용해 인증하기 위해서는 GitHub Repository Secrets 에 키를 저장해야 합니다.
6. EC2 CodeDeploy-Agent 설치
CodeDeploy 가 S3 버킷에 업로드된 코드를 EC2 의 특정 위치에 배포하기 위해서는 배포 대상 EC2 에 codedeploy-agent 가 설치되어 있어야 합니다.
현재 production 과 test 환경은 별도의 EC2 를 사용하고 있기 때문에 두 인스턴스 모두에 codedeploy-agent 를 설치합니다.
codedeploy-agent 설치 전, EC2 에는 npm, yarn 과 같은 패키지 매니저와 git, nvm, node 가 필수적으로 설치되어 있어야 합니다.
6-1) awscli 설치
아래는 EC2 에 awscli 를 설치하는 과정입니다.
a) AWS EC2 접속
b) AWS CLI 설치
$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
$ unzip awscliv2.zip
$ sudo ./aws/install
c) AWS configure 인증 정보 입력
$ sudo aws configure
AWS Access Key ID [None]: AWS_ACCESS_KEY_ID 입력!
AWS Secret Access Key [None]: AWS_SECRET_ACCESS_KEY 입력!
Default region name [None]: ap-northeast-2
Default output format [None]: json
6-2) codedeploy-agent 설치
a) Ruby 패키지 설치
codedeploy-agent 는 Ruby 로 작성되었기 때문에 이를 EC2 에서 실행하기 위해 Ruby 패키지를 설치해주어야 합니다.
$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
b) CodeDeploy Agent 설치 파일 다운로드
Ubuntu 의 경우, /home/ubuntu 로 이동하여 설치 파일을 다운로드합니다.
CentOS 의 경우, /root 에서 설치하여도 문제가 발생하지 않았습니다.
codedeploy-agent 설치 파일을 다운로드하였다면, 설치 파일에 실행 권한을 부여합니다.
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
c) CodeDeploy Agent 설치
다운로드한 install 파일을 실행하여 codedeploy-agent 를 설치합니다.
📌 CodeDeploy Agent 설치 이슈
공식문서에 따르면, 현재 Ubuntu 20.04 이상 버전에서 codedeploy-agent 설치 시 이슈가 있어 설치 과정의 출력을 임시 로그 파일에 작성하여 해결한다고 합니다.
$ sudo ./install auto > /tmp/logfile
d) CodeDeploy 데몬 실행 확인
$ sudo service codedeploy-agent status
e) CodeDeploy 인스턴스 부팅 시 자동 실행 설정
$ sudo vim /etc/init.d/codedeploy-startup.sh
#!/bin
sudo service codedeploy-agent restart
$ sudo chmod +x /etc/init.d/codedeploy-startup.sh
7. appspec.yml 작성
이제 프로젝트 루트 경로에 appspec.yml 파일을 작성합니다.
CodeDeploy 가 EC2 에 소스 코드를 배포하는 과정을 이 파일에 정의할 수 있습니다.
중요한 점은, 프로젝트 당 appspec.yml 은 유일해야 하며, 항상 루트 경로에 위치하여야 한다는 점입니다.
다시 말해 production 과 test 에서 수행될 프로젝트 배포 과정은 모두 동일하게 작성되어야 합니다.
appspec.yml 파일이 위치한 프로젝트 루트를 기준으로, 구조에 맞게 EC2 디렉터리를 생성하고 각각의 파일을 위치시켜줍니다.
완성된 appspec.yml 파일 예시는 아래와 같습니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/api/api_back
- source: /nginx
destination: /home/api/nginx
- source: /docker-compose.yml
destination: /home/api
- source: /.env
destination: /home/api
- source: /config/
destination: /home/api/config
file_exists_behavior: OVERWRITE
permissions:
- object: /home/api
pattern: '**'
owner: root
group: root
hooks:
AfterInstall:
- location: scripts/after-deploy.sh
timeout: 2000
runas: root
8. GitHub Actions 작성 (2)
1번에서 작성했던 GitHub Actions 스크립트 파일을 마저 작성하도록 하겠습니다.
8-1) 환경 변수 파일 생성
GitHub Actions 에서 환경 변수 파일을 생성하기 위해서는 GitHub Repository Secret 에서 사전에 추가했던 환경 변수들을 불러와주어야 합니다.
GitHub Actions 에서는 이를 위해 secrets 객체를 제공하고 있으며, 이를 통해 환경 변수들을 불러올 수 있습니다.
jobs:
deploy:
if: github.event.pull_request.merged == true
env:
ENV_PATH: .env
environment: production
runs-on: ubuntu-latest
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: ⚙️ Create .env file
run: |
touch ${{ env.ENV_PATH }}
echo DOMAIN_FIR=${{ secrets.DOMAIN_FIR }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_PROD=${{ secrets.SOCKET_PORT_PROD }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_TEST=${{ secrets.SOCKET_PORT_TEST }} >> ${{ env.ENV_PATH }}
jobs:
deploy:
env:
ENV_PATH: .env
environment: test
runs-on: ubuntu-latest
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: ⚙️ Create .env file
run: |
touch ${{ env.ENV_PATH }}
echo DOMAIN_FIR=${{ secrets.DOMAIN_FIR }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_PROD=${{ secrets.SOCKET_PORT_PROD }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_TEST=${{ secrets.SOCKET_PORT_TEST }} >> ${{ env.ENV_PATH }}
8-2) 프로젝트 파일 압축
배포 대상 프로젝트 소스와 앞서 생성한 환경 변수 파일을 한 번에 묶어 압축하는 과정입니다.
S3 버킷으로 업로드할 파일의 크기를 최대한 줄이기 위해 압축하게 됩니다.
- name: 📦 Zip project files
run: zip -r ./$GITHUB_SHA.zip .
8-3) AWS 인증
- name: 🌎 Access to AWS
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
8-4) S3 에 압축된 파일 업로드
- name: 🚛 Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
8-5) CodeDeploy 배포
- name: 🚀 Deploy to EC2 with CodeDeploy
run: aws deploy create-deployment
--application-name codedeploy-app
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name ${{ env.DEPLOYMENT_GROUP_NAME }}
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
9. after-deploy.sh 작성
after-deploy.sh 는 앞서 appspec.yml 에서 걸어놓은 이벤트 Hook 으로, CodeDeploy 가 모든 프로젝트의 배포를 완료한 뒤에 실행되는 파일입니다.
#!/bin/bash
REPOSITORY=/home/api/
cd $REPOSITORY/api_back
echo "> 🔵 Stop & Remove docker services."
cd ..
docker compose down
echo "> 🟢 Run new docker services."
docker compose up --build -d
10. GitHub Actions 작성 (3)
지금까지 작성된 GitHub Actions 파일은 아래와 같습니다.
두 스크립트 파일 모두 각각의 브랜치에 지정한 이벤트가 발생할 경우 실행되며, 환경 변수 파일을 생성하고 프로젝트 파일과 함께 압축하여 S3 버킷으로 업로드합니다.
이후, CodeDeploy 에 업로드한 파일과 함께 배포 요청을 전달하게 됩니다.
name: 🚀 Deploy workflow on production environment
on:
pull_request:
branches: [main]
types: [closed]
jobs:
deploy:
if: github.event.pull_request.merged == true
env:
ENV_PATH: .env
S3_BUCKET_DIR_NAME: production
DEPLOYMENT_GROUP_NAME: production
environment: production
runs-on: ubuntu-latest
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: ⚙️ Create .env file
run: |
touch ${{ env.ENV_PATH }}
echo DOMAIN_FIR=${{ secrets.DOMAIN_FIR }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_PROD=${{ secrets.SOCKET_PORT_PROD }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_TEST=${{ secrets.SOCKET_PORT_TEST }} >> ${{ env.ENV_PATH }}
- name: 📦 Zip project files
run: zip -r ./$GITHUB_SHA.zip .
- name: 🌎 Access to AWS
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: 🚛 Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
- name: 🚀 Deploy to EC2 with CodeDeploy
run: aws deploy create-deployment
--application-name codedeploy-app
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name ${{ env.DEPLOYMENT_GROUP_NAME }}
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
name: 🚀 Deploy workflow on test environment
on:
push:
branches: ['release/v**']
jobs:
deploy:
env:
ENV_PATH: .env
S3_BUCKET_DIR_NAME: test
DEPLOYMENT_GROUP_NAME: test
environment: test
runs-on: ubuntu-latest
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 🗂️ Make config folder
run: mkdir -p config
- name: ⚙️ Create .env file
run: |
touch ${{ env.ENV_PATH }}
echo DOMAIN_FIR=${{ secrets.DOMAIN_FIR }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_PROD=${{ secrets.SOCKET_PORT_PROD }} >> ${{ env.ENV_PATH }}
echo SOCKET_PORT_TEST=${{ secrets.SOCKET_PORT_TEST }} >> ${{ env.ENV_PATH }}
- name: 📦 Zip project files
run: zip -r ./$GITHUB_SHA.zip .
- name: 🌎 Access to AWS
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
- name: 🚛 Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
- name: 🚀 Deploy to EC2 with CodeDeploy
run: aws deploy create-deployment
--application-name codedeploy-app
--deployment-config-name CodeDeployDefault.AllAtOnce
--deployment-group-name ${{ env.DEPLOYMENT_GROUP_NAME }}
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=${{ env.S3_BUCKET_DIR_NAME }}/$GITHUB_SHA.zip
마지막으로 위 스크립트 파일의 jobs 에 빌드 테스트를 추가해주어야 합니다.
빌드 테스트는 deploy job 보다 먼저 수행되어야 하며, develop 브랜치 또한 빌드 테스트를 수행하여야 합니다.
따라서 각각의 파일을 아래와 같이 수정해주었습니다.
name: 🚀 Build & Deploy workflow on production environment
on:
pull_request:
branches: [main]
types: [closed]
jobs:
build:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.15.0]
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 📀 Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: ✨ Install pnpm
uses: pnpm/action-setup@v2
id: pnpm-install
with:
version: 7.29.3
run_install: false
- name: 🚛 Get pnpm cache store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: ⚡️ Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: 📦 Install dependencies
run: pnpm install
- name: 🔨 Build Project
run: pnpm build
deploy:
needs: build
...생략...
deploy job 이전에 build job 이 추가되어 트리거 이벤트 조건이 build job 의 if 문으로 수정되었습니다.
또한 deploy job 에는 needs 옵션을 주어 build job 이 종료된 이후에 수행될 수 있도록 의존성을 추가해주었습니다.
사실 GitHub Actions 의 모든 job 은 별도의 환경에서 수행됩니다.
다시 말해, bulid job 과 deploy job 은 각각 격리된 환경에서 개별적으로 수행되며 needs 옵션을 주지 않으면 병렬적으로 수행되기 때문에 순서를 보장할 수 없습니다.
이외에 빌드 테스트를 수행할 노드 버전을 matrix 옵션에 추가해주었고, 패키지 매니저로는 pnpm 을 사용하였습니다.
GitHub Actions 작성 방법에 대해 숙지하고 있다면 이해하기 어려운 내용은 아니라고 생각해 test 환경의 스크립트 파일로 넘어가보겠습니다.
name: 🚀 Build & Deploy workflow on test environment
on:
push:
branches: [develop, 'release/v**']
pull_request:
branches: [develop]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.15.0]
steps:
...생략...
deploy:
if: contains(github.ref_name, 'release/v')
needs: build
...생략...
build job 의 steps 는 ci.cd.prod.yml 의 예시와 동일하기 때문에 생략하였습니다.
test 환경의 스크립트 파일에서 눈의 띄는 변경 사항은 develop 브랜치가 추가되었다는 점입니다.
이전의 CI/CD 설계에서는 release 와 main 브랜치에 대해서만 CI/CD 프로세스를 적용하기로 했었지만, 사실 develop 브랜치에 feature 들이 추가될 때마다 빌드 테스트가 자동으로 수행되는 것이 좋습니다.
따라서 스크립트 파일의 on 옵션에는 release 브랜치 뿐 아니라, develop 브랜치 관련 이벤트도 추가해주었습니다.
다만 develop 브랜치는 빌드 테스트만 수행하고 종료되며, 배포 작업을 수행하지는 않습니다.
즉, 새롭게 추가된 feature 들이 애플리케이션 로드와 빌드에 문제를 일으키지 않는지 확인하는 과정이기 때문에 배포 작업에서는 제외해주어야 합니다.
이를 위해, deploy job 의 if 조건에 contains 메서드를 활용해주었습니다.
github.ref_name 은 github 에서 제공하는 컨텍스트로, workflow 를 트리거한 브랜치 혹은 태그의 이름입니다.
다시 말해 workflow 를 트리거한 브랜치 이름에 release/v 가 포함되어 있는 경우에만 deploy job 을 수행하게 됩니다.
이렇게 설정하면, develop 브랜치에서 Push 혹은 PR 이벤트가 발생한 경우 build job 만을 수행하게 되고,
release 브랜치의 Push 이벤트가 발생한 경우 build job 과 deploy job 을 모두 수행할 수 있습니다.
11. AWS Slack 연동
이제 CI/CD 프로세스 적용을 위한 모든 파일이 작성되었습니다.
하지만 CodeDeploy 의 배포가 성공했는지 여부를 확인하기 위해서는 AWS Console 에 접속하여야 한다는 불편함이 남아있습니다.
따라서 CodeDeploy 의 배포 결과를 Slack 알림으로 받아볼 수 있도록 AWS Chatbot 을 연동해주는 것이 좋습니다.
11-1) AWS WorkSpace 생성
11-2) AWS Chatbot 생성
11-3) AWS Chatbot 인라인 정책 추가
이제 새로운 Chatbot 관련 정책을 생성하여 앞서 만든 Chatbot IAM 에 추가해주어야 합니다.
AWS IAM 에서는 Chatbot 역할에 대한 템플릿을 별도로 제공하고 있지 않기 때문에 인라인으로 직접 추가해주어야 합니다.
11-4) AWS CodeDeploy 알림 생성
Chatbot 과 Slack 연결, Chatbot 의 IAM 역할 설정까지 완료되었습니다.
이제 CodeDeploy 의 배포 결과가 알림을 생성하도록 설정합니다.
11-5) Slack 채널에 aws bot 초대
등록한 Slack 채널의 채팅방에 아래와 같이 커맨드를 입력하여 Chatbot 을 초대합니다.
이제 아래와 같이 Slack 알림 메시지가 전송됩니다.
CI/CD 플로우
이제 프로젝트에 CI/CD 프로세스가 성공적으로 적용되었습니다.
각 환경 별로 적용된 CI/CD 플로우를 정리해보도록 하겠습니다.
🔨 develop
먼저 develop 브랜치의 CI/CD 플로우입니다.
개발이 완료된 feature 들은 develop 브랜치로 PR 을 통해 Merge 됩니다.
이 때 발생한 PR 이벤트에 따라 GitHub Actions 의 Build job 이 수행됩니다.
만일 Build job 이 성공하지 못했을 경우, 코드는 병합될 수 없습니다.
🚧 release
다음은 release 브랜치의 CI/CD 플로우입니다.
한 배포 단위의 feature 들이 모두 merge 되었다면, develop 브랜치에서 분기되어 Push 됩니다.
이 때 발생한 Push 이벤트에 따라 GitHub Actions 의 Build job 과 Deploy job 이 수행됩니다.
이후, GitHub Actions 로부터 배포 요청을 받은 AWS CodeDeploy 는 AWS S3 에 업로드된 소스 코드를 받아 압축을 푼 뒤, 지정된 EC2 에서 appspec.yml 과 after-deploy.sh 파일의 스크립트 명령을 모두 실행하여 배포합니다.
마지막으로 CodeDeploy 는 배포 결과를 AWS Chatbot 에게 전달하며, 최종 배포 결과는 Slack 알림으로 전송됩니다.
🔥 main
마지막으로 main 브랜치의 CI/CD 플로우입니다.
기본적으로 release 브랜치의 플로우와 동일하며, release 브랜치로부터 Pull Request 이벤트에 의해 트리거된다는 점만이 차이점입니다.
이렇게 프로젝트 CI/CD 적용 및 플로우에 대한 정리를 모두 마쳤습니다.
기존의 번거롭던 배포 프로세스는 한 줄의 Git 커맨드만으로 간소화되었고, 배포 성공 여부 또한 실시간으로 Slack 에서 확인할 수 있게 되었습니다.
덕분에 배포에 대한 부담감이 줄어들고, 소요되던 시간도 훨씬 단축되어 이전보다 더욱 잦은 주기로 배포를 진행하고 있습니다.
물론 아쉬운 점도 있습니다.
앞서 짧게 언급했었지만, 이번 CI/CD 적용 과정에서 Unit Test 와 Integration Test 단계가 존재하지 않는 CI 는 CI 라고 볼 수 없다는 생각이 들었습니다.
Build Test 만으로는 새롭게 추가된 feature 들이 애플리케이션 내부에 어떤 영향을 미치는지 전혀 파악할 수 없고, 애플리케이션 로드 혹은 빌드가 문제없이 수행되었다고 해서 모든 로직이 정상 동작하는 것이 아니기 때문입니다.
CI 에서 Unit Test 와 Integration Test 를 수행하고, 테스트 코드를 작성하여야 하는 이유에 대해 확실하게 깨닫게 된 좋은 경험이었습니다.
와우! 마지막 slack 연동까지! 너무 정리가 잘되어 있어 도움을 많이 받았습니다.
감사합니다.