[Infra] Github Actions + ECR + Auto Scaling Group + EC2 + CodeDeploy + S3 를 사용하여 Blue/Green CI/CD 구축하기

kshired·2022년 8월 13일
14
post-thumbnail

이 포스팅은 간단하게(?) Github Actions + ECR + Auto Scaling Group + CodeDeploy + S3를 사용하여 Blue Green 배포를 해보는 과정을 다음 글입니다.

이 글이 끝났을 때, CI/CD 과정은 위 그림과 같을 것이며 하나하나 알아보겠습니다.

이 글은 Github Actions과 블루 그린 배포에 대한 이해와, ECR, VPC 등이 이미 설정되어 있다고 생각하고 진행 할 것이며 다른 IAM, ALB, Auto Scaling Group, Code Deploy 등은 하나하나 다루어볼 예정입니다.

추가로 작성의 편의성을 위해 반말로 진행되는 점은 양해부탁드립니다.

IAM 설정

우리가 제일 먼저 해야할 것은 IAM을 설정하는 것이다. IAM은 AWS에서 권한등을 관리하기 위해 사용하는 것으로 이를 통해 특정 인스턴스나 그룹 혹은 계정에 특정 권한을 줄 수 있다.

Policy

S3 Access Policy

S3 접근을 위한 policy로 Auto Scaling 그룹을 통해 생성된 EC2에서 사용된다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

ECR Access Policy

ECR 접근을 위한 policy로 Auto Scaling 그룹을 통해 생성된 EC2에서 사용된다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GrantSingleImageReadOnlyAccess",
            "Effect": "Allow",
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:GetRepositoryPolicy",
                "ecr:DescribeRepositories",
                "ecr:ListImages",
                "ecr:DescribeImages",
                "ecr:BatchGetImage"
            ],
            "Resource": "arn:aws:ecr:${region}:${aws_id}:repository/${img_name}"
        },
        {
            "Sid": "GrantECRAuthAccess",
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        }
    ]
}

여기서, region, aws_id, img_name 은 적절히 변경해주자.

CodeDeploy Auto Scaling Policy

Code Deploy 배포 그룹에서 사용 될 Policy로 Auto Scaling 그룹에 접근할 수 있는 권한을 준다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "ec2:CreateTags",
                "ec2:RunInstances"
            ],
            "Resource": "*"
        }
    ]
}

Role

Auto Scaling Role

위에서 만든 S3 Acess PolicyECR Access Policy 를 연결하도록 하자. 이 role은 생성되는 EC2가 S3와 ECR에 접근할 수 있도록 하는 역할이다.

사진에서 Auto-Scaling-Policy는 S3 Access Policy입니다.

CodeDeploy Auto Scaling Role

이 role은 아래에서 생성 할 CodeDeploy 앱에게 권한을 주는 role으로, 아래와 같이 위에서 만든 CodeDeploy Auto Scaling policy와 AWS에서 기본적으로 제공하는 AWSCodeDeployRole을 연결하자.

사진에서 Codedeploy-autosclaing은 S3 CodeDeploy Auto Scaling policy입니다.

AMI 생성

CodeDeploy, Docker 등등을 사용하기 위한 AMI를 생성하자.

AMI는 Amazon Machine Image로, AWS 인스턴스를 생성할 때 base가 되는 이미지이다. 이러한 이미지들을 ubuntue, centos 등등 아래와 같이 제공하고 있으며 우리가 직접 설정한 내용들을 미리 구현하여 이미지로 만들어 둘 수 있다.

간단하게 EC2 인스턴스를 생성하고, 그 인스턴스에 Docker, CodeDeployAgent 등등.. 필요한 것들을 미리 설치하자.

그 후에 아래와 같이 AMI를 생성하자. ( 작업 → 이미지 및 템플릿 → 이미지 생성 )

생성을 누르면, 이미지 이름과 설명을 간단하게 적을 수 있는데 적당히 적고 넘어가자.

생성을 하면 아래와 같은 이미지 → AMI에 들어가면, 이미지가 생성되어 있다. 그것을 시작 템플릿에서 사용할 것이다.

Launch Template ( 시작 템플릿 ) 생성

시작 템플릿은, 어떠한 인스턴스를 만들 때 그 인스턴스를 간단하게 시작하기 위한 템플릿이다.

