이전에 프로젝트 배포를 할 때는 jar 파일만을 올리는 git repository를 이용하여 push 한 후, EC2 서버에 접속한 뒤 jar 파일을 pull 받아 실행시키는 방식으로 배포를 했습니다.
이 방식을 여러 번 진행한 결과 수동으로 매번 빌드를 하고 파일을 옮겨야 하는 방식이 매우 비효율적으로 느껴졌고, 이 과정을 자동화 할 방법을 찾아보게 되었습니다.
불편함을 해소하고자 빌드/배포 자동화 방식들을 알아보게 되었고, 여러 방식들을 찾게 되었습니다.
그 중 간단하게 적용할 수 있는 방법인 Github Actions를 사용하여 다양한 배포 방식을 적용하기로 했습니다.
Github Action은 워크플로우를 통해 빌드와 배포의 자동화가 가능하며, 서버의 상태를 확인하고 배포하는 과정을 순차적으로 실행시킬 수 있습니다.
먼저 이번 포스팅에서는 git Action을 이용하여 빌드 후 S3 bucket에 업로드 후 CodeDeploy를 이용하여 EC2 인스턴스에 배포하는 과정을 구현합니다.
제가 구현하려는 로직은 다음과 같습니다.
- 사용자가 github 레포지토리에서 특정 브랜치에 코드 변경사항을 Push 한다.
- push 혹은 PR event가 발생하면 Git Actions가 실행되어 지정한 리눅스 버전에서 build 후 jar 파일을 생성 후 S3 버켓으로 copy 해준다.
- CodeDeploy에게 S3에 있는 jar 파일을 가지고 배포를 진행해달라고 전달한다.
- CodeDeploy는 배포할 EC2 인스턴스 내부에 있는 CodeDeploy Agent에게 배포 명령을 내리고, Agent는 jar 파일을 S3에서 받아 주어진 스크립트에 따라 배포를 진행한다.
- 새로운 SpringBoot WAS를 띄우고 Nginx 스위칭을 통해 무중단 배포를 할 수 있도록 Agent에게 배포 스크립트 제공한다.
GitActions와 S3를 이용한 빌드 자동화하기
GitAction WorkFlow 설정
GitAction을 실행할 워크플로우는 레포지터리 내에서 .github/workflows 폴더 아래에 위치한 gradle.yml 파일로 설정한다.
name: Java CI with Gradle
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
S3_BUCKET_NAME: "S3 버켓 이름"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean build
# 디렉토리 생성
- name: Make Directory
run: mkdir -p deploy
# Jar 파일 복사
- name: Copy Jar
run: cp ./build/libs/"파일명".jar ./deploy
# 파일 압축
- name: Make zip file
run: zip -r -qq -j ./"파일명".zip ./deploy
shell: bash
# AWS 인증
- name: 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 }}
# S3에 빌드된 파일 업로드
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./"파일명".zip s3://$S3_BUCKET_NAME/
name : Workflow의 이름을 지정
env : 전역 변수로 사용할 변수 지정
on
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow를 동작하게 하는 trigger이다.
repository에서 해당 브랜치에 Event가 발생할 때마다 실행이 된다.
push, pull_request, schedule 을 사용할 수 있다.
단일 Event를 사용할 수도 있고, array로 작성할 수도 있다.
on: push
on: [pull_request, issues]
jobs
jobs:
build:
runs-on: ubuntu-latest
steps:
# uses키에 사용하고자 하는 action의 위치를 {소유자}/{저장소명}@{참조자} 형태로 명시한다.
# Github에서 제공하는 체크아웃 액션의 소유자는 actions이고, 저장소 이름은 checkout이며 최신 버전인 v3을 사용한다.
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
여러개의 job을 사용할 수 있으며, 여러개의 job을 사용할 때는 서로 정보 교환도 가능하다. 그리고 각각 독립적으로 실행할 수도 있고, Default로 병렬 실행이 된다.
Job안에는 여러개의 step이 존재할 수 있고, 순서대로 실행이된다.
run-on : 어떤 OS에서 실행될 지 지정한다.
uses : 이미 만들어진 액션을 사용할 때 지정한다.
with라는 키워드로 Action에 값을 전달할 수 있다.
.gitignore에 등록한 파일 Secrets에 업로드 후 빌드 시 적용하기
프로젝트에서 .gitignore로 등록한 파일들을 함께 등록하여 빌드할 수 있도록 Github Secrets에 등록하고, workflow를 실행시킬 gradle.yml에 다음 구문을 추가시켜 빌드시에 해당 파일을 위치시킬 수 있도록 한다.
- name: Copy yml
env:
OCCUPY_SECRET: ${{ secrets.OCCUPY_SECRET }}
OCCUPY_SECRET_DIR: src/main/resources
OCCUPY_SECRET_DIR_FILE_NAME: application.yml
run: echo $OCCUPY_SECRET | base64 --decode > $OCCUPY_SECRET_DIR/$OCCUPY_SECRET_DIR_FILE_NAME
OCCUPY_SECRET : github secrets에 저장될 OCCUPY_SECRET에 있는 암호화 된 secret 파일 본문을 가져온다.
OCCUPY_SECRET_DIR : main code에 해당 secret 파일이 저장될 위치
OCCUPY_SECRET_DIC_FILE_NAME : 생성될 파일 이름(기존과 동일해야한다.)
프로젝트 빌드시에 필요한 파일을 Github Secrets에 등록해야 하는데 위의 Secrets에서 가져온 파일을 base64로 디코딩 후 적용하므로 해당 파일을 base64로 인코딩하여 등록해야 한다.
https://www.convertstring.com/ko/EncodeDecode/Base64Encode
위의 사이트에서 application.yml 파일을 복사해서 인코딩 후 Settings/Secrets/Actions 에 OCCUPY_SECRET으로 올리면 된다.
git actions에서 빌드 중간에 환경 세팅을 하는 것이기 때문에 프로젝트 yml에서도 해당 구문을 추가해준다.
spring:
config:
import: application.yml
Github Action이 S3 버킷에 접근할 수 있도록 권한 생성
S3 버킷을 생성하는 과정은 다양한 블로그에서 다루기 때문에 생략하려고 합니다.
IAM은 AWS 서비스에 대한 액세스 권한을 최소한으로 부여하고 관리할 수 있게 해주는 서비스이다.
외부 서비스에서 AWS 서비스에 접근하려고 할 때도 물론이고, AWS 서비스 간에 접근하고자 할 때도 권한 부여를 해줄 수 있다.
먼저 GithubAction이 프로젝트 빌드 후 S3 버킷에 접근하여 빌드한 파일을 업로드할 수 있도록 S3와 CodeDeploy에 대한 접근 권한을 가지고 있는 사용자 역할을 부여한다.
사용자에게 정책 권한 부여시 권한 설정 > 기존 정책 직접 연결 에서 아래와 같이 역할을 설정한다.
- AmazonS3FullAccess
- AWSCodeDeployFullAccess
태그는 선택사항으로 나중에 AWS에 실행중인 서비스를 검색하거나 필터링할 때 사용된다.
이후 나오는 대시보드에서 액세스 키 설정을 한다.
이렇게 해서 나오는 accesskey와 secretkey를 github secrets에 저장한다.
CI 테스트하기
Github Actions에서 master 브랜치에 코드 변경이 있을 시 빌드 후 S3에 업로드 되는 과정을 확인해본다.
위에서 yml파일에 설정한 master 브랜치로 PR을 보내게 되면 다음과 같이 GitAction이 실행되는 것을 볼 수 있다.
이후 S3 버킷에서 파일을 확인해보면 생성된 zip파일을 확인할 수 있다.
CodeDeploy를 이용한 CD 구현하기
EC2 인스턴스 생성은 이미 많은 자료가 있기 때문에 생략한다.
CodeDeploy란
CodeDeploy는 AWS의 EC2, 온프레미스 인스턴스 ECS 등의 서비스로 애플리케이션 배포를 자동화하는 배포 서비스이다.
다양한 애플리케이션을 지속적으로 배포할 수 있는데, 코드나 패키지, 스크립트등의 컨텐츠가 포함된다.
한 번 구축해 놓으면 이후로는 배포가 매우 간편하고 AWS 콘솔을 통해 제어하면서 배포 과정을 확인할 수 있기 때문에 배포 플로우 구축에 용이히다.
CodeDeploy를 사용했을때의 장점은 다음과 같다.
- 배포 자동화 : CodeDeploy는 개발, 테스트 및 프로덕션 환경에 걸쳐 애플리케이션 배포를 완전 자동화시킬 수 있다. 또한 인프라에 맞춰 규모를 조정할 수 있어 인스턴스 하나또는 수천개에 동시에 배포할 수 있다.
- 무중단 배포 : CodeDeploy는 애플리케이션 가용성을 극대화하기 위해 두개자 배포 유형을 적용한다.블루/그린 배포 시 새로운 인스턴스에서 환경 테스트를 완료한 후 해당 인스턴스로 트래픽이 다시 라우팅 된다.
- In-place 배포에서 CodeDeploy는 EC2 인스턴스 전체에 대해 롤링 업데이트를 수행한다.
- 중지 및 롤백 : 오류가 있는 경우 자동 또는 수동으로 배포를 중지하고 롤백할 수 있다.
- 중앙 집중식 제어: CodeDeploy 콘솔 또는 AWS CLI를 통해 배포 상태를 추적할 수 있다.
CodeDeploy를 이용한 배포 과정
- 개발한 애플리케이션의 최상단 경로에 AppSpec.yml이라는 파일을 추가
- AppSpec.yml은 배포에 필요한 모든 절차를 적어둔 명세서이다.
- CodeDeploy에 프로젝트의 특정 버전을 배포에 달라고 요청하면, CodeDeploy는 배포를 진행할 EC2 인스턴스에 설치되어있는 CodeDeploy Agent들과 통신하며 Agent들에게 요청받은 버전을 배포해달라고 요청한다.
- 요청받은 Agent들은 코드 저장소에서 프로젝트 전체를 서버에 내려받고, AppSpec.yml 파일을 읽어 해당 파일에 적힌 절차대로 배포를 진행한다.
- Agent는 배포를 진행한 후 CodeDeploy에게 성공/실패 등의 결과를 알려준다.
EC2에 CodeDeploy Agent 설치
Amazon Linux 2023 에서 CodeDeploy를 설치하기 위한 방법은 여기에서 확인할 수 있다.
# 패키지 매니저 업데이트, 루비 설치
sudo yum update
sudo yum install ruby
sudo yum install wget
# CodeDeploy 에이전트 설치 관리자를 다운로드한다. 서울 리전의 버킷으로 명령어를 설정했다.
cd /home/ec2-user
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
# install 파일에 대한 실행 권한을 설정한다.
chmod +x ./install
# 최신 버전의 CodeDeploy 에이전트를 설치.
sudo ./install auto
# 사용자 인증 필요
aws configure
# CodeDeploy 에이전트 1.5.0은 Amazon Linux 2023에서 지원하지 않기 때문에 1.6.0 버전을 설치
sudo ./install auto -v releases/codedeploy-agent-codedeploy-agent-1.6.0-49.noarch.rpm.noarch.rpm
# 서비스가 실행중인지 확인하려면 다음 명령을 실행한다.
# CodeDeploy 에이전트가 설치되어 실행 중이면 "The AWS CodeDeploy agent is running"와 같은 메시지가 표시되어야 합니다.
sudo service codedeploy-agent status
# "error: No AWS CodeDeploy agent running"와 같은 메시지가 표시되면 서비스를 시작하고 다음 두 명령을 한 번에 하나씩 실행합니다.
sudo service codedeploy-agent start
sudo service codedeploy-agent status
CodeDeploy Agent가 설치된 것을 확인할 수 있다.
자주 사용되는 명령어
// codedeploy 로그확인, CodeDeploy에서 오류가 날 시 이곳을 확인하면 된다
cat /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log
// 실행중인 jar파일 pid 가져오기
pgrep -f jar
// 실행중인 파일 실행시간 조회
ps -eo pid,etime,cmd | grep 프로세스명 | grep -v grep
EC2에 IAM 역할 부여하기
배포를 위한 IAM User 생성(deploy)
후에 사용될 gradle.yml에서 AWS에 접근해 배포에 해당되는 기능들을 사용할 수 있도록 IAM 계정을 생성해준다
EC2에 S3, CodeDeploy 권한 정책을 부여
IAM > 역할 > 역할만들기
EC2를 선택한 후 S3와 CodeDeploy에 대한 권한 정책을 검색하여 추가한다.
- AmazonS3FullAccess
- AWSCodeDeployFullAccess
역할의 이름을 설정하고 역할 만들기를 선택한다.
역할 생성후에는 EC2 대시보드로 돌아와
EC2 우클릭 - 인스턴스 설정 - IAM 역할 연결/바꾸기 선택
방금 생성한 IAM 역할을 부여한다.
이후 Agent에 IAM Role을 적용하기 위해 EC2 재시작이 필요하다.
다시 EC2에 접속하여 Agent를 재시작한다.
sudo service codedeploy-agent restart
CodeDeploy IAM 역할 생성하기
위에서와 같이 CodeDeploy를 위한 IAM 역할을 생성한다.
역할의 권한은 CodeDeploy를 제어할 수 있도록 부여한다.
- AWSCodeDeployRole
CodeDeploy 애플리케이션 생성하기
CodeDeploy - 애플리케이션 - 애플리케이션 생성
애플리케이션을 생성한 후 만들어진 애플리케이션에서 배포 그룹 생성을 클릭한다.
배포 그룹 생성 화면에서 이름과, 방금 만든 IAM 역할을 연결한다.
최소단위의 무중단 배포를 CodeDeploy가 아닌 Nginx를 통해 구현할 것이기 때문에, 지금은 일단 현재 위치 배포를 선택하고 넘어간다.
현재 위치 배포와 블루/그린 배포 모두 무중단 배포의 형태이다.
우리는 여러 서버를 띄워 교체하는 방식이 아니라, 하나의 EC2 내부에서 두 개의 포트에 WAS를 띄우고 스위칭 하는 방식을 쓸 것이기 때문에 CodeDeploy가 무중단 배포의 역할을 가지고 있지 않는다.
환경 구성은 EC2를 선택하고, 태그에는 EC2 인스턴스에 설정했던 태그를 걸어준다.
해당 태그를 기준으로 CodeDeploy가 배포를 진행한다.
배포 설정은 여러 대의 서버를 어떤 단계에 따라 순차적으로 배포할 것인지를 선택하는 설정이다.
EC2 하나를 사용하기 때문에 한번에 배포를 끝내는 CodeDeployDefault.AllAtOnce를 선택한다.
로드밸런서 연결은 활성화를 해제하고 배포 그룹을 생성했다.
Github에 스크립트 추가하기
CodeDeploy의 동작을 제어할 스크립트를 생성한다.
먼저 프로젝트의 최상단에 appspec.yml 을 생성한다.
appspec.yml은 CodeDeploy Agent가 참조하면서 배포를 진행하는 명세서이다. 저장소에서 내려받은 프로젝트 파일들을 서버 내 어떤 디렉터리로 옮길지, 어떤 권한의 계정으로 명령어를 실행할지 등 배포에 필요한 다양한 설정들을 제공한다. 또한 설치 전/후, 배포 성공 검증 등 다양한 이벤트에 대한 후크를 제공한다.
# appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/app/
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
hooks:
ApplicationStart:
- location: deploy.sh
timeout: 60
runas: root
- os
어떤 OS를 위한 배포 파일인지 명시한다. - files
codeDeploy Agent는 배포 명령을 받으면 코드 저장소에 있는 프로젝트 전체를 서버의 임시 경로로 내려받는다. 내려 받은 프로젝트를 서버 내 어느 경로로 이동시킬지 명시할 수 있다. - hook
배포 시 발생하는 다양한 생명주기 마다 원하는 스크립트를 실행할 수 있게 후크를 제공한다.- timeout 옵션은 지정한 시간내에 스크립트가 완료되지 않으면 배포에 실패한 것으로 간주된다.
- BeforeInstall : 코드저장소에서 프로젝트를 내려받은 뒤 인스턴스 내 배포를 원하는 경로에 파일을 옮기기 전에 실행된다.
- AfterInstall : 파일을 모두 이동한 후 실행된다.
- AppllicationStart : 애플리케이션을 시작할 때 사용된다.
- ValidateService : 서비스를 재시작한 후 서비스가 올바르게 실행됐는지 확인할 때 사용한다.
위의 appspec.yml에서는 애플리케이션을 시작할 때 deploy.sh 스크립트를 실행시킨다.
프로젝트의 scripts/deploy.sh 파일을 다음과 같이 추가한다.
# deploy.sh
#!/usr/bin/env bash
REPOSITORY=/home/ec2-user/app
cd $REPOSITORY
CURRENT_PID=$(pgrep -fla java | grep [파일명] | awk '{print $1}')
echo "현재 구동중인 애플리케이션 pid : $CURRENT_PID"
if [ -z $CURRENT_PID ]
then
echo "> Nothing to end."
else
echo "> kill -9 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls $REPOSITORY/ | grep '*.jar' | tail -n 1)
echo "Jar Name : $JAR_NAME"
echo "$JAR_NAME 에 실행권한 추가"
chmod +x $JAR_NAME
JAR_PATH=$REPOSITORY/$JAR_NAME
echo "Jar Path : $JAR_PATH"
echo "> $JAR_PATH deploy"
nohup java -jar \
-Duser.timezone=Asia/Seoul \
-Dspring.profiles.active=prod \
$JAR_NAME >> $REPOSITORY/logs/log_$(date +\%Y\%m\%d).log 2>&1 &
# gradle.yml
name: CI/CD
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
S3_BUCKET_NAME: "S3 버켓 이름"
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# ...
# Jar 파일 복사
- name: Copy Jar
run: cp ./build/libs/"파일명".jar ./deploy
# appspec.yml 파일 복사
- name: Copy appspec.yml
run: cp appspec.yml ./deploy
# script files 복사
- name: Copy script
run: cp ./scripts/*.sh ./deploy
# 파일 압축
- name: Make zip file
run: zip -r -qq -j ./"파일명".zip ./deploy
shell: bash
# ...
# Deploy
- name: Code Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: aws deploy create-deployment --application-name "codeDeploy 이름" --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name "codeDeploy 그룹명" --file-exists-behavior OVERWRITE --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key="파일명".zip --region ap-northeast-2
- application-name : CodeDeploy 애플리케이션의 이름을 지정한다.
- deployment-config-name : 배포 그룹 설정에서 선택했던 배포 방식을 지정
- deployment-group-name : 배포 그룹의 이름
- s3-location : jar를 S3에서 가지고 오기 위해 차례로 bucket 이름, 파일 타입, 파일 경로 입력
이렇게 CodeDeploy 배포 설정까지 끝났다.
위에서 설정한 master 브랜치에 PR을 보내게 되면 GitActions에서 빌드 후 CodeDeploy로 배포가 되는 것을 확인할 수 있다.
이렇게 CodeDeploy에서 배포되는 과정까지 확인했다.
'DevOps > Git' 카테고리의 다른 글
[Git] Git 파일 관리 : add, status, rm (0) | 2025.04.16 |
---|---|
[Git] Git 저장소 생성 및 연결 : init, clone, remote (0) | 2025.04.15 |
[Git] Git 설치 및 초기 설정 : config (0) | 2025.04.14 |
[Git] Git이란 무엇인가요? (0) | 2025.04.13 |
Github Action이란 (0) | 2023.11.26 |