이전 프로젝트에서 코드가 수정될 때마다 반복되는 배포의 과정(똑같은 행위)이 비효율적이라고 느꼈습니다.
특히 날씨 공공 API의 경우 TimeZone 설정으로 인해서 배포한 후에만 버그를 잡을 수 있어서 빈번한 배포가 계속 발생했었고 그 과정에서 많은 비효율성을 느꼈던 것 같습니다.
그리고 상대방과 협업을 하면서 좋은 품질의 코드가 유지되어져야 했고 내 코드가 추가됨으로써 상대방의 코드에 영향을 끼치는지 여부를 확인하는 과정이 필요했습니다. 모든 테스트 코드가 성공되었을 때 main branch로 push할 수 있다는 명확한 규칙이 필요하다는 것을 느꼈습니다.
그래서 CI/CD를 다음 프로젝트 때는 기필코 구현해보리라 다짐했고 이번 프로젝트에 적용하게 되었습니다.
CD에 대해서 더 찾아보면서
인력거 -> 자전거 -> 자동차 -> 비행기로 발전하기 위해서
사용자들에게 실시간으로 피드백을 받을 수 있는 환경을 구축하기 위함이라는 것을 알게 되었고 이번 프로젝트에서 스프린트가 끝날 때마다 사용자들을 대상으로 피드백을 받아 볼 계획입니다.
다시 CI/CD에 대해서 정의를 내리면 아래와 같습니다.
CI(Continuous Integration)
CD(Continuous Deploy/Delivery)
actions/checkout@v4.1.1
이 무엇인지 고민했는 Action을 사용하는 행위라고 볼 수 있습니다.
이제부터 CI,CD를 적용한 코드를 분석해 보겠습니다.
📌 Github Actions의 Workflow를 명시하고 있는 문서로 이 문서를 읽고 행동합니다.
Workflow가 무엇일까요? Github Repository에 들어가는 작업 단위입니다.
(출처 : https://kotlinworld.com/384)
name: Build and Deploy to EC2
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
env:
BUCKET_NAME: team09-cicd-bucket
PROJECT_NAME: studay
DEPLOYMENT_GROUP_NAME: studay_cicd
CODE_DEPLOY_APP_NAME: studay_cicd
jobs:
# 작업의 이름
build_and_test:
# GitHub Actions 러너의 운영 체제
runs-on: ubuntu-latest
# 순차적으로 실행될 단계들을 정의하는 섹션
steps:
- name: Checkout code
uses: actions/checkout@v4.1.1
- name: Set up JDK 17
uses: actions/setup-java@v3.13.0
with:
java-version: '17'
distribution: 'corretto'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew #gradlew 실행
- name: Build with gradle
run: ./gradlew build
- name: Make Zip File
run: zip -qq -r ./$GITHUB_SHA.zip .
shell: bash
- 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_PRIVATE_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: Upload to S3
run: aws s3 cp --region ap-northeast-1 ./$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip
- name: Code Deploy To EC2 instance
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=$PROJECT_NAME/$GITHUB_SHA.zip
PROJECT_NAME: studay
s3의 하위 폴더명으로 생성된다.
name: Make Zip File
이 코드 스니펫은 GitHub Actions의 워크플로우 파일에 속하는 부분으로 보입니다. 여기서 사용된 내용을 해석해보겠습니다:
name: Make Zip File
: 이 부분은 이 액션의 이름을 정의합니다. GitHub Actions 워크플로우에서 각 단계는 이름으로 식별됩니다.run: zip -qq -r ./$GITHUB_SHA.zip .
: 이 부분은 실행될 명령을 정의합니다. 여기서 하는 일은 현재 디렉토리(.
)에 있는 파일들을 $GITHUB_SHA.zip
이라는 이름의 압축 파일로 만드는 것입니다. $GITHUB_SHA
는 GitHub에서 제공하는 환경 변수로, 현재 커밋의 SHA 해시 값을 나타냅니다.shell: bash
: 이 부분은 명령이 실행될 쉘(shell)을 지정합니다. 여기서는 Bash 쉘을 사용하도록 지정되어 있습니다.이 코드는 주로 GitHub Actions을 사용하여 소스 코드나 다른 파일들을 압축 파일로 만들 때 사용될 것입니다. 이 경우, 워크플로우는 현재 코드가 포함된 커밋의 SHA 해시를 이름으로 하는 ZIP 파일을 생성합니다.
📌 인스턴스에 배포하기 위해 사용되는 스크립트
AppSpec 파일은 파일에 정의된 일련의 수명 주기 이벤트 후크로 각 배포를 관리하는 데 사용
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/studay
permissions:
- object: /home/ubuntu/studay/
owner: ubuntu
group: ubuntu
hooks:
BeforeInstall:
- location: scripts/deploy.sh
timeout: 300
runas: root
AfterInstall:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
📌 배포 중 수정의 파일을 인스턴스의 위치로 복사하는 경우에만 필요
files:
- source: /
destination: /home/ubuntu/studay
source
if (source == 파일명) { // 단순 파일명만 표기
지정한 파일만 인스턴스에 복사
} else if (source == directory) { // directory 표기
directory 내의 모든 파일이 인스턴스에 복사
} else if (source == "/") { // = 슬래시 하나인 경우
수정된 버전의 모든 파일이 인스턴스에 복사
}
여기서 잠깐! 수정된 버전 = 그냥 배포될 파일을 이렇게 표현하는 것 같습니다.destination
destination
명령은 인스턴스에서 파일이 복사되어야 하는 위치를 식별합니다./root/destination/directory
(Linux, RHEL, Ubuntu) 또는 c:\destination\folder
(Windows)와 같은 정규화된 경로여야 합니다.file_exists_behavior
files:
- source: Config/config.txt
destination: /webapps/Config
- source: source
destination: /webapps/myApp
📌 ‘files’ 섹션에서 정의한 파일이 인스턴스에 복사된 후 해당 파일에 권한이 어떻게 적용되어야 하는지를 지정
permissions:
- object: /home/ubuntu/studay/
owner: ubuntu
group: ubuntu
EC2/온프레미스 배포용으로만 ‘permissions’ 섹션을 사용한다.
AWS Lambda 또는 Amazon ECS 배포에는 resources 섹션이 사용
📌 EC2/온프레미스 배포에 대한 ‘hooks’ 섹션에는 배포 수명 주기 이벤트 후크를 하나 이상의 스크립트에 연결하는 매핑이 포함되어 있습니다.
hooks:
BeforeInstall:
- location: scripts/deploy.sh
timeout: 300
runas: root
AfterInstall:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
내부 요소
📌 appspec.yml로 인해 실행되는 shell script
#!/usr/bin/env bash
REPOSITORY=/home/ubuntu/studay
cd $REPOSITORY
APP_NAME=studay
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep 'SNAPSHOT.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
if [ -z $CURRENT_PID ]
then
echo "> 종료할 애플리케이션이 없습니다."
else
echo "> kill -9 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
echo "> Deploy - $JAR_PATH "
nohup java -jar $JAR_PATH > /dev/null 2> /dev/null < /dev/null &
#!/usr/bin/env bash
는 이 스크립트가 Bash 쉘에서 실행됨을 나타냅니다.REPOSITORY=/home/ubuntu/studay
: REPOSITORY
환경 변수에 리포지토리 경로(/home/ubuntu/studay
)를 할당합니다.cd $REPOSITORY
: 현재 작업 디렉토리를 REPOSITORY
경로로 변경합니다.APP_NAME=studay
: 애플리케이션 이름을 APP_NAME
변수에 할당합니다.JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep 'SNAPSHOT.jar' | tail -n 1)
:ls $REPOSITORY/build/libs/
명령어로 해당 디렉토리 내의 파일 목록을 가져옵니다.grep 'SNAPSHOT.jar'
는 파일 중에서 'SNAPSHOT.jar'이라는 문자열을 포함한 파일을 찾습니다.tail -n 1
로 최신 JAR 파일을 선택합니다. 이 파일명을 JAR_NAME
변수에 할당합니다.JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
: 앞서 찾은 최신의 JAR 파일 이름을 기반으로 JAR 파일의 전체 경로를 JAR_PATH
변수에 할당합니다.CURRENT_PID=$(pgrep -f $APP_NAME)
: 실행 중인 프로세스 중에서 애플리케이션 이름에 해당하는 프로세스의 PID(Process ID)를 찾아 CURRENT_PID
변수에 할당합니다.if [ -z $CURRENT_PID ]
는 현재 실행 중인 프로세스가 없는 경우를 확인합니다.echo "> kill -9 $CURRENT_PID"
는 종료할 프로세스의 PID를 출력합니다.kill -15 $CURRENT_PID
로 해당 PID에 해당하는 프로세스를 강제로 종료합니다.sleep 5
로 5초 동안 대기합니다.nohup java -jar $JAR_PATH > /dev/null 2> /dev/null < /dev/null &
은 새로운 프로세스로 JAR 파일을 실행합니다. nohup
은 세션 로그아웃 후에도 프로세스를 계속 실행하도록 합니다. > /dev/null 2> /dev/null < /dev/null
은 표준 입력 및 출력을 비활성화하여 백그라운드에서 실행되도록 합니다.이 스크립트는 주어진 경로에서 최신 SNAPSHOT JAR 파일을 찾아 실행 중인 프로세스를 종료하고, 새로운 JAR 파일로 애플리케이션을 실행시킵니다.
The deployment failed because a specified file already exists at this location:
1) appspec.yml에서 beforeInstall 과정에서 수행할 스크립트를 추가한다. (이미 존재하고 있는 파일 삭제 후 진행)
hooks:
BeforeInstall:
- location: scripts/deploy.sh
timeout: 300
runas: root
2) 파일들을 OVERWRITE 한다.
**file_exists_behavior : OVERWRITE**
https://goodgid.github.io/Github-Action-CI-CD-CodeDeploy-App-Spec-File/