이 시작 템플릿을 만들어두면, 시작 템플릿의 내용대로 바로 인스턴스를 만들 수 있다.

기본 설정

EC2 → 시작 템플릿 → 시작 템플릿 생성을 누르자.

위와 같이 나온다면, 간단하게 템플릿 이름과 설명을 적고 아래로 내려가서 아까 만들어 두었던 AMI를 선택하자.

아래로 내리면, 인스턴스 유형도 선택할 수 있는데 필요한 사양의 인스턴스 유형을 선택하자.

나는 비용이 가장 적으면서, 2a, 2b, 2c 가용영역에서 사용할 수 있는 t3-micro를 선택했다.

추가로 사용하고 있는 키페어가 있다면, 그걸 사용해도 되고 새로운 키페어를 만들어서 사용해도 된다.

이 키페어는 앞으로 Auto Scaling Group으로 설정 되어 생성 될 ec2 그룹들에 로그인하기 위해 사용될 것이다.

네트워크 설정

서브넷 정보는 Auto Scaling Group에서 설정할 것이기 때문에 현재는 사용하지 않음으로 두자.

하지만, 보안 그룹은 제대로 설정해야한다. 이 보안그룹이 속해있는 VPC에 따라 Auto Scaling Group 생성시 서브넷과 VPC를 제대로 선택할 수 있다.

나는 api 서버를 위한 보안그룹이 미리 생성되어 있어 아래와 같이 설정했다.

( 나는, ssh와 spring 서버에 대한 인바운드를 허용하기 위해 22, 8080 을 허용하고 아웃바운드는 모두 허용해주었다. )

마지막으로 가장 중요한 작업이 남았다.

아까 위에서 만든, IAM을 위에서 만든 Role과 연결하여 인스턴스가 생성될 때 CodeDeploy, S3, ECR에 접근할 수 있도록 권한을 주자.

고급 세부 정보 → IAM 인스턴스 프로파일 → 아까 만들어 두었던 Auto Scaling Role 선택

나는 Auto Scaling Monstera Role 이라는 이름으로 Role을 만들었기에, 이것을 사용하겠다.

Target Group ( 대상 그룹 ) 생성

이제 로드밸런서를 생성하기 전 그룹으로 묶일 인스턴스들의 단위인 대상 그룹을 만들어야한다.

이 그룹들은 함께 묶여 로드밸런서에 의해 로드밸런싱 될 것이며, 특정 엔드포인트를 통해 Health Check될 것이다.

유형은 간단하게 인스턴스들을 묶을 것이기 때문에, 인스턴스로 선택하자.

이름은 간단하게 적고, 포트는 스프링 부트의 기본 포트인 8080으로 매핑해줄 것이다.

추가로, 대상 그룹들이 속하는 VPC를 정확하게 선택하자.

Health check를 위한 엔드포인트는 간단하게, spring boot actuator를 사용하여 /actuator/health를 사용하도록 한다.

고급 상태 검사 설정을 적절하게 바꾸어주자.

이 때, 간격과 정상 임계 값을 크게 잡으면 Blue Green 배포시 시간을 많이 소모하게 된다. ( 현재 설정에선 최악의 경우, 한 인스턴스당 150초 ( 30초 * 5회 ) + a 까지 기다리게 된다. )

너무 작지도, 너무 크지도 않은 값을 적절하게 설정하자.

로드 밸런서 생성

private subnet에 있는 인스턴스 그룹들이 외부로 노출되기 위해서는 로드 밸런서를 설정해야한다.

이제 로드밸런서를 생성하고, 이름을 적고 아래와 같이 설정하자.

체계에서 “내부”로 설정을 하게 된다면, 외부로 노출되는 것이 아닌 private subnet에서 특정 그룹에 대한 로드밸런싱을 하게 되니 주의하자.

네트워크 매핑

ALB가 배치 될 VPC를 선택하고, 그 ALB가 어느 가용영역들을 커버할지 매핑하자.

나는 이미 az를 2a, 2b, 2c 를 사용할 수 있도록 VPC를 구성하였기 때문에 2a, 2b, 2c를 모두 선택할 것이다.

중요한 것은 2a, 2b, 2c 모두 public 영역으로 정의된 ( 라우팅 테이블을 설정한 ) 서브넷들을 선택해야한다.

그래야 2a, 2b, 2c 가용역역으로 들어오는 트래픽을 분산시켜 줄 수 있다.

