😎

CloudFormation と GitHub Actions と CodeDeploy で ECS の Blue/Green デプロイする

2023/07/06に公開

アルダグラムでエンジニアをしている @sukechannnn です!

最近、社内でデプロイ基盤を刷新するプロジェクトを進めていたのですが、その中で CloudFormation と GitHub Actions と CodeDeploy を使った ECS の Blue/Green デプロイをしようとしたところ、調べても情報が出てこなくて困りました。

公式ドキュメントを読みながら設定した結果、上手くできたので紹介します。

違う、そうじゃない

「CodeDeploy CFn Fargate Blue/Green デプロイ」でググってよく出てくる方法は、公式に書いてある AWS::CodeDeploy::BlueGreen hook を使った方法 です。このやり方は「CFn で CodeDeploy の Blue/Green デプロイをトリガーする」方法であり、やりたいことと微妙に違いました。

今回やりたかったのは以下のような方法です。

  • CFn は CodeDeploy のセットアップ、ECS Service, TaskDefinition などAWSリソースの構成管理にのみ利用
  • CodeDeploy による ECS の Blue/Green デプロイのトリガーは GitHub Actions 経由で行う

設定方法

CloudFormation の設定

CFn の yaml の例は以下の通りです(CodeDeploy に関する設定のみ書いています)。

Import しているリソースは、予め作られている想定です。

  • demo-ecs-cluster
  • demo-ecs-service
  • demo-target-group
  • demo-alb-listener
  • demo-ecs-codedeploy-role
AWSTemplateFormatVersion: "2010-09-09"
Description: CodeDeploy with ECS Blue/Green deploy

Resources:
  EcsCodeDeploy:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: "demo-ecs-codedeploy"
      ComputePlatform: ECS

  EcsDeploymentGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      ApplicationName: !Ref EcsCodeDeploy
      AutoRollbackConfiguration:
        Enabled: True
        Events:
          - "DEPLOYMENT_FAILURE"
      BlueGreenDeploymentConfiguration:
        DeploymentReadyOption:
          ActionOnTimeout: CONTINUE_DEPLOYMENT
          WaitTimeInMinutes: 0
        TerminateBlueInstancesOnDeploymentSuccess:
          Action: TERMINATE
          TerminationWaitTimeInMinutes: 5
      DeploymentConfigName: "CodeDeployDefault.ECSAllAtOnce"
      DeploymentGroupName: "demo-ecs-codedeploy-group"
      DeploymentStyle:
        DeploymentOption: "WITH_TRAFFIC_CONTROL"
        DeploymentType: "BLUE_GREEN"
      ECSServices:
        - ClusterName:
            Fn::ImportValue: "demo-ecs-cluster"
          ServiceName:
            Fn::ImportValue: "demo-ecs-service"
      LoadBalancerInfo:
        TargetGroupPairInfoList:
          - TargetGroups:
              - Name:
                  Fn::ImportValue: "demo-target-group-blue"
              - Name:
                  Fn::ImportValue: "demo-target-group-green"
            ProdTrafficRoute:
              ListenerArns:
                - Fn::ImportValue: "demo-alb-listener"
      ServiceRoleArn:
        Fn::ImportValue: "demo-ecs-codedeploy-role-arn"

ポイントは以下の通りです。

あとは BlueGreenDeploymentConfiguration で好きな設定にすればOKです。上記の設定では、タイムアウトはなし、デプロイが完了した時の古いインスタンスを終了するまでの時間を5分で設定してます。

GitHub Actions の設定

次は GitHub Actions の設定です。main ブランチにマージしたらデプロイする想定で書いています。

name: Deploy app to Amazon ECS

pull_request:
  types: [ closed ]
  branches:
    - 'main'

# permission can be added at job level or workflow level
permissions:
  id-token: write

jobs:
  ecr-push:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1-node16
        with:
          role-to-assume: arn:aws:iam::xxxxx:role/GithubActionsRole
          role-session-name: GitHubActions-${{ github.run_id }}
          aws-region: ap-northeast-1

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

      # DockerイメージをビルドしてECRにプッシュ
      - name: Docker image build and push to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: path/to/demo-app
          IMAGE_TAG: ${{ github.sha }}
        run: |
          echo $ECR_REGISTRY/$ECR_REPOSITORY
          docker login -u AWS -p $(aws ecr get-login-password) https://xxxx.dkr.ecr.ap-northeast-1.amazonaws.com
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f Dockerfile --build-arg AWS_ACCOUNT_ID="xxxxx" --build-arg IMAGE_TAG=$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

      # amazon-ecs-render-task-definition を使うために TaskDefinition を ECS から取ってくる
      # TaskDefinition を CFn で管理しているためこうしている
      - name: Fetch TaskDefinition from ECS and generate task-def-api.json (api)
        run: |
          aws ecs describe-task-definition --task-definition app-task-definition | \
            jq '.taskDefinition | del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities)' > task-def-api.json

      # TaskDefinition の image を push した最新のものに書き換える
      - name: Render TaskDefinition
        id: render-container-api
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-def-api.json
          container-name: app-service-container
          image: ${{ steps.login-ecr.outputs.registry }}/path/to/demo-app:${{ github.sha }}

      # デプロイする
      - name: Deploy app with CodeDeploy Blue/Green deployment
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.render-container-api.outputs.task-definition }}
          cluster: demo-ecs-cluster
          service: demo-ecs-service

image はデプロイの度にビルドして ECR に push し、その最新の image を用いてデプロイするようにしています。

以下の Action を利用して TaskDefinition の更新とデプロイを行っています。

先ほど CFn で demo-ecs-cluster の demo-ecs-service に対して CodeDeploy で Blue/Green デプロイを設定しているので、amazon-ecs-deploy-task-definition@v1 でそのクラスター/サービスを指定してデプロイすることで、CodeDeploy 経由で Blue/Green デプロイすることができます。

まとめ

CloudFormation と GitHub Actions を用いて CodeDeploy で Blue/Green デプロイする方法について紹介しました。すべてコード化/自動化できてとても便利ですね!

CloudFormation, GitHub Actions と書きましたが、CodeDeploy の設定は Terraform でもできますし、GitHub Actions で行っていることも AWS CLI を使えばできると思うので、利用している技術に応じて適宜読み替えてもらえればと思います。

アルダグラム Tech Blog

Discussion