이 글은 공부를 하면서 알게 된 내용들을 기록하는 글 입니다. 오류나 고쳐야 할 사항들이 있다면 지적 부탁드립니다!
🎯 목표
Github Actions와 AWS CodeDeploy, AWS S3, AWS EC2를 이용해서 개발자가 특정 브랜치에 PR/Push를 했을 때,
AWS 운영 환경에 서비스가 자동으로 배포되게끔 CICD 자동화를 해보자.
🖥️ 개발 환경
스프링 버전: Spring boot 3.2.1
빌드툴: Gradle
JDK: openjdk 17
데이터베이스: mariadb, mongodb
✅ AWS EC2 생성 및 환경 설정
개발한 서비스를 AWS EC2 인스턴스에 배포해도록 하자.
AWS에 처음 가입하면 1년간 프리티어 수준으로 서비스를 이용할 수 있는데, EC2의 경우에는 `12개월 동안 매월 750시간`을 제공한다.
750을 24로 나눴을 때 31을 넘기 때문에, 프리티어를 사용하고 있을 때에는 인스턴스 하나 정도는 무료로 사용할 수 있다 :)
2. Docker 통해 mariaDB 설치 및 계정 설정
3. Docker 통해 mongoDB 설치 및 계정 설정
4. EC2에 CodeDeploy agent 설치하기
밑의 명령어를 하나씩 작성하여 EC2에 CodeDeploy agent를 설치하자
sudo apt update
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto > /tmp/logfile
설치 이후에 밑의 명령어를 통해 codedeploy-agent가 제대로 설치되었는지 확인하자.
이후에 밑의 사진처럼 active로 뜬다면 설치가 제대로 된 것이다.
sudo service codedeploy-agent status
5. EC2에 Java 설치하기
sudo apt update
sudo apt install openjdk-17-jdk
✅ AWS IAM 사용자 생성 후, Github Actions 값 입력
우리는 개발자가 특정 브랜치(ex: production)에 `PR` 혹은 `Push`를 했을 때 `Github Actions`가 적절한 일을 하여 AWS 운영 환경에 서비스를 올리기를 바랬다.
그런데 우리도 AWS 서비스를 이용하려면 로그인을 통해 권한을 얻어야하는데, `Github Actions`에서도 이러한 과정이 있어야하지 않을까?
따라서 필요한 권한들을 가진 `AWS IAM 사용자`를 생성하고,
IAM 사용자를 사용할 수 있는 값을`Github Actions`에 `secret`으로 설정하여 `Github Actions`에서 필요한 일들을 할 수 있도록 하자.
1. AWS IAM 사용자 생성하기
1) AWS IAM 서비스 접속 후, `엑세스 관리` - `사용자` - `사용자 생성` 선택
2) `사용자 이름`에 원하는 이름 설정
3) `직접 정책 연결`을 누른 후, 밑에 해당하는 정책들을 추가
AmazonS3FullAccess
AWSCodeDeployFullAccess
AWSCodeDeployRole
2. IAM 사용자의 엑세스 키 만들기
`Github Actions`에게 IAM 사용자 정보를 전달하기 위한 엑세스 키를 만들어보자.
1) IAM 사용자 목록에서 방금 생성한 IAM 사용자를 클릭
2) 요약 란의 `엑세스 키 만들기` 선택
3) `기타` 선택 이후 생성을 누르면 엑세스 키 생성
엑세스 키 생성이 완료되면, `access key`와 `secret key`가 나오는데, 이 둘을 복사해서 안전한 곳에 저장하거나
.csv 파일을 안전한 곳에 다운받아놓자.
3. Github Actions에 IAM 정보 입력하기
우리가 배포하고 싶은 코드가 있는 Repository에 들어가자.
`Github Settings` - `Secrets and variables` - `Actions` - `New repository secret`를 누른 후,
위에서 발급받은 값을 각각 입력해주자
✅ EC2에서 S3에 접근하기 위한 IAM 역할 생성
위에서 EC2 인스턴스에 CodeDeploy agent를 설치했는데, 이 agent는 S3에 접근하여 빌드 파일을 가져오는 역할을 한다.
따라서 EC2 인스턴스에 S3에 접근할 수 있는 IAM 역할을 부여해주어야 한다.
1. AWS IAM 역할 생성하기
1) AWS IAM 서비스 접속
2) `엑세스 관리` - `역할` - `역할 생성` 선택
3) 신뢰할 수 있는 엔티티 선택에 `AWS 서비스` 선택
4) 사용 사례에 `EC2` 선택
5) 권한 추가에 `AmazonS3FullAccess` 선택
6) 역할 이름에 사용하고자하는 이름 입력
2. EC2에 역할 부여하기
이전에 생성한 EC2 인스턴스 선택 - `작업` - `보안` - `IAM 역할 수정` 선택
IAM 역할 목록에서 생성한 IAM 역할을 연결해주자
IAM 역할 연결 이후에, EC2 인스턴스의 세부 정보를 확인해보면 우리가 부여한 IAM 역할이 설정되어 있음을 확인할 수 있다.
✅ S3 설정하기 - 버킷 생성
Github Actions의 워크플로우를 통해서 빌드 파일(.jar)을 생성하게 되는데, 이를 zip 형식으로 S3에 저장할 것이다.
빌드 파일을 저장할 `버킷`을 생성하자.
AWS S3 서비스 접속 - 버킷 - 버킷 만들기 선택
버킷 이름에는 원하는 이름을 작성하고 생성 버튼을 누르면 된다.
✅ AWS CodeDeploy 설정하기
AWS CodeDeploy는 배포 그룹에 해당되는 EC2 인스턴스에게 적절한 스크립트를 통해 서비스를 실행하도록 한다.
1. AWS CodeDeploy용 IAM 역할 생성하기
1) AWS IAM 서비스 접속 - 역할 생성
2) 신뢰할 수 있는 엔터티 유형에 `AWS 서비스` 선택
3) 사용 사례에 `CodeDeploy` 선택
기본적으로는 AWSCodeDeployRole이 적용되어 있으므로 그대로 생성하면 된다 :)
2. 애플리케이션 생성
이제 CodeDeploy에게 `애플리케이션`을 생성하고 `EC2` 인스턴스를 설정해주어 어떤 EC2 인스턴스에서 작업을 하게 할지 알려주자.
1) AWS CodeDeploy 접속 - 애플리케이션 생성 선택
2) 애플리케이션 이름에는 사용하고 싶은 이름 선택
3) 컴퓨팅 플랫폼에 `EC2/온프레미스` 선택
3. 배포 그룹 생성
생성한 AWS CodeDeploy 어플리케이션에 배포 그룹을 만들어 EC2 인스턴스를 설정해주자
1) 생성한 애플리케이션 - `배포 그룹 생성`
2) 배포 그룹 이름에는 사용하고 싶은 이름 입력
3) 서비스 역할에 위에서 생성한 CodeDeploy용으로 생성한 IAM 역할 선택
4) 배포 유형: 현재 위치 선택
5) 환경 구성: `Amazon EC2 인스턴스` 선택 - 값에 배포에 사용하고자하는 EC2 인스턴스 이름 선택
6)AWS CodeDeploy 에이전트 설치에 `한 번만` 선택
7) 배포 구성에 `CodeDeployDefault.AllAtOnce` 선택
8) 로드밸런서는 비활성화
✅ Github Actions workflow 파일 작성 - main.yml
이제 Github Actions의 workflow 파일을 작성하자.
`.github` - `workflow` 폴더에 `.yml` 파일이 있으면, Github Actions가 해당 파일들을 확인하고 조건에 맞는 워크플로우를 실행한다.
name: Build and Deploy to EC2
# 워크플로우가 언제 실행될 것인지 조건 명시
on:
push:
branches: [ "production"]
pull_request:
branches: [ "production" ]
# AWS 관련 값 변수로 설정
env:
AWS_REGION: ap-northeast-2
AWS_S3_BUCKET: gitget-deploy-bucket
AWS_CODE_DEPLOY_APPLICATION: GitGet-Application-CD
AWS_CODE_DEPLOY_GROUP: GitGet-Deployment-Group
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
# JDK 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
# 공개되면 안되는 정보를 담은 .yml 파일을 생성
- name: make application.yml
run: |
mkdir -p ./src/main/resources
cd ./src/main/resources
touch ./application.yml
touch ./application-common.yml
touch ./application-prod.yml
echo "${{ secrets.APPLICATION }}" > ./application.yml
echo "${{ secrets.COMMON }}" > ./application-common.yml
echo "${{ secrets.PROD }}" > ./application-prod.yml
# 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
shell: bash
- name: Build and Test
run: ./gradlew build test
# 빌드 파일을 zip 형식으로 압축 - S3에서는 jar 저장이 안되기 때문에 zip으로 생성
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
# AWS 권한
- name: AWS credential 설정
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }}
# S3 버킷에 빌드파일(zip 파일)을 업로드
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip
# EC2 인스턴스에 S3에 저장되어 있던 zip 파일을 받아와 배포 시작
- name: EC2에 배포
run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip
여기에서 눈에 여겨볼 step은 `make application.yml` 이다.
서비스 개발을 하면서 민감한 정보(DB 비밀번호, JWT 시크릿 티 등)을 .yml 파일로 등록하고 이를 `.gitignore`에 저장하고 사용한다.
배포할 때 이러한 yml 파일이 필요한데 이를 Github Actions secrets로 저장해놓고, Github action이 실행될 때 yml 파일을 생성하도록 하자.
이 링크에서 더 자세한 내용을 확인할 수 있다.
✅ appspec.yml 작성
AWS CodeDeploy가 `appspec.yml`의 내용을 읽고 이 흐름대로 작동한다.
`appspec.yml` 파일은 프로젝트의 제일 루트 경로에 존재해야 제대로 작동한다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app
overwrite: yes
permissions:
- object: /
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
✅ deploy.sh 작성
EC2 인스턴스에 설치한 CodeDeploy agent는 `appspec.yml`에서 설정해준 스크립트를 읽고 작업을 실행한다.
`appspec.yml`의 `location`에서 `scripts/deploy.sh`로 설정했으므로, 밑과 같은 위치에 스크립트가 있어야 한다.
밑의 스크립트가 하는 일은 다음과 같다.
1) build 파일이 있는 위치를 통해 build 파일을 특정 경로(DEPLOY_PATH)에 복사
2) 현재 EC2 인스턴스에서 java 어플리케이션이 돌아가고 있다면 모두 일괄 종료
3) build 파일을 백그라운드 옵션(`nohup java -jar`)으로 실행
##!/bin/bash
BUILD_JAR=$(ls /home/ubuntu/app/build/libs/*.jar)
JAR_NAME=$(basename $BUILD_JAR)
echo ">>> build 파일명: $JAR_NAME" >> /home/ubuntu/deploy.log
echo ">>> build 파일 복사" >> /home/ubuntu/deploy.log
DEPLOY_PATH=/home/ubuntu/app/
cp $BUILD_JAR $DEPLOY_PATH
echo ">>> 현재 실행중인 애플리케이션 pid 확인 후 일괄 종료" >> /home/ubuntu/deploy.log
sudo ps -ef | grep java | awk '{print $2}' | xargs kill -15
DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME
echo ">>> DEPLOY_JAR 배포" >> /home/ubuntu/deploy.log
echo ">>> $DEPLOY_JAR의 $JAR_NAME를 실행합니다" >> /home/ubuntu/deploy.log
nohup java -jar $DEPLOY_JAR >> /home/ubuntu/deploy.log 2> /home/ubuntu/deploy_err.log &
✅ Github Actions 실행
Github Actions 워크플로우 파일인 main.yml에서 우리는 production 브랜치에 PR 혹은 Push를 했을 때 배포 프로세스가 실행되도록 했다.
production 브랜치에 PR을 날렸을 때, 밑과 같이 Github Actions에서 워크플로우가 실행이 되고, 그 결과 배포가 정상적으로 됨을 확인할 수 있다 :)
✅ CI/CD pipeline 구성
CI/CD pipeline을 구성하는 과정에서 제일 혼란스러웠던 부분은,
`AWS CodeDeploy 서비스`와 `EC2 instance 내에 설치되어 있는 CodeDeploy agent`의 역할이었다.
`AWS CodeDeploy`는 전체 배포 프로세스를 관리하고 조율하는 역할을 하며, yml 파일에 작성되어 있는 스크립트를 실행한다.
1️⃣ 어떤 EC2 인스턴스들을 대상으로 배포 프로세스를 진행할 것인지 관리하는 `배포 그룹 관리`
2️⃣ Rolling, Blue/Green과 같은 `배포 정책 설정`
3️⃣ 배포 수명주기 이벤트(BeforeInstall, AfterInstall 등) 관리
와 같은 일을 실행한다.
`CodeDeploy Agent`는 EC2 인스턴스에 설치되어 실행되는 데몬 프로세스로,
AWS CodeDeploy 서비스와 지속적으로 통신하면서 새로운 배포 작업이 있는지 확인합니다.
만일 배포 작업이 존재한다면 S3에서 빌드 파일을 다운로드 받는 등, yml 파일의 지시사항에 따라 단계별로 배포를 수행합니다.
🧨 Trouble shooting
🔥 AWS Codedeploy 배포 실패: `UnknownError - Missing credentials`
AWS EC2 인스턴스에 IAM 역할(S3)도 부여하고 Codedeploy-agent도 설치를 했는데, 막상 Codedeploy 단계에 들어가면 첫 단계인 ApplicationStop에서부터 오류가 발생했다 :(
0. `/var/log/aws/codedeploy-agent`에서 오류 메세지 확인하기
`cd`를 통해 /var/log/aws/codedeploy-agent 위치로 이동 후, `ls` 명령어를 통해 내부에 있는 파일 리스트를 확인해보면`codedeploy-agent.log`를 확인할 수 있다.
ubuntu@ip-:~$ cd /var/log/aws/codedeploy-agent
ubuntu@ip-:/var/log/aws/codedeploy-agent$ ls
codedeploy-agent.20241024.log codedeploy-agent.log codedeploy-agent.log.age
ubuntu@ip-:/var/log/aws/codedeploy-agent$ vi codedeploy-agent.log
나의 경우에는 해당 로그 파일을 확인했을 때 `Missing credentials`를 확인할 수 있었다.
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials - please check if this instance was started with an IAM instance prof
1. AWS EC2 인스턴스에 CodeDeploy-agent가 설치 & 실행 중인지 확인해볼 것
EC2 인스턴스에 접속하여 밑의 명령어를 실행했을 때 `active (running)`으로 떠야 CodeDeploy-agent가 실행 중인 것이다.
만일 active가 아니라면 EC2 인스턴스에서 CodeDeploy 서비스의 요청을 받지 못하기 때문에 오류가 발생할 것이다.
systemctl status codedeploy-agent
2. AWS EC2 인스턴스의 CodeDeploy-agent 재시작
EC2 인스턴스에 IAM 역할을 제대로 넣었고, CodeDeploy-agent도 정상적으로 실행 중인데도 같은 오류가 발생하는 경우가 있다.
이 경우 대체로 EC2 인스턴스에 IAM role을 부여하기 전에, CodeDeploy-agent가 설치 & 실행되어 IAM 역할을 인식 못하는 것이라고 한다.
이 경우에는 CodeDeploy-agent 를 재시작하면 해결이 된다.
sudo service codedeploy-agent restart
✅ 참고 링크
https://diary-developer.tistory.com/32
https://be-developer.tistory.com/56