프로젝트/서버관리

블루/그린 배포 방식

쿠키담임선생님 2025. 8. 19. 09:16

서버 이중화의 필요성


  1. 무중단 서비스 제공
    1. 하나의 서버에 장애가 발생해도 다른 서버로 연결이 가능함.
  2. 부하 분산
    1. 로드 밸런서를 통해 여러 서버로 부하를 분산시킬 수 있음.

로드밸런서란?


클라이언트 요청을 여러 서버로 분산시켜주는 중간 장치

주요목적

  • 가용성 : 특정 서버 장애 시 다른 서버가 요청 처리.
  • 확장성 : 서버 대수를 늘려 트래픽 급증에 대응.
  • 성능 최적화 : 과부하를 방지해 응답 속도 유지.

무중단 배포 방식의 필요성


  1. 서비스 연속성 유지
    1. 배포 중에도 사용자 요청을 처리할 수 있어, 서비스 이용경험을 최대화 시킬 수 있음.
  2. 비즈니스 손실 최소화
    1. 짧은 다운타임도 거래 실패, 수익 손실로 이어질 수 있음.
  3. 안정적 롤백 및 단계적 배포
    1. 문제가 발견되면 신속하게 이전 버전으로 되돌릴 수 있음.
  4. 운영, 유지보수 편의성
    1. 배포 전용 시간을 따로 잡지 않아도 되어, 스케줄에 구애받지 않고 배포가 가능하다.
  5. 자동화/ci/cd 파이프 라인 강화
    1. 배포 자동화 수준이 높아지므로, 코드품질 향상으로도 이어진다.
    2. 반복 가능한 배포 프로세스가 정립되어, 실수로 인한 인프라 설정 오류나 누락을 예방할 수 있다.
  6. 고객 신뢰 확보
    1. 사용자 입장에서는 항상켜져 있는 안정적인 서비스가 좋은 인상을 남긴다.

 

 

무중단 배포의 방식은 몇개가있는가


1. 블루-그린(Blue-Green) 배포

  • 개념:
    • 운영 중인 ‘Blue’ 환경과 새로운 버전을 배포할 ‘Green’ 환경을 동일한 스펙으로 별도로 운영
    • Green 환경 검증 후 로드밸런서 설정만 바꿔(트래픽 스위칭) 전환
  • 장점:
    • 배포 전후 환경이 완전히 분리되어 있어, 문제가 생기면 즉시 롤백(Blue로 재전환) 가능
    • 테스트·검증→전환 과정이 명확
  • 단점:
    • 동일 사양의 리소스를 두 벌 운영해야 하므로 비용 증가
    • 데이터베이스 스키마 변경 시 동기화가 까다로울 수 있음

2. 카나리(Canary) 배포

  • 개념:
    • 신규 버전을 소수의 사용자(예: 5~10%)에게만 먼저 배포하여 안정성 검증
    • 이상 없으면 점진적으로 트래픽 비율을 늘려 전체 롤아웃
  • 장점:
    • 장애 확산 범위를 초기에 차단 가능
    • 실사용 데이터 기반 안정성 검증
  • 단점:
    • 점진 배포를 위한 트래픽 조절 로직 구현 필요
    • 롤아웃 기간 동안 Blue/Green보다 운영 복잡도 증가

3. 롤링(Rolling) 업데이트

  • 개념:
    • 서버 풀(pool)을 N개 배치했을 때, 일정 단위(예: 1대씩)로 순차 교체
    • 각 교체 시점마다 로드밸런서에서 제외 후 배포→재등록
  • 장점:
    • 전체 서버가 동일한 환경으로 점진 전환되어 리소스 낭비 없음
    • 배포 속도가 빠르고 비용 효율적
  • 단점:
    • 롤백 시 이미 교체된 서버 수만큼 되돌려야 하므로 복구 복잡
    • 짧은 시간이라도 특정 서버만 구버전 동작 → 버전 불일치 가능성