리스너 및 라우팅

아까 만들어둔 대상 그룹에 HTTP 80, HTTPS 443 을 연결하자.

이렇게 설정하면 ALB가 만들어주는 url을 통해 http 혹은 https를 이용하여 접근할 수 있게 된다.

필자는 http의 보안 문제를 염려하여, http 80을 허용하지 않았다.

즉, https로 들어오는 요청만 유효하게 API 서버들에게 로드밸런싱 될 것이다.

보안 정책 선택

이제 https로 설정한 것을 위해 보안 정책과 인증서를 선택하자. 보안 정책은 기본으로 되어 있는 값을 사용하면 되고, 만약 ALB에서 생성해주는 url 대신 다른 도메인을 사용할 것이라면 ACM에서 인증서를 선택하자. ( 이 작업은 미리 Route53을 통해 도메인을 설정하고, ACM 인증서를 받아두어야 한다. )

이렇게 설정이 끝나면, ALB 생성을 누르자.

Auto Scaling Group 생성

이제 Auto Scaling Group 을 생성할 차례이다. Auto Scaling Group은 트래픽 혹은 하드웨어 사용량에 따라 인스턴스의 개수를 줄이거나 늘릴 수 있게 해주는 서비스이다.

그룹 이름은 또 적당히 적고, 아까 만들어 둔 시작 템플릿을 사용하자.

VPC 설정

아까 시작 템플릿에서 네트워크를 일부러 적지 않은 것이, Auto Scaling Group 에서 사용하기 위함이다.

이제 아래와 같이, 생성 될 인스턴스들의 VPC를 선택하고 인스턴스들이 어느 가용영역에 생성 될지 선택하자.

나는 private subnet 영역에 각 인스턴스들을 배치하고, ALB를 통해 트래픽을 분산시킬 것이기 때문에 아래와 같이.

pivate subnet 3개를 선택해주었다.

로드밸런싱 정책 선택

이렇게 설정하고 넘어가면 로드밸런싱 정책을 선택해야한다.

로드밸런서와 대상 그룹을 이미 선택했기 때문에 아래와 같이 미리 만들어둔 대상그룹에 연결하자.

나머지는 그대로 두고, 다음을 누르자.

Auto Scaling Group 정책 선택

이제는 Auto Scaling Group에 의해 생성될 인스턴스들의 최소 값, 최대 값, 평상시 원하는 값을 설정해야한다.

나는 각 가용영역에 일단 1개씩 분배하여 원하는 용량 3, 최소 용량 3을 지정하고, 만약 트래픽이 더 들어올시 최대 6개까지 올라갈 수 있게 설정하였다.

그 후 알림과 태그는 적절하게 설정하자.

검토 영역에서 여태까지 잘 선택했는지 체크하고, Auto Scaling Group을 생성하자.

이제 생성하면, Auto Scaling Group에 의해 자동으로 인스턴스가 시작될 것이다.

생성되면 대시보드에서 Auto Scaling Group에 의해 관리되는 인스턴스들을 확인할 수 있다.

CodeDeploy 설정

이제 인프라를 세팅했으니, CodeDeploy를 세팅하자.

CodeDeploy는 appspec.yml 이라는 yml 파일에 의해 작동하는 도구로, 원하는 스크립트를 실행하거나 동작 시킬 수 있다.

나는 CodeDeploy를 통해, S3에 올라간 script를 EC2 인스턴스에 다운받아 실행시키려고 한다.

appspec.yml 생성

그렇기에 다음과 같이 appspec.yml 을 정의하고 git repository에 포함시키자. ( git repository에 포함시키는 이유는 github actions를 통해 S3에 업로드 할 것이기 때문이다. )

version: 0.0
os: linux
files:
    - source: /
      destination: /home/ubuntu/app
      overwrite: yes

permissions:
    - object: /
      pattern: "**"
      owner: ubuntu
      group: ubuntu
      mode: 755

hooks:
    AfterInstall:
        - location: scripts/deploy.sh
          timeout: 60
          runas: ubuntu

간단하게 살펴보면 3가지 일을 한다.

  1. S3에 업로드 된 zip 파일의 압축을 풀어 /home/ubuntu/app 으로 이동.
  2. 이동 시키기 전 파일들의 권한을 설정한다. ( shell script 실행을 위해 실행 권한을 주자 )
  3. hooks는 codedeploy를 통해 ec2에 다운받은 파일을 실행한다.

