문제 상황

  • 현재 CI/CD 파이프라인으로는 코드에 오류가 있을 때 서버가 바로 내려가는 불편함이 있었다. typo 하나로 서버 down..🥶
  • 이 상황에 문제를 느껴 code deploy를 활용하여 cd 파이프라인을 개선하였다.
  • 무중단 배포, 그 중에서도 blue green 배포 전략을 사용하였고 이를 위해 ec2 auto scaling을 추가하였다.
    • blue green 배포 전략을 위해서는 원본 인스턴스와 대체 인스턴스가 필요한데 이것 때문에

Code deploy, Blue Green 배포 전략이란

무중단 배포(Zero-downtime Deployment)

  • 서비스가 중단되지 않은 상태에서 배포하기.
  • 무중단 배포를 하기 위해서는 최소 서버 2대 이상을 확보해야 한다.

Blue Green 배포 전략

트래픽을 한번에 구버전에서 신버전으로 옮기는 방법이다. Blue/Green 배포 전략에서는 현재 운영중인 서비스의 환경을 Blue라고 부르고, 새롭게 배포할 환경을 Green이라고 부른다.

Blue와 Green의 서버를 동시에 나란히 구성해둔 상태로 배포 시점에 로드 밸런서가 트래픽을 Blue에서 Green으로 일제히 전환시킨다. Green 버전 배포가 성공적으로 완료 되었고, 문제가 없다고 판단했을 때에는 Blue 서버를 제거할 수 있다. 혹은 다음 배포를 위해 유지해둘수 있다.

더이상 dev 서버가 잘못된 커밋으로 인해 내려가는 일이 없어짐!

Code Deploy를 위한 AWS 설정

1. IAM 설정

  • ec2 인스턴스에서 ecr,s3에 접근하기 위한 IAM 역할을 생성해준다.
  • code deploy 앱에 부여하는 IAM 역할을 생성해준다.
  • 저기서 auto-scaling-policy는 직접 추가해준 정책으로 다음과 같다.
  • { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "iam:PassRole", "ec2:CreateTags", "ec2:RunInstances" ], "Resource": "*" } ] }

2. AMI 생성

  • 기존에 배포되어있던 인스턴스의 AMI 를 생성한다.
    • AMI란?
      • 일종의 EC2를 시작하기 위한 기본 세팅의 모음

3. 시작 템플릿 생성

  • EC2 > 시작 템플릿 > 생성으로 들어가서 시작 템플릿을 만든다.
    • 이는 auto scaling 시 인스턴스를 시작하기 위한 템플릿으로 사용될 것
  • 내 소유의 AMI를 선택한다.
  • 키페어는 기존 인스턴스에서 사용하던 키페어를 동일하게 등록하였다.
  • 그 후 인스턴스 스토리지는 t2.micro, 20GB로 선택해주었다.
  • 고급 세부 정보 > IAM 인스턴스 프로파일에 들어가서 기존에 ec2에 연결하기 위해 만들어두었던 IAM 역할을 선택한다. 그래야 새로 만든 auto scaling 인스턴스에도 IAM 역할이 부여된다.

4. Target Group(대상그룹) 생성

  1. 서비스의 로드밸런서의 타겟이 되는 대상 그룹을 생성한다.
    1. 대상그룹 > 대상에 지금은 아무것도 없는게 정상!

5. Load Balancer 생성

  • 대상그룹의 연결하는 load balancer를 생성한다.
  • 보안 정책은 기본으로 등록되어 있는 값을 사용하고 미리 발급해둔 acm 인증서를 연결하자.

6. Auto Scaling Group 설정

  • auto scaling group을 생성한다.
  • 시작 템플릿은 아까 만들어준 템플릿을 사용한다.
  • auto scaling group의 그룹 크기를 정한다.
    • 현재 우리 서비스는 1,1,2로 설정
  • 배포 구성은 CodeDeployDefault.AllAtOnce로 설정
  • 아까 생성한 로드밸런서를 연결한다.

7. CodeDeploy 설정하기

  • Code Deploy에서 애플리케이션 생성 > 배포 그룹을 생성한다.
  • 서비스 역할은 최초에 code deploy용 으로 만든 IAM 역할을 입력한다.
  • 배포 유형은 블루/그린 방법 선택.
  • 방금 만든 auto scaling group을 연결한다.

8. health check API 분리

  • 서버 코드 단에서 spring actuator를 활용하여 health check api를 분리하였다.

CI/CD 설정 : github actions와 연동

name: Deploy