어떻게 적용했는지


  1. 기존 인스턴스를 2개로 먼저 만들기 (오토 스케일링 그룹에서 min/max 를 2로 고치면 된다.)
    1. 이때 인스턴스 2개로 분산이 잘 되는지 로드밸런스 확인 한번 해주기.
    2. 그리고 min/max를 2로 고치고 0으로 고치고 왓다갓다 테스트 해보기. 이때 0으로 고칠때 적용이 안되면 일시 중지된 프로세스(ReplaceUnhealthy, HealthCheck, Terminate) 이렇게 넣어보고, 그래도 안되면 다시 해당 프로세스 뺀 다음에 0으로 고치고 적용해보기. 약간 반영이 느릴 수 있음.
  2. 오토스케일 그룹을 하나 더 만들어서 인스턴스를 2개 더 추가해야함
    1. 이때 그러면 총 인스턴스가 4개가 되어있는 상태.
    2. 즉 오토스케일링 그룹이 2개이고 각각 2개씩 인스턴스를 가진 상황임.
    3. 이렇게 해야하는 이유가 타겟그룹을 통해 왓다갓다 하면서 가중치를 부여하기 위함.
  3. 기존 타겟그룹의 설정을 참고하여 green용으로 하나 더 만들기.
    1. 기존 타겟 그룹에서 green이라는 이름을 붙여서 하나 더만들기.
    2. 타겟 그룹은 로드밸런서 타기 전에 가중치 0/1 을 주게 되면서 그린 타겟과 블루 타겟을 왓다갓다 하는 용도임.
  4. 기존 로드밸런서에 새로 생성한 타겟그룹을 붙이기.
    1. 리스너 편집을 통해 대상그룹을 하나 더 추가한다.
    2. 아래 사진을 참고

위 작업을 마치면 아래 사진처럼 보인다.

 

 

 

추가적으로 code deploy에 들어가서 해당 설정에 오토스케일링 그룹을 추가해주어야함.

 

 

 

 

 

배포스크립트 설명


Step 1: CodeDeploy로 배포 시작

text- name: Deploy with CodeDeploy
  env:
    AWS_ACCESS_KEY_ID:     ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  run: |
    deployment_id=$(aws deploy create-deployment \\
      --application-name ${{ env.APPLICATION_NAME }} \\
      --deployment-group-name ${{ env.DEPLOYMENT_GROUP_NAME }} \\
      --file-exists-behavior OVERWRITE \\
      --s3-location bucket=${{ env.S3_BUCKET_NAME }},bundleType=zip,key=${{ env.ZIP_FILE_NAME }} \\
      --query 'deploymentId' --output text)
    echo "DEPLOYMENT_ID=$deployment_id" >> $GITHUB_ENV
  • CodeDeploy를 통해 S3에 업로드한 zip 파일로 배포를 시작
  • 생성된 deployment_id를 환경변수로 저장

Step 2: 배포 완료 대기

text- name: Wait for deployment to complete
  run: |
    aws deploy wait deployment-successful --deployment-id ${{ env.DEPLOYMENT_ID }}
  • CodeDeploy 배포가 성공적으로 완료될 때까지 대기합니다.

Step 3: 현재 Prod/Stage TG/ASG 결정

text- name: Determine current Prod and Stage TG/ASG
  run: |
    PROD_TG=$(aws elbv2 describe-listeners \\
      --listener-arn ${{ env.LISTENER_ARN }} \\
      --region ${{ env.AWS_REGION }} \\
      --query 'Listeners[0].DefaultActions[0].ForwardConfig.TargetGroups[?Weight==`1`].TargetGroupArn' \\
      --output text)
    if [ "$PROD_TG" = "${{ env.BLUE_TG_ARN }}" ]; then
      PROD_ASG="${{ env.BLUE_ASG_NAME }}"
      STAGE_TG="${{ env.GREEN_TG_ARN }}"
      STAGE_ASG="${{ env.GREEN_ASG_NAME }}"
    else
      PROD_ASG="${{ env.GREEN_ASG_NAME }}"
      STAGE_TG="${{ env.BLUE_TG_ARN }}"
      STAGE_ASG="${{ env.BLUE_ASG_NAME }}"
    fi
    echo "PROD_TG=$PROD_TG" >> $GITHUB_ENV
    echo "PROD_ASG=$PROD_ASG" >> $GITHUB_ENV
    echo "STAGE_TG=$STAGE_TG" >> $GITHUB_ENV
    echo "STAGE_ASG=$STAGE_ASG" >> $GITHUB_ENV
  • 현재 ELB 리스너가 트래픽을 보내고 있는 Target Group(Prod)을 조회
  • Blue/Green 방식에 따라 Prod/Stage의 TG, ASG를 결정
  • 결과를 환경변수로 저장

