쉽게 설명하면 Github에서 제공하는 GitAction을 통해서 프로젝트의 레포지토리에 CI/CD를 적용할 수 있다.
배포 플랫폼, 환경에 따라 설정법은 다양하다.
AWS EC2를 이용한 방법의 흐름은 다음과 같다.
CD 배포 자동화
이 글의 작성 목적이자 Github Actions을 활용하는 가장 대표적인 예시 중 하나이다.
로컬 레포지토리에서 원격 레포지토리로 푸쉬하고 난 후, Github Actions에서는 이벤트 발생에 따라 자동으로 빌드 및 배포하는 스크립트를 실행시켜준다.
애플리케이션의 규모가 클수록 빌드, 배포 시간이 오래걸리는데 이를 자동화시켜놓으면 해당 시간을 낭비하지 않을 수 있다.
Testing
팀 프로젝트를 진행하다가 Pull Request를 보내면 자동으로 테스트를 진행하는 것또한 Github Actions으로 구현할 수 있다.
따라서 테스트 성공 여부에 따라서 자동으로 PR을 Open 및 Close 할 수 있다.
Cron Job
Github Actions를 통해 특정 시간대에 스크립트를 반복 실행하도록 구현할 수 있다.
매일 특정 시간이 되면 크롤링 작업을 진행한다는 등의 예시가 존재한다.
각 구성 요소는 Workflow, Event, Job, Step, Action, Runner가 있다.
Workflow
Workflow란 레포지토리에 추가할 수 있는 일련의 자동화된 커맨드 집합다.
하나 이상의 Job으로 구성되어 있으며, Push나 PR과 같은 이벤트에 의해 실행될 수도 있고 특정 시간대에 실행될 수도 있다.
빌드, 테스트, 배포 등 각각의 역할에 맞는 Workflow를 추가할 수 있고, .github/workflows
디렉토리에 YAML 형태로 저장한다.
Event
Event란 Workflow를 실행시키는 Push, Pull Request, Commit 등의 특정 행동을 의미한다.
그리고 위의 특정 행동이 아닌, Repository Dispatch Webhook을 사용하면 Github 외부에서 발생한 이벤트에 의해서도 Workflow를 실행시킬 수 있다.
Event 종류에 대해 더 알고 싶다면 Github Actions 공식 문서
Job
Job이란 동일한 Runner에서 실행되는 여러 Step의 집합을 의미한다.
기본적으로 하나의 Workflow 내의 여러 Job은 독립적으로 실행되지만, 필요에 따라 의존 관계를 설정하여 순서를 지정해줄 수 있다.
예시로 테스트를 수행하는 Job과 빌드 작업을 수행하는 Job이 하나의 Workflow에 존재한다고 생각해봅시다.
여기서 테스트 Job은 반드시 빌드 Job 이후에 수행되어야 하는데, 여기서 의존 관계를 설정해 빌드 Job이 성공적으로 끝나야 테스트 Job을 수행할 수 있도록 지정할 수 있습니다.
따라서 만약 빌드가 실패할 시에는 테스트 Job도 실행되지 않겠죠.
Step
Step이란 커맨드를 실행할 수 있는 각각의 Task를 의미하는데, Shell 커맨드가 될 수도 있고, 하나의 Action이 될 수도 있다.
하나의 Job 내에서 각각의 Step은 다양한 Task로 인해 생성된 데이터를 공유할 수 있다.
Action
Action이란 Job을 만들기 위해 Step을 결합한 독립적인 커맨드로, 재사용이 가능한 Workflow의 가장 작은 단위의 블럭이다.
직접 만든 Action을 사용하거나 Github Community에 의해 생성된 Action을 불러와 사용할 수 있다.
Runner
Runner란 Github Actions Workflow 내에 있는 Job을 실행시키기 위한 애플리케이션이다.
Runner Application은 Github에서 호스팅하는 가상 환경 또는 직접 호스팅하는 가상 환경에서 실행 가능하며, Github에서 호스팅하는 가상 인스턴스의 경우에는 메모리 및 용량 제한이 존재한다.
Workflow는 .github/workflows
디렉토리 내에 .yml
파일을 생성해도 되지만, Repository의 Actions 탭에서 자동으로 템플릿을 만들어주는 기능을 이용하는 것이 좋다.
set up a workflow yourself 로 직접 생성해도 되고 템플릿을 만들어주는 아래 기능들을 선택해도 된다.
name: Build and Deploy Spring Boot to AWS EC2
# main 브랜치에 푸쉬 했을때
on:
push:
branches: [ dev ]
# 해당 코드에서 사용될 변수 설정
env:
PROJECT_NAME: chatchallenge
BUCKET_NAME: chatchallengebucket
CODE_DEPLOY_APP_NAME: codeDeploy-test
DEPLOYMENT_GROUP_NAME: codeDeploy-group
jobs:
build-with-gradle:
runs-on: ubuntu-20.04
steps:
- name: main 브랜치로 이동
uses: actions/checkout@v3
- name: JDK 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: gradlew에 실행 권한 부여
run: chmod +x ./gradlew
- name: jasypt key주입
run: echo ${{secrets.JASYPT}} | base64 --decode >> ./src/main/resources/application-common.yml
- name: 프로젝트 빌드
run: ./gradlew clean build -x test
# 프로젝트 압축
- name: Make zip file
run: zip -r ./chatchallenge.zip .
shell: bash
# AWS 권한 확인
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
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 ./chatchallenge.zip s3://chatchallengebucket/chatchallenge.zip
# Send application to deployment group
- name: Code Deploy
run: aws deploy create-deployment --application-name $CODE_DEPLOY_APP_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $DEPLOYMENT_GROUP_NAME --s3-location bucket=$BUCKET_NAME,bundleType=zip,key=chatchallenge.zip
간단하게 설명하면 on: push: branches: [dev] → dev 브랜치에서 push가 일어나면
jobs 를 step별로 실행하게 된다.
env 로 변수 설정도 가능하고
해당 레포지토리의 secret도 주입할 수 있다.
만약 application.yml 을 jasypt 처리 했다면
${{secrets.JASYPT}} | base64 --decode >> ./src/main/resources/application-common.yml
jasypt 키를 환경변수로 입력해줘야하고 base64로 인코딩 된 키 값을 입력해줘야한다.
인코딩 된 텍스트를 secret에 추가해주면 된다.
이때 application의 마지막 줄을 충분히 띄워주지 않으면
위 사진과 같이 입력되면서 build되어 오류가 발생할 수 있음을 주의하자!
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/spring-github-action
overwrite: yes
permissions:
- object: /
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
ApplicationStart:
- location: scripts/start.sh
timeout: 60
appspec.yml
파일은 CodeDeploy에서 배포를 관리하는 데 사용하는 YAML 형식 또는 JSON 형식의 파일이다.appspec.yml
파일은 파일에 정의된 일련의 수명 주기 이벤트 후크로 각 배포를 관리하는 데 사용된다.EC2/온프레미스
기준으로 files
, permissions
, hooks
영역으로 나뉘는데 이를 섹션
이라 부른다.
배포 주기에 따른 실행 순서
위 그림에서의 Start
, DownloadBundle
, Install
, End
는 스크립트 실행되는 구간에서 제외 되기에 회색으로 처리
이벤트 목록
DownloadBundle
– 이 배포 수명 주기 이벤트 중 에이전트는 애플리케이션 수정 파일을 다음 임시 위치로 복사한다.BeforeInstall
- 파일 암호화 해제 및 현재 버전의 백업 만들기와 같은 사전 설치 작업에 이 배포 수명 주기 이벤트를 사용할 수 있다.Install
- 이 배포 수명 주기 이벤트 중에 CodeDeploy 에이전트는 수정 파일을 임시 위치에서 최종 대상 폴더로 복사한다.AfterInstall
- 애플리케이션 구성 또는 파일 권한 변경과 같은 작업에 이 배포 수명 주기 이벤트를 사용할 수 있다.ApplicationStart
- 해당 이벤트 실행 중에 중지된 서비스를 다시 시작하려면 일반적으로 ApplicationStop
이벤트를 사용한다.ValidateService
– 마지막 배포 수명 주기 이벤트입니다. 배포가 성공적으로 완료되었는지 확인하는 데 사용된다.이 외에 생략된 로드 밸런서
관련한 이벤트 후크 등은 공식 문서
files:
- source: /
destination: /home/ubuntu/spring-github-action
overwrite: yes
source
: 인스턴스에 복사할 수정된 파일 또는 디렉토리를 설정상대경로
destination
: 인스턴스에서 파일이 복사되어야 하는 위치를 식별한다.overwrite
: 선택 값으로, 동일한 파일이 있을 경우 덮어쓰기 여부를 작성한다.permissions:
- object: /
owner: ubuntu
group: ubuntu
permissions
섹션은 files
섹션에서 정의한 파일이 인스턴스에 복사된 후 해당 파일에 권한이 어떻게 적용되어야 하는 지를 지정한다.
permissions
는 EC2/온프레미스에만 사용되므로 AWS Lamda/ECS 배포는 resources 섹션 을 참조할 수 있다.
#!/bin/bash
ROOT_PATH="/home/ubuntu/spring-github-action"
JAR="$ROOT_PATH/application-plain.jar"
APP_LOG="$ROOT_PATH/application.log"
ERROR_LOG="$ROOT_PATH/error.log"
START_LOG="$ROOT_PATH/start.log"
PROFILES_ACTIVE="Dspring.profiles.active=dev"
NOW=$(date +%c)
echo "[$NOW] $JAR 복사" >> $START_LOG
cp $ROOT_PATH/build/libs/chat-0.0.1-SNAPSHOT.jar $JAR
echo "[$NOW] > $JAR 실행" >> $START_LOG
nohup java -jar -$PROFILES_ACTIVE $JAR > $APP_LOG 2> $ERROR_LOG &
SERVICE_PID=$(pgrep -f $JAR)
echo "[$NOW] > 서비스 PID: $SERVICE_PID" >> $START_LOG
#!/bin/bash
ROOT_PATH="/home/ubuntu/spring-github-action"
JAR="$ROOT_PATH/application-plain.jar"
STOP_LOG="$ROOT_PATH/stop.log"
SERVICE_PID=$(pgrep -f $JAR) # 실행중인 Spring 서버의 PID
NOW=$(date +%c)
if [ -z "$SERVICE_PID" ]; then
echo " [$NOW] 서비스 NouFound" >> $STOP_LOG
else
echo " [$NOW] [$SERVICE_PID] 서비스 종료 " >> $STOP_LOG
kill "$SERVICE_PID"
# kill -9 $SERVICE_PID # 강제 종료를 하고 싶다면 이 명령어 사용
fi
파일 위치
EC2, CodeDeploy, S3, IAM 생성과 설정은 레퍼런스가 많으니 참고하면서 만드시면 됩니다.
reference
Spring Boot - Github Action, S3, EC2, CodeDeploy 연동
GitHub Actions를 이용한 CI/CD 구축하기
[AWS] EC2-CodeDeploy Appspec.yml에 대하여