on:
  # 특정 브랜치로 푸시될 때에만 워크플로우를 실행합니다.
  push:
    branches:
      - develop
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: setup jdk 17
        uses: actions/setup-java@v2
        with:
          distribution: 'adopt'
          java-version: '17'
          cache: 'gradle'

      - name: add permission to gradlew
        run: chmod +x ./gradlew
        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_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Create .env file
        run: |
          touch .env
          echo "${{ secrets.ENV_VARS }}" >> .env

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: weddingmate
          IMAGE_TAG: latest
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          mkdir scripts
          touch scripts/deploy.sh
          echo "aws ecr get-login-password --region ap-northeast-2 | sudo docker login --username AWS --password-stdin $ECR_REGISTRY" >> scripts/deploy.sh
          echo "sudo docker pull $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh
          echo "sh /home/ubuntu/srv/weddingmate/config/scripts/deploy.sh $IMAGE_TAG" >> scripts/deploy.sh

      - name: Upload to S3
        env:
          ZIP_TAG: ${{ github.sha }}
        run: |
          zip -r deploy-$ZIP_TAG.zip ./scripts appspec.yml
          aws s3 cp --region ap-northeast-2 --acl private ./deploy-$ZIP_TAG.zip s3://weddingmate-codedeploy-bucket

      - name: Start deploy
        env:
          IMAGE_TAG: ${{ github.sha }}
        run: |
          aws deploy create-deployment --application-name deploy \
          --deployment-config-name CodeDeployDefault.OneAtATime \
          --deployment-group-name deploy-group \
          --s3-location bucket=weddingmate-codedeploy-bucket,bundleType=zip,key=deploy-$IMAGE_TAG.zip

      - name: Slack notification
        uses: 8398a7/action-slack@v3
        with:
            status: ${{ job.status }}
            author_name: Github Action Test
            fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
        env:
            SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()
  1. AWS IAM 설정
  2. ECR 로그인
  3. Docker 이미지 빌드 ( 빌드한 이미지의 버전은 github sha )
  4. 빌드한 이미지를 ECR로 push
  5. CodeDeploy에 필요한 스크립트를 생성
  6. 스크립트들을 압축하여 S3에 업로드
  7. Deploy 시작

구체적인 코드와 설명을 보고 싶다면 https://github.com/SWM-Space-Odyssey/WeddingMate_BackEnd/pull/119 를 참고하세요.

마주한 에러들 aka 삽질일기

배포 2 단계 오류

The overall deployment failed because too many individual instances failed deployment, too few healthy instances are available for deployment, or some instances in your deployment group are experiencing problems.

배포 스크립트에 문제가 있을 때 발생하는 오류. 에러 로그를 확인하여 해결해야 한다.

CodeDeploy 배포 로그 위치

CodeDeploy Log: /var/log/aws/codedeploy-agent/codedeploy-agent.log

  • CodeDeploy Agent와 관련된 로그가 쌓이는 곳

Deploy Log: /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

  • 해당 인스턴스에서 배포되는 로그들이 배포 그룹, ID와 관계없이 다 쌓인다.

Deploy ID별 로그: /opt/codedeploy-agent/deployment-root/{deployment-group-ID}/{deployment-ID}/logs/scripts.log

  • 특정 배포 ID의 로그를 보고 싶을 경우 참조하면 된다.Codedeploy AllowTraffic 에서 무한 로딩 후 배포 실패
ERROR [codedeploy-agent(658)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Error polling for host commands: Aws::Errors::MissingCredentialsError - unable to sign request without credentials set
ERROR [codedeploy-agent(658)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Missing credentials - please check if this instance was started with an IAM instance profile
  • 대상그룹에서 health check 가 fail하면서 생긴 문제
  • health check가 성공하도록 성공 코드를 401로 변경해두었다.

Slack Notification 추가

  • slack webhook 사용하여 추가
    ```yaml
  • name: Slack notification
    uses: 8398a7/action-slack@v3
    with:env:if: always()
  • SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  • status: ${{ job.status }} author_name: Github Action Test fields: repo,message,commit,author,action,eventName,ref,workflow,job,took

https://velog.io/@king/slack-incoming-webhook

마치면서

  • 나의 삽질이 우리 서비스의 배포 과정에 조금이라도 도움이 되었으면 합니다..💗

참조

https://velog.io/@kshired/Github-Actions-ECR-Auto-Scaling-Group-EC2-CodeDeploy-S3-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-BlueGreen-CICD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0

BELATED ARTICLES

more