프로젝트 진행하면서 스프링부트 서버 구축을 AWS EC2 를 활용하여 진행했다. 매번 코드 업데이트할 때마다 AWS EC2 접속해서 빌드하고 실행하는 게 너무 귀찮았다. Github에 코드 올리면 자동으로 배포까지 됐으면 좋겠다고 생각하다가 Github Actions를 활용하여 CI/CD 구축을 해보기로 결심했다.
Github 공개 레파지토리를 이용하다보니 Secret key, DB접속 정보 등 .gitignore 파일에 적어 Github 에는 올리지 않는 Secret 파일들이 존재
변수 몇 개만 필요한 게 아니라 여러 파일들이 필요한 거였기 때문에 Github Secrets에 base64 인코딩한 파일 내용을 저장한 후 워크플로에서 디코딩하는 방법 이용
방법1) base64 인코딩/디코딩 사이트 에서 텍스트 붙여넣어서 base64 인코딩
방법2) ssl을 위한 keystore.p12 는 사이트 이용 없이 openssl 사용하여 base64 인코딩
openssl base64 -in [키스토어 파일명] -out [저장될텍스트파일명]
# 빌드 파일 권한 수정
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# gradle 빌드
- name: Build with Gradle
run: ./gradlew clean build
# application.yml 파일 복사
- name: Copy application.yml
env:
APPLICATION_YML_FILE: ${{ secrets.APPLICATION_YML }}
run: echo $APPLICATION_YML_FILE | base64 --decode > src/main/resources/application.yml
# recipeapp-key.json 파일 복사
- name: Copy recipeapp-key.json
env:
RECIPEAPP_KEY_FILE: ${{ secrets.RECIPEAPP_KEY }}
run: echo $RECIPEAPP_KEY_FILE | base64 --decode > src/main/resources/recipeapp-key.json
# Secret.java 파일 복사
- name: Copy Secret.java
env:
SECRET_JAVA_FILE: ${{ secrets.SECRET_JAVA }}
run: echo $SECRET_JAVA_FILE | base64 --decode > src/main/java/com/recipe/app/config/secret/Secret.java
# keystore.p12 파일 복사
- name: Copy keystore.p12
env:
KEYSTORE_FILE: ${{ secrets.KEYSTORE }}
run: echo $KEYSTORE_FILE | base64 --decode > keystore.p12
src/main/java/com/recipe/app/config
폴더 내에 /secret
디렉토리가 없어서 해당 폴더에 파일 복사 실패
/secret
폴더 생성 하는 스텝 추가하여 해결
# secret 파일 디렉토리 생성
- name: Create secret directory
run: mkdir src/main/java/com/recipe/app/config/secret
Reason is that base64 produces output with line breaks, which are garbage chars for the decoder.
Solution is to either produce the base64 without linebreaks (-w 0) or make the decoder ignore garbage chars (-i).
CodeDeploy 에서 어떤 인스턴스 수행할지 구분하기 위해 태그 사용
EC2 인스턴스 > 작업 > 인스턴스 설정 > 태그 관리 > 새로운 태그 추가
IAM 액세스 관리에서 역할 관리 페이지로 이동하여 새로운 역할 만들기
IAM > 액세스 관리 > 역할 > 역할 만들기
EC2 인스턴스에서 IAM 연결
EC2 인스턴스 > 작업 > 보안 > IAM 역할 수정 > IAM 역할 업데이트
EC2 인스턴스 접속해서 CodeDeploy Agent 설치
$ sudo yum update
$ sudo yum install ruby
$ sudo yum install wget
$ cd /home/ec2-user
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
$ sudo service codedeploy-agent status
The AWS CodeDeploy agent is running
이라고 떠야함IAM > 액세스 관리 > 역할 > 역할 만들기
AWS 서비스와 CodeDeploy 선택 후 역할 이름 지정하고 생성
Github Actions 에서 CodeDeploy 와 S3 접근 가능하도록 처리 필요
IAM > 액세스 관리 > 사용자 > 사용자 추가
(+) 2023.07.22 기준 변경
(+) 2023.07.22 기준 변경
생성 후 생성된 콘솔 암호 키 값 저장 (나중에는 확인 불가)
IAM > 사용자 > 사용자 선택 > 보안 자격 증명 > 액세스 키 만들기
CodeDeploy에 고유한 애플리케이션 사양 파일로 YAML 형식 또는 JSON 형식 파일
프로젝트의 어떤 파일들을 EC2의 어떤 경로에 복사할지 설정 가능하고 배포 프로세스 이후에 수행할 스크립트를 지정하여 자동으로 서버 띄울 수 있음
기본적으로 루트 디렉터리에 위치해야 함
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/app
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
hooks:
AfterInstall:
- location: stop.sh
timeout: 60
runas: root
ApplicationStart:
- location: start.sh
timeout: 60
runas: root
참고한 appspec.yml 에서는 ubuntu 서버 기준이라 destination: /home/ubuntu/app
이어서 해당 폴더 찾을 수 없다는 오류 발생
EC2가 ubuntu 서버가 아니라 Amazon Linux 이므로 destination: /home/ec2-user/app
로 수정해서 해결
쉘스크립트 실행 시에 sudo 권한이 필요했음
runas: ec2-user
로 지정했던 부분을 root 권한으로 실행될 수 있도록 runas: root
로 변경해서 해결
PROJECT_ROOT 를 현재 사용중인 EC2에 맞게 PROJECT_ROOT="/home/ec2-user/app"
로 수정
JAR_FILE의 jar파일명을 맞게 수정
#!/usr/bin/env bash
PROJECT_ROOT="/home/ec2-user/app"
JAR_FILE="$PROJECT_ROOT/app-0.0.1-SNAPSHOT.jar"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)
# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
kill -9 $CURRENT_PID
fi
애플리케이션을 실행하는 스크립트
SSL 처리 때문에 필요한 keystore.p12 가 루트 위치에 존재하지 않아서 오류 발생
EC2 서버에 복사된 keystore.p12 파일을 루트 위치로 이동시키기 위해서 cp $PROJECT_ROOT/keystore.p12 /keystore.p12
내용 추가
#!/usr/bin/env bash
PROJECT_ROOT="/home/ec2-user/app"
JAR_FILE="$PROJECT_ROOT/app-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/deploy/*.jar $JAR_FILE
cp $PROJECT_ROOT/keystore.p12 /keystore.p12
# 실행권한 추가
chmod +x $JAR_FILE
# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG
# 전송할 파일을 담을 디렉토리 생성
- name: Make Directory for deliver
run: mkdir deploy
# Jar 파일 Copy
- name: Copy Jar
run: cp ./build/libs/*.jar ./deploy/
# appspec.yml Copy
- name: Copy appspec
run: cp appspec.yml ./deploy/
# 쉘스크립트 deploy 폴더로 복사
- name: Copy shell
run: |
cp ./scripts/start.sh ./deploy/start.sh
cp ./scripts/stop.sh ./deploy/stop.sh
# keystore.p12 deploy 폴더로 복사
- name: Copy keystore
run: |
cp ./keystore.p12 ./deploy/keystore.p12
# 압축파일 형태로 전달
- name: Make zip file
run: zip -r -qq -j ./recipe-storage-build.zip ./deploy
# 압축한 파일 S3 Bucket으로 업로드
- name: Deliver to AWS S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws s3 cp \
--region ap-northeast-2 \
--acl private \
./recipe-storage-build.zip s3://recipe-storage-github-actions-s3-bucket/
# EC2 서버에 deploy
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws deploy create-deployment \
--application-name recipe-storage-codedeploy-app \
--deployment-group-name recipe-storage-codedeploy-deployment-app \
--file-exists-behavior OVERWRITE \
--s3-location bucket=recipe-storage-github-actions-s3-bucket,bundleType=zip,key=recipe-storage-build.zip \
--region ap-northeast-2
jar 파일 실행 시에 keystore.p12 파일도 필요해서 deploy 폴더로 복사되도록 내용 추가
압축파일명, S3 bucket 이름, codedeploy 애플리케이션명, codedeploy 배포 그룹명 맞춰서 수정