결국, CodeDeploy를 통해 S3에 업로드 된 deploy script를 실행하는 것이라고 보면 된다.

배포 생성

CodeDeploy 배포 생성을 누르자.

간단하게 이름과 어떤 대상을 위한 CodeDeploy인지만 체크하면, 생성이 완료된다.

배포 그룹 생성

배포 그룹은 배포 어플리케이션의 하위 항목으로 EC2가 어떠한 방법으로 배포 될 지 정의하는 항목이다.

배포 그룹 이름은 적당히 만들고, 서비스 역할에서 초반에 만든 CodeDeploy Auto Scaling Role을 연결하자.

배포 유형 및 환경 구성

배포 유형은 무중단 배포 및 roll-back이 가능한 블루/그린을 선택하자.

환경 구성은 아까만든 Auto Scaling 그룹을 자동으로 복사하여 사용하도록 하자.

배포 설정 및 트래픽 라우팅 설정

배포 설정은 새로 만든 Auto Scaling Group을 어떻게 할 것인지, 설정하는 부분이다.

배포 성공시 트래픽을 Green Group으로 돌릴 수 있도록, 트래픽 재 라우팅은 즉시 라우팅으로 설정하자.

또, Blue Group으로의 롤백이 가능하도록하기위해 어느 정도 후에 종료할지 적절히 설정하자.

( 나는 dev는 6시간, prod는 하루 동안 유지되도록 하였다. )

로드 밸런서는 아까 만들어둔 대상 그룹에 연결하면 된다. 이렇게 설정하면 Green group으로 traffic routing이 가능해질 때 대상 그룹으로 인스턴스를 등록해준다.

CI/CD 설정

이제 CodeDeploy 설정이 끝났으니, Github actions와 dockerfile, script를 작성하자.

여기서 고민해보아야 할 것은 ECR에 올라가는 도커 이미지의 버전인데, 보통 간단하게 github commit sha를 사용한다. 이 값은 커밋에 따라 변하므로, deploy를 위한 shell script도 동적으로 생성해야한다.

그렇기에 나는 github actions 에서 shell script를 동적으로 생성하기로 정하였다.

Dockerfile 작성

Github Actions를 위한 yml 파일 작성 전에, docker 파일을 생성하자.

FROM adoptopenjdk:11 AS builder
COPY .editorconfig .
COPY gradlew .
COPY settings.gradle.kts .
COPY build.gradle.kts .
COPY gradle gradle
COPY src src
COPY backend-config backend-config
RUN chmod +x ./gradlew
RUN ./gradlew build

