어쩌다보니 자동배포?
프로젝트를 진행하면서 개인서버에서 클라우드서버로 이전하게 된 사건이 있었습니다.
그 때 이전하면서 서버에 파일을 전송하는 프로그램 없이 그냥 깃허브에 푸시한 순간 배포가 되게 할 수는 없을까? 하는 생각이 들었고, 그렇게 자동배포의 여정이 시작 되었습니다.
CI 와 CD 란?
CI와 CD는 소프트웨어 개발과 배포에서 중요한 역할을 하는 모범 사례 및 원칙들입니다.
CI (Continuous Integration, 지속적 통합)
•
CI는 개발자가 코드 변경 사항을 공유 저장소에 빈번하게 병합(통합)할 것을 권장하는 개발 방법론입니다.
•
CI 과정에는 보통 자동화된 빌드와 테스트가 포함됩니다. 이렇게 하면 코드 변경 사항이 현재의 코드베이스나 다른 코드 변경 사항과 문제 없이 잘 작동하는지 확인할 수 있습니다.
•
CI의 주요 목표는 통합 문제를 신속하게 발견하고 해결하여 소프트웨어 품질을 높이는 것입니다.
CD (Continuous Delivery & Continuous Deployment, 지속적 전달 및 지속적 배포)
•
CD는 소프트웨어를 자동화된 방식으로 빠르게, 안정적으로 고객에게 제공할 수 있도록 지원하는 원칙과 관행의 집합입니다.
•
Continuous Delivery: 소프트웨어 변경 사항이 자동화된 테스트와 빌드를 거쳐 프로덕션 환경으로 배포될 준비가 되었음을 보증합니다. 그러나 실제 배포는 수동으로 수행될 수 있습니다.
•
Continuous Deployment: Continuous Delivery의 확장판으로, 모든 변경 사항이 자동 테스트를 거친 후 자동으로 프로덕션 환경에 배포됩니다.
•
CD의 주요 목표는 빠른 피드백 루프를 제공하고, 소프트웨어의 신속한 반복을 가능하게 하며, 사용자에게 지속적으로 가치를 제공하는 것입니다.
CI/CD 파이프라인을 사용하면 팀은 코드의 변경 사항을 자주, 빠르게, 안정적으로 프로덕션 환경에 배포할 수 있게 됩니다. 이로 인해 소프트웨어 제품의 품질이 향상되며, 버그 수정이나 새로운 기능의 배포가 더욱 빠르게 이루어질 수 있습니다. CI/CD 도구로는 Jenkins, Travis CI, GitLab CI/CD, CircleCI, GitHub Actions 등이 있습니다.
깃허브 액션 vs 젠킨스
가장 많이 들어보고, 그만큼 많이 쓰이는 두 개의 CI/CD 도구에 대해 알아봅시다.
Jenkins
•
서버에 젠킨스를 설치해야 합니다.
•
작업이 동기화 되어야 하기 때문에 배포하는 데 더 많은 시간이 소요 됩니다.
•
계정 및 트리거를 기반으로 하며 깃허브 이벤트를 준수하지 않는 빌드를 중심으로 합니다.
•
환경 호환성을 위해 도커 이미지에서 실행해야 합니다.
•
캐싱 매커니즘을 지원하기 위해 플러그인을 사용할 수 있습니다.
•
공유할 수 없습니다.
•
문서가 다양합니다.
•
규모가 큰 프로젝트에 적합 합니다.
GitHub Actions
•
서버에 설치할 필요가 없습니다.
•
비동기로 CI / CD 를 달성할 수 있습니다.
•
모든 깃허브 이벤트에 대한 작업을 제공하고, 다양한 언어와 프레임 워크를 지원합니다.
•
모든 환경과 호환 됩니다.
•
캐싱이 필요한 경우 자체 캐싱 매커니즘을 작성해야 합니다.
•
깃허브 마켓 플레이스를 통해 공유가 가능합니다.
•
젠킨스에 비해 문서가 없습니다.
•
프로젝트 규모가 크지 않거나 외부 클라우드 서비스를 사용하는 경우 적합 합니다.
어떤 것을 선택했나요?
저희 프로젝트의 경우 규모가 작고, 외부 클라우드(AWS) 에 서비스가 배포되기 때문에 깃허브 액션을 선택 하였습니다.
아키텍쳐로 알아보는 자동배포
세세하게 들어가기 전, 아키텍쳐로 큰 그림을 이해해 봅시다.
배포과정 살펴보기
1.
열심히 코드 작업을 한 뒤, 깃허브로 푸시를 합니다.
2.
깃허브 액션은 깃허브에 코드가 올라오면 이를 알아채 배포과정을 밟게 됩니다.
3.
깃허브 액션은 워크 플로우에 작성된 순서대로 작업을 수행합니다. 저의 경우,
•
jdk 설치
•
프로젝트 빌드
•
프로젝트 압축
•
S3 에 전달
•
코드 디플로이가 S3 에 배포된 압축된 프로젝트를 EC2 서버에 배포
이렇게 작성하였습니다.
4.
EC2 에 배포한 후에 미리 작성한 appspec.yml 대로 작업이 수행됩니다. 저의 경우,
•
실행되고 있는 부트 프로젝트가 있는지 점검
•
실행되고 있다면 종료 후, 새로 배포한 프로젝트 실행
이렇게 작성하였습니다.
application.yml 을 submodule 로 관리하기
application.yml 이 프로젝트 레포지토리에 없다면 자동배포가 제대로 이뤄지지 않습니다.
•
깃허브 액션을 통해 빌드를 수행할 경우, 해당 레포지토리에 있는 파일들을 가지고 빌드가 이루어 집니다.
•
때문에 서브모듈을 이용하여 application.yml 을 관리해주는 것이 좋습니다.
•
깃허브액션 워크 플로우 작성해보기
deploy.yml
•
최상위 디렉토리에 .github/workflows 를 생성해 주어야 합니다.
•
서브모듈 토큰의 경우 계정의 세팅 → developer … 에서 생성 해주세요.
•
생성한 토큰과 시크릿 키들은 배포할 코드가 들어있는 레포지토리의 설정에 등록해주세요.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle / dev
on:
push:
branches: [ 감지할 브랜치명을 넣어주세요. ]
pull_request:
branches: [ 감지할 브랜치명을 넣어주세요. ]
permissions:
contents: read
env:
S3_BUCKET_NAME: { s3 버킷 이름을 넣어주세요. }
CODE_DEPLOY_APP_NAME: { 코드디플로이 어플리케이션 이름을 넣어주세요. }
CODE_DEPLOY_GROUP_NAME: { 코드디플로이 그룹 이름을 넣어주세요.}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.SUBMODULE_TOKEN }}
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: build
gradle-version: 8.1.1
# 프로젝트 압축
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip ./
shell: bash
# AWS 권한 확인
- 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: ap-northeast-2
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/{ 디렉토리가 존재한다면 이곳에 작성해주세요. }/$GITHUB_SHA.zip
# Deploy
- name: Deploy
run: |
aws deploy create-deployment \
--application-name { 코드디플로이 어플리케이션 이름을 넣어주세요 } \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name { 코드디플로이 그룹 이름을 넣어주세요 } \
--file-exists-behavior OVERWRITE \
--s3-location bucket={ s3 버킷 이름을 넣어주세요 },bundleType=zip,key={ 디렉토리가 존재한다면 경로를 넣어주세요. }/$GITHUB_SHA.zip \
--region ap-northeast-2 \
YAML
복사
appspec.yml
이름 주의해서 작성해주세요!
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/{ 배포 경로를 입력해주세요. }
pattern: "*.jar"
permissions:
- object: /
pattern: "*.jar"
owner: ec2-user
group: ec2-user
mode: '755'
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
runas: ec2-user
ApplicationStart:
- location: scripts/start.sh
timeout: 60
runas: ec2-user
YAML
복사
•
AfterInstall : 디플로이가 서버에 배포를 완료하면 실행할 스크립트 입니다.
•
ApplicationStart: 애플리케이션을 실행할때 사용할 스크립트 입니다.
Script 작성하기
최상단 경로에 scripts 폴더를 만들고, 그 안에 스크립트 파일을 넣어 주세요.
stop.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ec2-user/{ 배포 경로 }"
JAR_FILE="$PROJECT_ROOT/build/libs/{ 빌드 파일 이름 }"
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 -15 $CURRENT_PID
fi
Bash
복사
start.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ec2-user/{ 배포 경로 }"
JAR_FILE="$PROJECT_ROOT/build/libs/{ 빌드 파일 이름 }"
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/build/libs/*.jar $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
Bash
복사
Code Deploy
EC2 가 두개라면 코드 디플로이는 어떻게 구분할까?
저희 프로젝트처럼 ec2 가 두개인 경우를 생각해봅시다. 코드 디플로이는 S3 에서 압축된 파일을 가져와 적절한 서버에 압축을 풀어 배포합니다. 이때 서버를 어떻게 구분하는 걸까요?
배포그룹을 사용하여 올바른 서버 찾아가기
주의해야할 점은?
이 배포그룹과 적절한 EC2 를 연결해줘야 합니다.
아래와 같이 배포그룹 생성시 EC2 에 설정한 태그를 제대로 작성해주세요.
주의
organization 의 접근권한 문제
•
깃허브 액션이 해당 레포지토리에 접근할 수 없는 경우 action 이 실행되지 않습니다.
•
아래와 같이 수정해주세요.