Step 4: Stage ASG 인스턴스 2대로 스케일업

text- name: Scale Stage ASG to 2 for HA
  run: |
    aws autoscaling update-auto-scaling-group \\
      --auto-scaling-group-name $STAGE_ASG \\
      --min-size 2 --max-size 2 --desired-capacity 2
  • Stage(새 버전) ASG의 인스턴스 수를 2대로 늘려 고가용성(HA) 확보

Step 5: Stage TG 인스턴스 헬스체크 대기

text- name: Wait for Stage TG instances to be healthy
  run: |
    aws elbv2 wait target-in-service \\
      --target-group-arn $STAGE_TG \\
      --region ${{ env.AWS_REGION }}
  • Stage Target Group에 연결된 인스턴스들이 정상(Healthy) 상태가 될 때까지 대기

Step 6: 트래픽을 Stage TG로 스위칭

text- name: Switch traffic to Stage TG
  run: |
    aws elbv2 modify-listener \\
      --listener-arn ${{ env.LISTENER_ARN }} \\
      --default-actions '[{"Type":"forward","ForwardConfig":{"TargetGroups":[{"TargetGroupArn":"'"$STAGE_TG"'","Weight":1},{"TargetGroupArn":"'"$PROD_TG"'","Weight":0}]}}]'
  • ELB 리스너의 트래픽을 Stage TG(새 버전)로 전환

Step 7: 이전 Prod ASG 인스턴스 0대로 스케일다운

text- name: Scale down old Prod ASG to zero
  if: always()
  run: |
    echo "Scaling down old Prod ASG ($PROD_ASG) to zero to save cost"
    aws autoscaling update-auto-scaling-group \\
      --auto-scaling-group-name $PROD_ASG \\
      --min-size 0 --max-size 0 --desired-capacity 0 \\
      --region ${{ env.AWS_REGION }}
  • 이전 Prod ASG의 인스턴스 수를 0으로 줄여 비용 절감

Step 8: Stage 인스턴스로 새 AMI 생성 및 Launch Template 업데이트

text- name: Create AMI from one Stage instance and update Launch Template
  run: |
    IMAGE_NAME="ami-dev-app-$(date -u +%Y%m%d%H%M%S)"
    INSTANCE_ID=$(aws elbv2 describe-target-health \\
      --target-group-arn $STAGE_TG \\
      --query 'TargetHealthDescriptions[0].Target.Id' \\
      --output text)
    new_ami_id=$(aws ec2 create-image \\
      --instance-id $INSTANCE_ID \\
      --name $IMAGE_NAME \\
      --no-reboot \\
      --query 'ImageId' --output text)
    echo "New AMI ID: $new_ami_id"
    USER_DATA=$(base64 -w0 < scripts/user-data.sh)
    cat > template-data.json <<EOF
    {
      "ImageId": "$new_ami_id",
      "UserData": "$USER_DATA"
    }
    EOF
    aws ec2 create-launch-template-version \\
    --launch-template-id ${{ env.LAUNCH_TEMPLATE_ID }} \\
    --version-description "Auto-updated $(date -u)" \\
    --source-version '$Latest' \\
    --launch-template-data file://template-data.json \\
    --query 'LaunchTemplateVersion.VersionNumber' --output text > ver.txt
    NEW_VER=$(cat ver.txt)
    aws ec2 modify-launch-template \\
    --launch-template-id ${{ env.LAUNCH_TEMPLATE_ID }} \\
    --default-version $NEW_VER
  • Stage(새 버전) 인스턴스 중 하나로부터 AMI 생성
  • user-data.sh 스크립트를 base64 인코딩하여 Launch Template에 포함
  • 기존 Launch Template에서 새로운 버전을 생성(새 AMI 사용)
  • 새 버전을 기본 버전으로 지정