클라우드 Prod 환경 CICD 및 Blue Green 배포 구축 확정 롤백 - 100-hours-a-week/16-Hot6-wiki GitHub Wiki

1. Confirm Prod Deployment Workflow – 불필요한 MIG 정리

전체코드

name: Confirm Prod Deployment (Clean Old MIG)

 on:
   workflow_dispatch:
     inputs:
       confirm_slot:
         description: '배포된 슬롯 이름(예: blue, green) ❌삭제할 버전 아님❌'
         required: true

 jobs:
   confirm:
     runs-on: ubuntu-latest
     env:
       ZONE: asia-northeast3-a

     steps:
       - name: Authenticate to GCP
         uses: google-github-actions/auth@v2
         with:
           credentials_json: '${{ secrets.GCP_SA_KEY }}'

       - name: Confirm deployment and clean old MIG
         run: |
           SLOT="${{ github.event.inputs.confirm_slot }}"
           ZONE="$ZONE"

           if [ "$SLOT" == "blue" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$SLOT"-==-"blue"-); then
             OLD_SLOT=green
           else
             OLD_SLOT=blue
           fi

           OLD_MIG="onthetop-backend-$OLD_SLOT"

           echo "$OLD_MIG 를 삭제합니다..."
           gcloud compute instance-groups managed delete $OLD_MIG --zone=$ZONE --quiet || true

Confirm Prod Deployment (Clean Old MIG) 워크플로는 배포가 정상적으로 검증된 뒤 여분의 MIG를 제거해 리소스 비용을 줄입니다.

트리거 & 인풋

입력 설명 예시
confirm_slot 현재 트래픽을 받고 있는 슬롯 이름(삭제 대상이 아님) blue

핵심 로직

  1. 대상 식별 : 입력 슬롯이 blue면 green MIG를, 반대면 blue MIG를 OLD_MIG로 지정.

  2. MIG 삭제

    gcloud compute instance-groups managed delete $OLD_MIG --zone=$ZONE --quiet || true
    
    • || true 로 MIG가 이미 없을 때도 오류 없이 종료 – 멱등성 보장.

검증 후 1‑클릭으로 불필요한 인스턴스 그룹을 없애 비용 최적화깨끗한 슬롯 관리를 동시에 달성합니다.


2. Rollback Prod Workflow – 안전한 전체 롤백

전체코드

name: Rollback Prod (Safe)

 on:
   workflow_dispatch:
     inputs:
       be_slot:
         description: '현재 잘못 배포된 BE 슬롯 (예: blue, green)'
         required: true
       fe_slot:
         description: '현재 잘못 배포된 FE 슬롯 (예: blue, green)'
         required: true

 jobs:
   rollback:
     runs-on: ubuntu-latest
     env:
       REGION: asia-northeast3
       ZONE: asia-northeast3-a
       BACKEND_SERVICE: onthetop-backend-service
       CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
       AWS_DEFAULT_REGION: ap-northeast-2

     steps:
       - name: Authenticate to GCP
         uses: google-github-actions/auth@v2
         with:
           credentials_json: '${{ secrets.GCP_SA_KEY }}'

       - name: Set up gcloud SDK
         uses: google-github-actions/setup-gcloud@v2

       - name: Set up AWS CLI
         uses: aws-actions/configure-aws-credentials@v2
         with:
           aws-access-key-id: ${{ secrets.CLOUDFRONT_ACCESS_KEY }}
           aws-secret-access-key: ${{ secrets.CLOUDFRONT_SECRET_KEY }}
           aws-region: ap-northeast-2

       - name: Rollback Backend with MIG Health Check
         run: |
           SLOT="${{ github.event.inputs.be_slot }}"
           if [ "$SLOT" == "blue" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$SLOT"-==-"blue"-); then
             TARGET="green"
           else
             TARGET="blue"
           fi

           NEW_MIG="onthetop-backend-$TARGET"
           OLD_MIG="onthetop-backend-$SLOT"
           PROJECT=$(gcloud config get-value project)

           echo "🔍 $TARGET MIG 인스턴스 그룹 헬스체크 중..."
           for i in {1..20}; do
             STATUS=$(gcloud compute instance-groups managed list-instances "$NEW_MIG" \
               --zone="$ZONE" --format=json | \
               jq -r 'map(.instanceStatus) | unique | .[]')

             echo "⏳ 시도 $i: 인스턴스 상태 = $STATUS"
             [ "$STATUS" == "RUNNING" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$STATUS"-==-"RUNNING"-) && break
             sleep 10
           done

           if [ "$STATUS" != "RUNNING" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$STATUS"-!=-"RUNNING"-); then
             echo "❌ MIG 인스턴스 그룹이 준비되지 않았습니다. 롤백 중단."
             exit 1
           fi

           echo "✅ MIG 인스턴스 그룹 준비 완료. 백엔드에 연결 중..."
           gcloud compute backend-services add-backend "$BACKEND_SERVICE" \
             --global \
             --instance-group="$NEW_MIG" \
             --instance-group-zone="$ZONE"

           echo "🔍 LB to MIG 헬스체크 확인 중..."
           MIG_URL="https://www.googleapis.com/compute/v1/projects/$PROJECT/zones/$ZONE/instanceGroups/$NEW_MIG"

           for i in {1..20}; do
             STATE=$(gcloud compute backend-services get-health "$BACKEND_SERVICE" --global --format=json | \
               jq -r --arg MIG "$MIG_URL" \
                 '.[] | select(.backend == $MIG) | (.status.healthStatus // []) | map(.healthState) | unique | .[]?')

             echo "⏳ 시도 $i: 상태 = $STATE"
             [ "$STATE" == "HEALTHY" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$STATE"-==-"HEALTHY"-) && break
             sleep 10
           done

           if [ "$STATE" != "HEALTHY" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$STATE"-!=-"HEALTHY"-); then
             echo "❌ HEALTHY 상태가 아님. 롤백 중단."
             exit 1
           fi

           echo "✅ HEALTHY 상태 확인됨. 롤백 진행."
           gcloud compute backend-services remove-backend "$BACKEND_SERVICE" \
             --global \
             --instance-group="$OLD_MIG" \
             --instance-group-zone="$ZONE" || true

           echo "🧹 $OLD_MIG 삭제 중..."
           gcloud compute instance-groups managed delete "$OLD_MIG" --zone="$ZONE" --quiet || true

       - name: Rollback CloudFront FE Origin
         run: |
           SLOT="${{ github.event.inputs.fe_slot }}"
           if [ "$SLOT" == "blue" ](/100-hours-a-week/16-Hot6-wiki/wiki/-"$SLOT"-==-"blue"-); then
             TARGET="green"
           else
             TARGET="blue"
           fi

           echo "🔄 FE 슬롯을 $TARGET 으로 되돌립니다."
           aws cloudfront get-distribution-config --id "$CLOUDFRONT_DISTRIBUTION_ID" > raw.json

           ETAG=$(jq -r '.ETag' raw.json)
           jq '.DistributionConfig' raw.json > config-only.json

           jq --arg TARGET "$TARGET" \
             '.Origins.Items[0].OriginPath = "/frontend/prod/\($TARGET)"' \
             config-only.json > updated-config.json

           aws cloudfront update-distribution \
             --id "$CLOUDFRONT_DISTRIBUTION_ID" \
             --if-match "$ETAG" \
             --distribution-config file://updated-config.json

       - name: Invalidate CloudFront Cache
         run: |
           aws cloudfront create-invalidation \
             --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" \
             --paths "/" "/index.html" "/assets/*.js" "/assets/*.css"

배포 실패·장애 발생 시 백엔드 + 프론트엔드를 한 번에 이전 슬롯으로 되돌립니다.

트리거 & 인풋

입력 설명 예시
be_slot 문제 발생한 백엔드 슬롯 green
fe_slot 문제 발생한 프론트엔드 슬롯 green

입력값은 잘못 올라간 슬롯입니다. 워크플로는 자동으로 반대 슬롯을 TARGET으로 판단합니다.

롤백 단계 요약

  1. GCP 인증 & AWS CLI 준비

  2. 백엔드 롤백

    • 대상 TARGET MIG 헬스체크(최대 200s)
    • LB 백엔드 서비스에 TARGET MIG 추가 → HEALTHY 확인
    • 기존 OLD_MIG 제거 + 삭제
  3. 프론트엔드 롤백

    • CloudFront OriginPath를 /frontend/prod/{TARGET} 로 수정
    • 캐시 무효화로 즉시 반영

안전 장치

단계 실패 시 처리
MIG 준비 미완료 워크플로 중단, 기존 상태 유지
LB Health 미통과 새 MIG 제거 후 중단 (트래픽 기존 슬롯 유지)

덕분에 롤백 자체가 실패해도 서비스는 중단되지 않도록 설계되어 있습니다.


실제 운영 흐름 예시

  1. Deploy Prod – 새로운 버전 배포
  2. 기능 검증Confirm Prod Deployment 로 구슬롯 제거
  3. 장애 발견 시 Rollback Prod → 즉시 이전 슬롯으로 전환

배포 스크립트와 위 두개의 워크플로를 조합하면, 배포 → 검증 → 확정 or 롤백까지 전 주기가 자동화되면서도 사람이 개입할 수 있는 안전한 Blue‑Green 파이프라인이 완성됩니다.