Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Kuma's Curious Paradise

[이룸] Github Actions Workflow 설정 파일 살펴보기 / 버전 관리 본문

이룸 프로젝트

[이룸] Github Actions Workflow 설정 파일 살펴보기 / 버전 관리

쿠마냥 2024. 4. 29. 21:30

프로젝트 발표날에 버전 관리를 어떻게 하고 있냐는 질문을 받았다. 이후 팀원들이 모여 '버전 관리'의 의미가 무엇인지 한참을 논의했었다. 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

  1. 코드 체크아웃 : 개발자가 Master 브랜치에 push를 하면, 해당 깃허브 레포지토리에 있는 코드를 작업 환경에 복제한다. 
  2. JDK 설정 : 'actions/checkout'이라는 이름을 가진 액션의 3버전을 사용하겠다는 의미다. JDK를 설정한다. 이때 temurin 배포판을 사용한다. 
  3. 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를 사용하여 프로젝트를 빌드한다. 테스트는 제외한다.
  4. Docker Hub 로그인 : 도커 공용 레지스트리인 Docker Hub에 로그인한다. 
  5. Docker Image 빌드: 'eroom-prod:latest'
    1) docker build --platform linux/amd64 -t ${{ secrets.DOCKER_USERNAME }}/eroom-prod:latest . : linux/amd64 플랫폼에 적합한 이미지를 빌드한다. 이후 해당 이미지에 'latest' 태그를 단다. 
  6. 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

  1. 타겟 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 명령을 사용한다. 
  2. Docker Compose 실행  위에 'latest' 태그를 단 최신 이미지를 빌드한다. 
  3. 배포서버 URL 확인 : 서버 헬스 체크
  4. 롤백 : 웹사이트에 문제가 생긴 경우 현재의 도커 이미지들을 모두 내리고 'previous' 태그를 단 이미지를 빌드한다. 
  5. 로드 밸런서 대기 : 로드 밸런서가 새로 배포된 컨테이너를 인식하고 트래픽을 올바르게 라우팅할 시간적 여유(1분)를 제공한다. 
  6. Nginx 업스트림 변경 : 새로운 서버로 리디렉션 하도록 설정한다. nginx 재시작 없이 새로운 설정을 적용한다. (새로운 서버 버전으로 부드러운 전환을 위해 재시작하지 않고 설정 적용하는 것) 
  7. 현재 서버 중지 : 최신 이미지 빌드가 완료되면 현재 서버(사실은 이전 서버, 환경 변서 설정에는 현재 서버라고 되어 있음)를 중지한다. 
  8. Docker 이미지 정리 : 사용하지 않는 이미지들은 모두 삭제한다. 
  9. 타겟 서버 확인 : AWS 로드 밸런서를 통해 새로 배포된 타겟 서버 헬스 체크
  10. 최신 이미지 버전 푸시 : 배포가 성공하면 '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