EC2 배포 자동화2
1. 전체 구성도

이전에 EC2, Docker를 활용해서 간단한 CI/CD를 구현한 적이 있었다. 이번에는 AWS EC2, GitHub Actions, AWS S3, AWS CodeDeploy를 활용해서 간단한 CI/CD 자동화를 구현해볼 예정이다. 전체 구성도를 참고한 배포 과정은 다음과 같다.
GitHub 저장소에 코드 변경 사항을 푸시한다.
GitHub Actions에서 변경 사항을 감지하고 workflow를 실행한다.
main 브랜치의 Pull Request일 경우: CI를 실행한다.
빌드를 실행한다.
테스트를 실행한다.
main 브랜치의 merge인 경우
빌드를 실행하고 zip 파일을 만든다.
AWS S3에 zip 파일을 업로드한다.
AWS CodeDeploy에 배포를 요청한다.
EC2 인스턴스에 설치된 AWS CodeDeploy에서 배포 액션을 실행한다.
AWS S3에 저장된 zip 파일을 EC2에 다운로드한다.
프로젝트를 실행한다.
1.1. 스펙 변경 이유
1.1.1. GitHub Actions Runner -> AWS CodeDeploy
이전 게시글에도 말했었지만, GitHub Actions Runner에는 보안 이슈가 있었다. 공개 저장소가 아니면 GitHub Actions를 무료로 사용할 수 없고, 개인 저장소가 아니면 GitHub Actions Runner에 보안 이슈가 발생한다. 결국 양자택일을 해야 했는데 현재 진행중인 프로젝트가 공개 저장소였고 바꿀 계획이 없었다. 따라서 GitHub Actions Runner 대신 다른 걸 사용해서 EC2에 배포를 하기로 했다.
GitHub Actions에서 SSH로 EC2 접근 권한을 얻어 배포
AWS CodeDeploy 사용
먼저 1번은 또 다른 보안 이슈가 발생한다는 문제가 있었다. 깃헙 액션은 ip가 고정되어 있지 않아서 SSH 권한을 주고 배포할 경우 모든 ip주소에 대해 개방해야 했다. GitHub Actions Runner를 보안 이슈 때문에 쓰지 않기로 했는데 다시 보안 문제가 발생한다. 기존의 CI/CD 세팅을 바꾸지 않을 수 있다는 장점은 있었지만, 보안 문제로 포기했다. 2번의 경우 또 다시 세팅 공부를 해야하고 결국 CodeDeploy에 접근할 액세스 키를 저장소에 보관해야 한다는 단점이 있었다. 그러나 포크만 따면 위험한 코드를 생성하는 PR를 보낼 수 있는 GitHub Actions Runner, 22번 포트를 강제로 개방해야하는 SSH 접근보다 보안 위험성이 낮다고 생각했다. 또한 Secret 키는 등록할 때만 키를 확인 가능하고 편집할 때는 확인할 수 없는 등의 보안 장치가 있었다. 따라서 AWS CodeDeploy를 사용하기로 했다.
1.1.2. Docker Hub -> AWS S3
최초에 도커를 사용한 건 DB도 도커 컨테이너로 올릴 생각이 있었기 때문이다. 그러나 EC2 인스턴스에서 프로젝트와 DB를 같이 관리하는 건 프리티어 스펙이나 운영 관점에서 좋지 않다는 생각이 들었다. 그래서 AWS RDS를 쓰기로 결정했다. AWS S3가 같은 AWS 서비스인 AWS CodeDeploy와 궁합이 좋았다. 또한 도커 컨테이너로 프로젝트만 올리는 건 의미가 없다고 생각했다. 따라서 Docker Hub 대신 AWS S3를 사용하기로 했다.
1.1.3. Workflow 분리
기존의 workflow는 빌드 -> 배포가 모두 한 번에 일어났다. 이런 경우 빌드가 실패했는데 배포가 될 수도 있고 배포 시점을 자유롭게 조정하기가 힘들다. 따라서 기존의 workflow에서 빌드와 배포를 나누기로 했다.
2. 준비물
GitHub Actions에서 AWS EC2로 S3, CodeDeploy를 활용해서 배포하기 위해서는 사전 준비가 필요하다.
AWS EC2 세팅
AWS S3 세팅
AWS IAM 세팅
AWS CodeDeploy 세팅
번외
(필요한 경우) AWS RDS 세팅
2.1. 체크사항
각각의 세팅이 전부 다 완료되었다고 가정하자. 위 게시글에 언급하지 않은 체크사항이 몇 가지 있다.
IAM 역할
두 개의 역할과 한 개의 사용자, 그리고 그 사용자에 대한 액세스 키가 생성되어야 한다.
역할1 - EC2 인스턴스 역할
AmazonS3FullAccess
AWSCodeDeployFullAccess
역할2 - CodeDeploy 역할
AWSCodeDeployRole
사용자
액세스 키 보유
정책이 제대로 선택되었는지 확인하자. CodeDeploy를 확인했을 때 접근 거부로 실패했다면 권한이 제대로 부여되지 않았을 수 있다. 내 경우 정책을 잘못 집어넣어서 배포 실패가 발생했다.
EC2 인스턴스에 IAM 역할 부여
IAM 역할을 만들었다고 자동으로 EC2 인스턴스에 역할이 부여되지는 않는다.