FROM adoptopenjdk:11
RUN mkdir /opt/app
COPY --from=builder build/libs/*.jar /opt/app/spring-boot-application.jar
EXPOSE 8080
ENV	PROFILE local
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=${PROFILE}" ,"/opt/app/spring-boot-application.jar"]

Github Actions 파일 작성

name: Deploy to dev env

on:
    push:
        branches:
            - dev
jobs:
    build-docker:
        runs-on: ubuntu-latest
        steps:
            - name: checkout
              uses: actions/checkout@v2
              with:
                  token: ${{ secrets.PRIVATE_TOKEN }}
                  submodules: true

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

            - name: add permission to gradlew
              run: chmod +x ./gradlew
              shell: bash

            - name: aws configure
              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: Login to ECR
              id: login-ecr
              uses: aws-actions/amazon-ecr-login@v1

            - name: build docker file and setting deploy files
              env:
                ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
                ECR_REPOSITORY: cs-broker
                IMAGE_TAG: ${{ github.sha }}
              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 | docker login --username AWS --password-stdin $ECR_REGISTRY" >> scripts/deploy.sh
                echo "docker pull $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh
                echo "docker run -p 8080:8080 -e PROFILE=dev -d --restart always --name csbroker-api $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh
            - name: upload to s3
              env:
                IMAGE_TAG: ${{ github.sha }}
              run: |
                zip -r deploy-$IMAGE_TAG.zip ./scripts appspec.yml
                aws s3 cp --region ap-northeast-2 --acl private ./deploy-$IMAGE_TAG.zip s3://cs-broker-bucket
            - name: start deploy
              env:
                IMAGE_TAG: ${{ github.sha }}
              run: |
                aws deploy create-deployment --application-name csbroker-dev-api-codedeploy \
                --deployment-config-name CodeDeployDefault.OneAtATime \
                --deployment-group-name csbroker-dev-api \
                --s3-location bucket=cs-broker-bucket,bundleType=zip,key=deploy-$IMAGE_TAG.zip

Github Actions의 자세한 설명은 넘어가고, 간단하게 어떠한 동작을 하는지만 설명하겠다.

  1. AWS IAM 설정
  2. ECR 로그인
  3. Docker 이미지 빌드 ( 빌드한 이미지의 버전은 github sha )
  4. 빌드한 이미지를 ECR로 push
  5. CodeDeploy에 필요한 스크립트를 생성
  6. 스크립트들을 압축하여 S3에 업로드
  7. Deploy 시작

1-4 는 적절하게 secret 값을 github setting에 설정한 다음 사용하면 된다.

당연히 1에서 설정하는 IAM은 S3, ECR, CodeDeploy를 사용할 수 있는 권한이 있는 IAM이여야한다.

5부터는 자세히 알아보자

mkdir scripts
touch scripts/deploy.sh
echo "aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin $ECR_REGISTRY" >> scripts/deploy.sh
echo "docker pull $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh
echo "docker run -p 8080:8080 -e PROFILE=dev -d --restart always --name csbroker-api $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> scripts/deploy.sh

5번 과정은 간단한 과정으로, scripts 디렉토리를 생성하고, deploy.sh 파일을 생성하는 과정을 담고 있다.

차례로 설명하면아래와 같다.

  1. scripts 디렉토리 생성
  2. deploy.sh 생성
  3. ecr login script를 deploy.sh 에 추가
  4. docker image를 pull 받는 script를 deploy.sh에 추가
  5. docker image를 run하는 script를 deploy.sh 에 추가

이제 이렇게 생성한 script를 아까 작성한 appspec.yml과 함께 압축하여 S3에 업로드하자. ( 이것이 6번 과정이다. )

zip -r deploy-$IMAGE_TAG.zip ./scripts appspec.yml
aws s3 cp --region ap-northeast-2 --acl private ./deploy-$IMAGE_TAG.zip s3://버킷이름
  1. deploy-{도커 이미지 버전}.zip 으로 스크립트 압축파일 생성
  2. s3에 업로드

이제 S3에 올린 스크립트를 CodeDeploy가 실행할 수 있게 트리거를 날리자.

aws deploy create-deployment --application-name CodeDeploy 앱 이름 \
--deployment-config-name CodeDeployDefault.OneAtATime \
--deployment-group-name 배포 그룹 이름 \
--s3-location bucket=버킷이름,bundleType=zip,key=deploy-$IMAGE_TAG.zip

간단하게 설명하면, S3에 올린 스크립트를 이용해 CodeDeploy 앱의 특정 배포그룹에 배포를 진행하는 것으로 생각하면 된다.

실제 Deploy 과정 살펴보기

이제 우리는 CI/CD 세팅을 완벽하게 끝냈다.

이제 dev 브랜치로 merge되면 자동으로 배포가 시작되며, 위에서 정한 시간에 따라 blue group이 자동으로 삭제될 것이다!

Github Actions

Code Deploy

마치면서

이번 CI/CD 를 구성하면서, 많은 어려움을 겪었다.

CodeDeploy + ECR + EC2 + Auto Scaling Group을 함께 사용하는 예시를 인터넷 어느 곳에서도 찾을 수 없었기에 직접 하나하나 구성하면서 실패도했다.

어떻게 된게 정말 하나도 없습니다.

이 글을 읽는 사람들 중 나와 같은 방식으로 배포를 계획하고 있는 사람이 있다면, 이 글을 통해 문제 없이 배포할 수 있으면 좋겠다!

끝!

profile
글 쓰는 개발자

2개의 댓글

comment-user-thumbnail
2023년 1월 17일

ami ec2의 서브넷과 vpc 는 어떻게 하나요?

답글 달기
comment-user-thumbnail
2023년 1월 20일

글 잘 읽었습니다! 새로 빌드된 내용을 도커 이미지로 만들어서 전달하고 배포하시는 형태로 하셨는데 이 경우 ASG에 의해서 Launch Template으로 EC2 instance가 실행되어야 하는 경우 이전 버전의 AMI여서 update가 안될 것같은데 이 문제는 어떻게 해결하셨나요?

답글 달기