이전 포스트 Docker를 활용한 Spring Boot 프로젝트 EC2 배포와 Github Actions과 Docker을 활용한 CI/CD 구축를 통해 Github Actions과 Docker를 사용하여 CI/CD 환경을 구축하는 방법에 대해 알아보았다.
하지만 도커를 사용해 배포를 하기 위해서는, 스프링 프로젝트 빌드 파일을 도커 이미지로 만들어 Docker Hub의 Public Repository에 올려야 한다. (무료로 사용하기 위해서)
그렇게 되면, 모든 사람이 우리의 빌드 파일에 접근할 수 있고 만약 누군가 이를 역빌드한다면 AWS key 등 민감한 정보가 노출될 수 있다는 위험이 있다.
그래서 이번 포스트에서는 Docker 대신 CodeDeploy와 S3를 사용한 CI/CD 환경을 구축해보고자 한다.
참고로 AWS CodeDeploy를 사용하여 EC2에 코드를 배포하는 것은 비용이 부과되지 않는다 :)
- 코드를 작성한 뒤, Github에 push를 한다.
- develop 브랜치에 push가 발생하면, Github Actions이 실행된다.
- Github Actions는 빌드를 하여 코드에 문제가 없는지 확인한다.
- Github Actions는 프로젝트 파일을 압축하여 AWS S3로 전송하고, CodeDeploy에게 배포를 요청한다.
- CodeDeploy는 S3로부터 zip 파일을 받아 배포를 진행한다.
이번 포스트에서는 EC2 인스턴스 생성이나 S3 버킷 생성에 대해 다루지 않을 것이다.
CodeDeploy로 배포 자동화 환경을 만들기 위해서는 2가지 IAM 역할이 필요하다.
- CodeDeploy를 위한 역할
- EC2 인스턴스에서 CodeDeploy Agent를 활용하여 S3에 있는 배포 파일을 가져오기 위한 S3 접근 가능 역할
역할 만들기 버튼을 눌러 역할을 생성하자.
AWS 서비스
CodeDeploy
를역할 이름과 설명을 적어준다.
AWSCodeDeployRole
권한을 부여한 뒤 역할 생성을 마무리한다.AWS 서비스
EC2
를역할 이름과 설명을 적고, AWSCodeDeployAccess
, AmazonS3FullAccess
권한을 추가해주어 역할을 만든다.
그리고 EC2 서비스로 들어가서 위 사진과 같은 방법으로 EC2 인스턴스에 IAM 역할을 설정해주자. 이 과정을 하지 않아 한참 헤맸었다 🥲
(뒤 과정에서 IAM 역할을 설정하지 않았을 때 발생한 에러 스크린샷이다.)
이전에 S3 버킷을 만들며 IAM 사용자를 만든 적이 있다.
IAM 사용자에게 AWSCodeDeployFullAccess
와 AWSCodeDeployRole
권한을 추가해준다.
이제 배포 과정을 관리할 CodeDeploy 애플리케이션을 만들자.
애플리케이션 생성 버튼 클릭
애플리케이션 이름을 지어주고, 컴퓨팅 플랫폼은 EC2/온프레미스
를 선택한다.
다음으로는 CodeDeploy 애플리케이션에 연결할 배포 그룹을 만들어야 한다.
배포 이름을 지어주고, 서비스 역할에 앞서 만든 CodeDeploy용 역할을 추가해준다.
EC2 인스턴스의 태그를 연동한다.
만약 EC2 인스턴스에 태그가 없다면, 태그 추가 버튼을 눌러 태그를 만들어준다.
혹시 몰라서 로드밸런싱만 비활성화해두고, 나머지는 기본 설정으로 하여 배포 그룹을 만들었다.
EC2 인스턴스로 들어가 CodeDeploy agent를 설치하자. 아래 명령어를 따라하면 된다.
나는 실행권한 추가 및 설치에서 Current running Ruby version for root is 3.0.2, but Ruby version 2.x needs to be installed
라는 에러를 만났고, 이 글을 참고하여 해결하였다.
# 1. ruby 설치
sudo apt-get install ruby
# 2. wget 설치 (agent 설치 파일을 가져오기 위해 사용)
sudo apt-get install wget
cd /home/ubuntu
# 3. 설치파일 다운로드
wget https://aws-codedeploy-ap-northeast2.s3.ap-northeast-2.amazonaws.com/latest/install
# 4. 실행권한 추가 및 설치
chomd +x ./install
sudo ./install auto
설치가 되었다면, 아래 명령어를 통해 실행이 잘 되는지 확인한다.
sudo service codedeploy-agent status
만약 codedeploy-agent가 실행되지 않았다면 start를 하여 실행시켜준다.
sudo service codedeploy-agent start
CI/CD 환경을 만들기 위한 AWS 서비스 세팅이 마무리되었으니, 배포 스크립트 파일을 작성하자.
프로젝트 최상단에 appspec.yml
이라는 파일을 만들고, 아래와 같이 내용을 채워준다.
appspec.yml은 CodeDeploy의 설정 파일로, 배포 시점에 특정한 쉘을 실행시킬 수 있다.
지금은 단순 배포만 할 예정이기 때문에 설치가 된 이후에 deploy.sh라는 스크립트를 실행시키도록 설정한다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app # 인스턴스에서 파일이 저장될 위치
hooks:
AfterInstall:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
참고로 나는 최상단에 scripts라는 폴더를 만들고 그 안에 deploy.sh 파일을 만들어주었다.
만약 appspec.yml처럼 최상단에 deploy.sh 파일을 만들면 location에서 scripts/
부분을 지워야 한다.
REPOSITORY=/home/ubuntu/app
cd $REPOSITORY
APP_NAME=demo
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
if [ -z $CURRENT_PID ] #2
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $CURRENT_PID"
sudo kill -15 $CURRENT_PID
sleep 5
fi
echo "> $JAR_PATH 배포" #3
nohup java -jar \
-Dspring.profiles.active=dev \
build/libs/$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
인스턴스에서 프로젝트를 배포하는 쉘 스크립트이다.
쉘 스크립트에 대한 자세한 내용은 EC2 서버에 프로젝트를 배포해 보자를 참고하자ㅏ.
저번에 했던거처럼 ./github/workflows 안에서 스크립트 파일을 작성해준다.
# github repository actions 페이지에 나타날 이름
name: CI/CD using Github Actions & AWS CodeDeploy
# event trigger
# main이나 develop 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "main", "develop" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# JDK setting - github actions에서 사용할 JDK 설정
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# gradle caching - 빌드 시간 향상
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# 환경별 yml 파일 생성(1) - application.yml
- name: make application.yml
if: |
contains(github.ref, 'main') ||
contains(github.ref, 'develop')
run: |
mkdir ./src/main/resources # resources 폴더 생성
cd ./src/main/resources # resources 폴더로 이동
touch ./application.yml # application.yml 생성
echo "${{ secrets.YML }}" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
shell: bash
# 환경별 yml 파일 생성(2) - dev
- name: make application-dev.yml
if: contains(github.ref, 'develop')
run: |
cd ./src/main/resources
touch ./application-dev.yml
echo "${{ secrets.YML_DEV }}" > ./application-dev.yml
shell: bash
# 환경별 yml 파일 생성(3) - prod
- name: make application-prod.yml
if: contains(github.ref, 'main')
run: |
cd ./src/main/resources
touch ./application-prod.yml
echo "${{ secrets.YML_PROD }}" > ./application-prod.yml
shell: bash
# gradle build
- name: Build with Gradle
run: ./gradlew build -x test
# make zip file
- name: Make zip file
run: zip -qq -r ./$GITHUB_SHA.zip .
shell: bash
# AWS 사용자 정보 입력
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.ACCESS_KEY_SECRET }}
aws-region: ap-northeast-2
# S3에 zip 파일 업로드
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/deploy/$GITHUB_SHA.zip --source .
# CodeDeploy에 배포 요청
- name: Code Deploy
run: aws deploy create-deployment --application-name ${{ secrets.CODE_DEPLOY_APP_NAME }}
--deployment-config-name CodeDeployDefault.OneAtATime
--deployment-group-name ${{ secrets.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }}
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=deploy/$GITHUB_SHA.zip
저번 포스트에서 Github Actions script 파일에 대해 자세하게 다뤘으니, 자세한 설명은 생략하겠다.
저번 포스트와 달라진 내용은 아래와 같다.
# make zip file
- name: Make zip file
run: zip -qq -r ./$GITHUB_SHA.zip .
shell: bash
# AWS 사용자 정보 입력
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.ACCESS_KEY_SECRET }}
aws-region: ap-northeast-2
# S3에 zip 파일 업로드
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/deploy/$GITHUB_SHA.zip --source .
# CodeDeploy에 배포 요청
- name: Code Deploy
run: aws deploy create-deployment --application-name ${{ secrets.CODE_DEPLOY_APP_NAME }}
--deployment-config-name CodeDeployDefault.OneAtATime
--deployment-group-name ${{ secrets.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }}
--s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=deploy/$GITHUB_SHA.zip
$GITHUB_SHA
는 AWS에서 자체적으로 제공해주는 변수로, 압축 파일을 만들 때 이름의 중복을 회피하기 위해 사용하였다.aws-access-key-id
와 aws-secret-access-key
에는 IAM 사용자 정보를 입력해준다./deploy
폴더를 만들고 업로드할 예정이므로, 버킷 이름 뒤에 /deploy
를 추가해주었다.CodeDeploy에 배포를 요청하는 부분에서 ${{ secrets.CODE_DEPLOY_APP_NAME }}
에 내가 원하는 이름을 적는 것인 줄 알고, 마음대로 이름을 적어서 발생한 오류이다.
앞서 만든 CodeDeploy 애플리케이션의 이름을 적어주어야 한다.
나도 Github Actions에서는 ✅이 뜨지만, 실제로는 배포가 되지 않는 어려움을 겪었다.
만약 Github Actions에 체크 표시가 뜬다면, S3에 압축 파일을 업로드하고 CodeDeploy에 요청하는 것까지는 잘 된 것이다.
그러므로 이후에 발생한 오류들은 CodeDeploy 대시보드에서 확인할 수 있다.
대시보드에서 배포 ID를 클릭하고, 배포 수명 주기 이벤트에서 View events를 클릭하여 어디가 잘못됐는지 알 수 있다. + 이 글을 참고하여 codedeploy agent의 로그를 함께 확인하면 에러 잡기가 조금 더 참고할 것이다.
나는 대부분 배포 스크립트를 적을 때 경로명을 잘못 작성하여 배포가 안되었다.
그렇게 자잘한 오류들을 수정하면 배포가 잘 된 것을 확인할 수 있다 ^~^
https://geonoo.tistory.com/162
https://shinsunyoung.tistory.com/120
https://countrymouse.tistory.com/entry/awsec2cicd2
https://supern0va.tistory.com/27
https://github.com/aws/aws-codedeploy-agent/issues/301#issuecomment-1129912011
https://velog.io/@linho1150/SpringBoot-CodeDeploy-Ec2로-CICD하기#312-ouput되는-jar파일의-이름을-고정하기-위해-buildgradle파일-수정-아래-코드-추가
https://sarc.io/index.php/aws/1327-tip-codedeploy-missing-credentials
https://stackoverflow.com/questions/41997426/instanceagentpluginscodedeployplugincommandpoller-missing-credentials
https://twofootdog.tistory.com/38