먼저 EC2 인스턴스 대시보드에서 현재 실행 중인 인스턴스를 확인한다.

여기서 인스턴스 ID 우클릭 ➜ 보안 ➜ IAM 역할 수정을 클릭하면 IAM 역할을 설정할 수 있다.

기존에 IAM에서 생성해둔 EC2 인스턴스 역할을 설정해주자.
3. EC2에 CodeDeploy 설치
# ==============================================================================
# 1. SSH로 EC2 인스턴스에 로그인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
ssh -i '프라이빗_키_경로/프라이빗_키.pem' '사용자_이름'@'퍼블릭_IPv4_주소'
# ==============================================================================
# 2. dnf 버전 확인 및 업데이트
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo dnf update
# ==============================================================================
# 3. ruby, wget 설치
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo dnf install ruby
# 터미널에 아래 명령어 입력 후 엔터
sudo dnf install wget
# ==============================================================================
# 4. wget으로 codedeploy 다운로드
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
cd /home/ec2-user
# 터미널에 아래 명령어 입력 후 엔터
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
# ==============================================================================
# 5. 파일에 실행 권한 설정
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
chmod +x ./install
# ==============================================================================
# 6. 최신 버전 CodeDeploy 에이전트 설치
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo ./install auto
# ==============================================================================
# 7. 서비스 실행 확인
# ==============================================================================
# 터미널에 아래 명령어 입력 후 엔터
sudo service codedeploy-agent status
# error 발생 시
# 터미널에 아래 명령어 입력 후 엔터
sudo service codedeploy-agent start
# 터미널에 아래 명령어 입력 후 엔터
sudo service codedeploy-agent status
# 제대로 실행되고 있다면 아래와 같은 문구 출력
# 중간 생략
The AWS CodeDeploy agent is running as PID 11111
나는 AWS 공식 문서(link)를 참고해서 Amazon Linux 2023에 code deploy를 설치했다.
4. 프로젝트에 CodeDeploy 설정
4.1. appspec.yml 생성
appspec.yml는 CodeDeploy가 배포를 관리하는데 사용하는 애플리케이션 사양 파일이다. 프로젝트 폴더 아래에 appspec.yml을 생성하고 작성한다.
version: 0.0
os: linux
files:
- source: /
destination: 'PROJECT_DESTINATION'
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: 'OWNER'
group: 'GROUP'
hooks:
AfterInstall:
- location: 'DEPLOY_SCRIPTS_LOCATION'
timeout: 60
runas: 'USER'
S3에서 프로젝트가 저장될 폴더 'PROJECT_DESTINATION'과 zip 파일 설치 후 실행할 'DEPLOY_SCRIPTS_LOCATION'은 본인의 폴더 경로와 일치하게 설정해주자. 그 외에도 'OWNER', 'GROUP', 'USER'는 본인의 EC2 운영체제에 맞게 바꿔주자.
4.2. deploy.sh 생성
deploy.sh는 CodeDeploy의 배포 동작을 지정하는 스크립트 파일이다. 프로젝트 폴더 아래에 deploy.sh를 생성하고 작성한다.
DEFAULT='DEFAULT_PATH'
cd $DEFAULT
APP_NAME='APP_NAME'
REPOSITORY=$DEFAULT/$APP_NAME
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep 'SNAPSHOT.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
if [ -z $CURRENT_PID ]
then
echo "> 현재 실행 중인 애플리케이션이 없습니다."
else
echo "> kill -15 $CURRENT_PID"
sudo kill -15 $CURRENT_PID
sleep 5
fi
nohup java -jar $JAR_PATH > /dev/null 2> /dev/null < /dev/null &
스크립트는 자신의 상황에 알맞게 변경하면 된다. 간혹 경로가 맞지 않아서 실행이 안 되는 경우가 있을 수 있으니 꼼꼼히 확인하자.
5. GitHub Actions 설정
5.1. 키 값 SECRETS 등록
앞서 IAM에서 생성한 사용자의 액세스 키를 깃헙 프로젝트의 Secrets and variables에 등록해야 한다. 액세스 키 외에도 본인이 숨기고 싶은 정보를 등록해서 관리할 수 있다.
5.2. workflow 작성
name: Continuous Integration
on:
pull_request:
branches: [ "main" ]
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build --no-daemon
- name: Run tests
run: ./gradlew test --no-daemon
CI workflow는 PR이 발생했을 때 실행된다.
name: Continuous Deployment
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build --no-daemon
- name: Make zip file
run: zip -qq -r ./$GITHUB_SHA.zip .
shell: bash
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://${{ secrets.AWS_S3_BUCKET_NAME }}/$GITHUB_SHA.zip
- name: Code Deploy
run: aws deploy create-deployment --application-name ${{ secrets.AWS_DEPLOY_NAME }}
--deployment-config-name CodeDeployDefault.OneAtATime
--deployment-group-name ${{ secrets.AWS_DEPLOY_GROUP_NAME }}
--s3-location bucket=${{ secrets.AWS_S3_BUCKET_NAME }},bundleType=zip,key=$GITHUB_SHA.zip
cd는 main 브랜치에 커밋되었을 때 실행된다. main 브랜치에 대한 커밋을 막아놓으면 PR이 merge될 때만 배포가 실행될 것이다.
제대로 설정했다면 PR 시 CI 실행, Main 브랜치에 Merge 시 CD가 실행된다.
참고 자료
Last updated