Kuma's Curious Paradise
[이룸] Github Actions Workflow 설정 파일 살펴보기 / 버전 관리 본문
프로젝트 발표날에 버전 관리를 어떻게 하고 있냐는 질문을 받았다. 이후 팀원들이 모여 '버전 관리'의 의미가 무엇인지 한참을 논의했었다. blue / green 전략을 택한 것이 곧 버전 관리이다, 개발용 배포용 버전을 어떻게 관리하냐는 질문이다 등등 다양한 이야기가 오고가다가 도달한 결론은, '서버 빌드가 실패했을 시 이전 버전으로 롤백이 되도록 수정하자'였다. 그래서 현재 버전에 문제가 생겼을 시 예전 버전으로 롤백하기 쉽도록 하는 것이 '버전 관리'가 아닌가 한 것이다. 따라서 새로운 이미지가 올라오면 blue, green을 번갈아가며 빌드하는 단순한 전략에서 발전하여 지금의 설정 파일이 되었다. 오늘은 이러한 버전 관리가 어떻게 구성되었는지, Github Actions Workflow 설정 파일을 살펴본다.
1. 기본 설정
# github workflow 이름
name: CI/CD
# event trigger
# master브랜치에 push 되었을 때 실행
on:
push:
branches: [ "master" ]
# 권한 설정 부분. 이 workflow는 '읽기'만 가능하다
permissions:
contents: read
2. 작업 1 : Build
- 코드 체크아웃 : 개발자가 Master 브랜치에 push를 하면, 해당 깃허브 레포지토리에 있는 코드를 작업 환경에 복제한다.
- JDK 설정 : 'actions/checkout'이라는 이름을 가진 액션의 3버전을 사용하겠다는 의미다. JDK를 설정한다. 이때 temurin 배포판을 사용한다.
- Gradle 빌드 : appliction.yml 파일을 읽어와 애플리케이션을 빌드한다.
1) mkdir -p ./src/main/resources: 현재 디렉토리에 src/main/resources 디렉토리를 생성한다.
2) echo ${{ secrets.YML }} | base64 --decode > ./src/main/resources/application.yml: GitHub Secrets에서 암호화된 YAML 파일을 가져와 base64로 디코딩한다. 이후 해당 내용을 ./src/main/resources/application.yml 파일에 넣는다.
3) cat ./src/main/resources/application.yml: 방금 생성한 application.yml 파일 내용을 콘솔에 출력한다.
4) chmod +x ./gradlew: Gradle Wrapper 스크립트에 실행 권한을 부여한다. gradlew는 빌드를 도와준다.
5) ./gradlew build -x test: gradlew를 사용하여 프로젝트를 빌드한다. 테스트는 제외한다. - Docker Hub 로그인 : 도커 공용 레지스트리인 Docker Hub에 로그인한다.
- Docker Image 빌드: 'eroom-prod:latest'
1) docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest . : linux/amd64 플랫폼에 적합한 이미지를 빌드한다. 이후 해당 이미지에 'latest' 태그를 단다. - Docker Image 푸시 : 빌드된 이미지를 Docker Hub에 푸시한다.
jobs:
build:
# 작업 환경 : 우분투 최신 버전
runs-on: ubuntu-latest
steps:
# JDK setting
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# Gradle 빌드
- name: Build with Gradle
run: |
mkdir -p ./src/main/resources
echo ${{ secrets.YML }} | base64 --decode > ./src/main/resources/application.yml
cat ./src/main/resources/application.yml
chmod +x ./gradlew
./gradlew build -x test
# Docker Hub 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# 최신 버전 이미지를 빌드
- name: Build Current Version Docker Image
run: docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest .
# 빌드된 최신 버전 이미지를 Docker Hub에 푸시
- name: Push Docker Images
run: |
docker push ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest
3. 작업 2: Deploy
- 타겟 IP 설정 :
STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.HOST_PROD }}/env") : HOST_PROD로 요청을 보낸 후 응답으로 오는 출력은 무시하고(-o /dev/null) HTTP 상태코드만 출력하여 STATUS 환경 변수를 설정한다. 이후 STATUS 코드가 200이 오면, 업스트림을 응답으로 오는 데이터(blue or green)로 설정하고 아니면 green으로 설정한다.
echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV : 현재 업스트림을 환경변수로 저장한다.
*** GITHUB_ENV : 이 파이은 깃허브 액셔닝 자동 관리하는 임시 생성 파일로, workflow 내에서 echo 명령을 통해 추가할 수 있다. 명령을 추가한 후에는 자유롭게 사용 가능. 따로 저장해야 하는 파일은 아니며, 변수를 바꾸고 싶다면 echo 명령을 사용한다. - Docker Compose 실행 위에 'latest' 태그를 단 최신 이미지를 빌드한다.
- 배포서버 URL 확인 : 서버 헬스 체크
- 롤백 : 웹사이트에 문제가 생긴 경우 현재의 도커 이미지들을 모두 내리고 'previous' 태그를 단 이미지를 빌드한다.
- 로드 밸런서 대기 : 로드 밸런서가 새로 배포된 컨테이너를 인식하고 트래픽을 올바르게 라우팅할 시간적 여유(1분)를 제공한다.
- Nginx 업스트림 변경 : 새로운 서버로 리디렉션 하도록 설정한다. nginx 재시작 없이 새로운 설정을 적용한다. (새로운 서버 버전으로 부드러운 전환을 위해 재시작하지 않고 설정 적용하는 것)
- 현재 서버 중지 : 최신 이미지 빌드가 완료되면 현재 서버(사실은 이전 서버, 환경 변서 설정에는 현재 서버라고 되어 있음)를 중지한다.
- Docker 이미지 정리 : 사용하지 않는 이미지들은 모두 삭제한다.
- 타겟 서버 확인 : AWS 로드 밸런서를 통해 새로 배포된 타겟 서버 헬스 체크
- 최신 이미지 버전 푸시 : 배포가 성공하면 'latest' 버전을 'previous' 버전 태그를 달고 푸시한다. 필요한 경우 이전 버전으로 쉽게 롤백하기 위함이다.
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Set target IP
run: |
STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.HOST_PROD }}/env")
echo $STATUS
if [ $STATUS = 200 ]; then
CURRENT_UPSTREAM=$(curl -s "http://${{ secrets.HOST_PROD }}/env")
else
CURRENT_UPSTREAM=green
fi
echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV
if [ $CURRENT_UPSTREAM = blue ]; then
echo "CURRENT_PORT=8080" >> $GITHUB_ENV
echo "STOPPED_PORT=8081" >> $GITHUB_ENV
echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV
elif [ $CURRENT_UPSTREAM = green ]; then
echo "CURRENT_PORT=8081" >> $GITHUB_ENV
echo "STOPPED_PORT=8080" >> $GITHUB_ENV
echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV
fi
- name: Docker compose
uses: appleboy/ssh-action@master
with:
username: ubuntu
host: ${{ secrets.HOST_PROD }}
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script_stop: true
script: |
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest
sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d
- name: Check deploy server URL
uses: jtalk/url-health-check-action@v3
with:
url: http://${{ secrets.HOST_PROD }}:${{env.STOPPED_PORT}}/env
max-attempts: 5
retry-delay: 10s
# 웹사이트에 문제가 발생한 경우 롤백 수행
- name: Rollback if Site Issue
if: steps.check_deploy_server_url.outcome == 'failure'
run: |
# 이전 버전 이미지를 사용하여 롤백
sudo docker-compose -f docker-compose-${{ env.TARGET_UPSTREAM }}.yml down
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/eroom-prod:previous
sudo docker-compose -f docker-compose-${{ env.TARGET_UPSTREAM }}.yml up -d
- name: Wait for Load Balancer to Register Targets
run: sleep 60
- name: Change nginx upstream
uses: appleboy/ssh-action@master
with:
username: ubuntu
host: ${{ secrets.HOST_PROD }}
key: ${{ secrets.PRIVATE_KEY }}
script_stop: true
script: |
sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{ env.TARGET_UPSTREAM }};" > /etc/nginx/conf.d/service-env.inc && nginx -s reload'
- name: Stop current server
uses: appleboy/ssh-action@master
with:
username: ubuntu
host: ${{ secrets.HOST_PROD }}
key: ${{ secrets.PRIVATE_KEY }}
script_stop: true
script: |
sudo docker stop ${{env.CURRENT_UPSTREAM}}
sudo docker rm ${{env.CURRENT_UPSTREAM}}
- name: Prune unused Docker images
run: sudo docker image prune -a
- name: Check Target Health
run: |
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-east-1:471112860836:targetgroup/eroomTargetGroup/029a9432dd208dc7 | jq -r '.TargetHealthDescriptions[].TargetHealth.State'
# 배포가 정상적으로 완료 시 현재 버전을 이전 버전으로 푸시
- name: Push Docker previous Images
if: ${{ github.event_name == 'deployment' && github.event.deployment.status == 'success' }}
run: |
# 현재 배포된 버전을 이전 버전으로 푸시
docker tag ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest ${{ secrets.DOCKER_USERNAME }}/eroom-prod:previous
docker push ${{ secrets.DOCKER_USERNAME }}/eroom-prod:previous
'이룸 프로젝트' 카테고리의 다른 글
[이룸] 검색 기능 고도화 4 - MySQL FULL-TEXT 성능 테스트 (0) | 2024.04.29 |
---|---|
[이룸] 도커의 작동 방식과 주요 개념들 (1) | 2024.04.26 |
[이룸] Redis 영속성 설정 / RDB + AOF (0) | 2024.04.24 |
[이룸] Redis 저장을 위한 직렬화 / 역직렬화 (1) | 2024.04.23 |
[이룸] elasticsearch 관련 트러블슈팅 1 - config에 ssl 관련 설정 삽입 (0) | 2024.04.17 |