CloudFormationでECSのBlue/Greenデプロイを構築（その２ープレースホルダ方法）

#概要

本文はAWS ECS（Fargate）のサービスをBlue/Greenにデプロイして、Code Pipelineからコンテナイメージやアプリを変更させ、CodeBuildでdocker buildし、CodeDeployにてBlueからGreenに切り替えさせる手順を紹介する。
AWS Code pipelineのFargate Blue/Greenのデプロイは、プレースホルダを利用するとCodeDeploy::BlueGreen フックを利用する2方法があるが、今回はプレースホルダを利用して実施する。CodeDeploy::BlueGreen フックを利用して実現するやり方は、前回の記事をご参照ください。

https://qiita.com/zhangsd/items/70283c4c980f2ea5a7c4

両方法の主な違いは、プレースホルダではCodeDeployの機能により、バージョン管理ツール (CodeCommitレポジトリ) に格納された taskdef.json のイメージURI と appspec.ymlのタスク定義のプレースホルダのみを更新してECS Blue/Greenデプロイを実行する。それ以外部分の更新は、Blue/Greenデプロイの管理外となる。

これに対して、CodeDeploy::BlueGreen フック方法ではTransform というAWSのマクロ機能を利用しECS の Blue/Green の環境を作成する。フック全体の広範な変換 (Transform) まで、テンプレートに対してカスタム処理を実行できるようになる。プレースホルダとは異なり、イメージURI以外のタスク定義の更新やサービスの設定更新も、Blue/Greenデプロイの管理範囲となる。

マクロの詳細は、下記のAWS CloudFormation マクロを使用例 - AWS CloudFormation をご参照下さい。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-macros.html

#環境用意
以下のリソースは事前に用意して置く。
ECRリポジトリ
VPC
Subnet1a
Subnet1c
SecurityGroup
Dockerfile

#実施手順
1. ELB（ロードバランサー）、リスナー、ターゲットグループを作成して置く。ELBのリスナーとたーべっとグループは下記のように設定する。
<br />80/TCPのリスナー
<br />80/TCPのリスナーで利用されるターゲットグループ、ターゲット80|
<br />8080/TCPのリスナー
<br />8080/TCPのリスナーで利用されるターゲットグループ、ターゲット同じく80
2. ECS環境を用意します。具体的は、ECSクラスタ、ECSサービス、ECSタスクを作成して置く。また、ECSサービスを作成する際、必ず“Blue/green deployment(powered by AWS CodeDeploy)”を選択する。

    ![phld-service-define.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/6b92e627-490a-37a6-2927-4a8e03d6a2cb.jpeg)

3. ECS サービスを作成する際、作成されたCodeDeploy applicationとdeployment group名をメモしておく。このapplicationとdeployment groupを次にCodePipeline定義する時に必要である。applicationとdeployment groupを作れなかった、若しくは既存のサービスに追加するタスクまたはその他の原因でapplicationとdeployment groupがない場合は、“JSONからapplicationとdeployment groupを作成する”章をご参照ください。

    ![phld-service-codedeploy-app-grp.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/f8696241-4aa8-b17f-7ff6-56ea81c97327.jpeg)


4. ECS タスク定義から、作成されたタスクのJSONタブの全て内容を、taskdef.jsonファイルとしてローカルに保存する。また、保存する前、行"image": "xxxxxxxxxxxx.dkr.ecr.xxxxxx.amazonaws.com/xxxxxx:latest",を"image": "<IMAGE1_NAME>"に変更する。
    ![phld-task-json.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/a9849b23-ffc7-ae89-8561-614a39b1220e.jpeg)
    ![phld-task-image.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/162b4aae-6a98-d1b3-42a7-ddf98a338136.jpeg)

5. 作成されたtaskdef.jsonファイルをCodecommitレジストリにアップロードする。
6. appspec.yml、buildspec.ymlファイルをCodecommitレジストリにアップロードする。appspec.yml のContainName、と buildspec.ymlファイルを実提供するサービスアプリにより変更してください。

```appspec.yml
version: 0.0
Resources:
        - TargetService:
                Type: AWS::ECS::Service
                Properties:
                        TaskDefinition: "<TASK_DEFINITION>"
                        LoadBalancerInfo:
                                ContainerName: "xxxxxxxx-xxxxxxxx1"
                                ContainerPort: "80"

```
```buildspec.yml
version: 0.2
phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region xxxxxx | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.xxxxxx.amazonaws.com
      - REPOSITORY_URI=xxxxxxxxxxxx.dkr.ecr.xxxxxx.amazonaws.com/xxxxxx
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)

  build:
    commands:
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG

  post_build:
    commands:
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '{"Version":"1.0","ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
    files: imageDetail.json
```

#検証方法

１．現用系（ブルー）とアプリ
Ubuntu18コンテナにイメージファイル。コンテナが実行されたらWebBrowserに“Hello World！”を表示される。

```Dockerfile
FROM ubuntu:18.04

# Install dependencies
RUN apt-get update && \
 apt-get -y install apache2

# Install apache and write hello world message
RUN echo 'Hello World!' > /var/www/html/index.html

# Configure apache
RUN echo '. /etc/apache2/envvars' > /root/run_apache.sh && \
 echo 'mkdir -p /var/run/apache2' >> /root/run_apache.sh && \
 echo 'mkdir -p /var/lock/apache2' >> /root/run_apache.sh && \ 
 echo '/usr/sbin/apache2 -D FOREGROUND' >> /root/run_apache.sh && \ 
 chmod 755 /root/run_apache.sh

EXPOSE 80

CMD /root/run_apache.sh
```

開発環境(グリーン)系：
Nginxコンテナにイメージファイル。コンテナが実行されたらWebBrowserに“Welcome to nginx!...”を表示される。

2. Codepipelineスタックを作成する(pholder-pipeline.yml)

Fargate で Ununtu18 コンテナを動かして、ALB経由でHTTP 80番ポートでアクセスするの“HelloWorld！”メッセージを作成する。

```pholder-pipeline.yml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For ECS Fargate Blue/Green Deploy with PlaceHolder
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Parameters:
  AwsTokyoRegion:
    Type: String
    Default: xxxxxx
  Service:
    Type: String
    Default: xxxx
  Env:
    Type: String
    Default: dev

  ECRName:
    Type: String
    Default: xxxxxxxx
  ContainerName:
    Type: String
    Default: xxxxxxxxxxxx
  CodeCommitRepositoryName:
    Type: String
    Default: xxxxxxxx
  CodeDeployAppName:
    Type: String
    Default: AppECS-xxxxxx-xxxxxx-service1
  CodeDeployGrpName:
    Type: String
    Default: DgpECS-xxxxxx-xxxxxx-service1

  NameTagPrefix:
    Type: String
    Default: xxxx
    Description: Prefix of Name tags.
  ServiceName:
    Type: String
    Default: xxxx
    Description: Prefix of Service tags.


# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------#
Resources:
  # ------------------------------------------------------------#
  # IAM Roles
  # ------------------------------------------------------------#
  # CodeWatchEventを実行できるIAMRole
  CloudwatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub  cwe-${Service}-${Env}-xxxx-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: CloudWatchEventsPipelineExecution
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: codepipeline:StartPipelineExecution
                Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}

  # CodeBuildに適用するIAMRole
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub codebuild-${Service}-${Env}-xxxx-role
      Path: /
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SampleCodeBuildAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Resource: '*'
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Effect: Allow
                Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketAcl
                  - s3:GetBucketLocation
              - Effect: Allow
                Action:
                  - codebuild:CreateReportGroup
                  - codebuild:CreateReport
                  - codebuild:UpdateReport
                  - codebuild:BatchPutTestCases
                  - codebuild:BatchPutCodeCoverages
                Resource: '*'
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:GetRepositoryPolicy
                  - ecr:DescribeRepositories
                  - ecr:ListImages
                  - ecr:DescribeImages
                  - ecr:BatchGetImage
                  - ecr:InitiateLayerUpload
                  - ecr:UploadLayerPart
                  - ecr:CompleteLayerUpload
                  - ecr:PutImage
                Resource: '*'

  # CodePipelineに適用するIAMRole
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub codepipeline-${Service}-${Env}-xxxx-role
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SamplePipeline
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Action:
                  - iam:PassRole
                Resource: '*'
                Effect: Allow
                Condition:
                  StringEqualsIfExists:
                    iam:PassedToService:
                      - ecs-tasks.amazonaws.com
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Action:
                  - codecommit:CancelUploadArchive
                  - codecommit:GetBranch
                  - codecommit:GetCommit
                  - codecommit:GetRepository
                  - codecommit:GetUploadArchiveStatus
                  - codecommit:UploadArchive
                Resource: '*'
                Effect: Allow
              - Action:
                  - codedeploy:CreateDeployment
                  - codedeploy:GetApplication
                  - codedeploy:GetApplicationRevision
                  - codedeploy:GetDeployment
                  - codedeploy:GetDeploymentConfig
                  - codedeploy:RegisterApplicationRevision
                  - codedeploy:*
                Resource: '*'
                Effect: Allow
              - Action:
                  - elasticbeanstalk:*
                  - ec2:*
                  - elasticloadbalancing:*
                  - autoscaling:*
                  - cloudwatch:*
                  - sns:*
                  - cloudformation:*
                  - rds:*
                  - sqs:*
                  - ecs:*
                Resource: '*'
                Effect: Allow
              - Action:
                  - codebuild:BatchGetBuilds
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuildBatches
                  - codebuild:StartBuildBatch
                Resource: '*'
                Effect: Allow

  # CodeDeployに適用するIAMRole
  CodeDeployRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'codedeploy.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
      RoleName: !Sub codedeploy-${Service}-${Env}-xxxx-role

  # S3Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  # CloudWatchEventの実行ルール
  AmazonCloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - Fn::Join:
              - ''
              - - 'arn:aws:codecommit:'
                - !Ref 'AWS::Region'
                - ':'
                - !Ref 'AWS::AccountId'
                - ':'
                - !Ref CodeCommitRepositoryName
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - master
      Targets:
        - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
          RoleArn: !GetAtt CloudwatchEventRole.Arn
          Id: codepipeline-AppPipeline

  # CodeBuild
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      ServiceRole: !Ref CodeBuildServiceRole
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - $(aws ecr get-login --no-include-email)
                - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
            build:
              commands:
                - echo Build started on `date`
                - echo Building the Docker image...
                - docker build -t $REPOSITORY_URI:$IMAGE_TAG .
                - docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
            post_build:
              commands:
                - echo Build completed on `date`
                - echo Pushing the Docker images...
                - docker push $REPOSITORY_URI:$IMAGE_TAG
                - echo Writing imageDetail json...
                - echo "{\"name\":\"${ContainerName}\",\"ImageURI\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}" > imageDetail.json
          artifacts:
            files: imageDetail.json
      Environment:
        PrivilegedMode: true
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:4.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: AWS_DEFAULT_REGION
            Value: !Ref AWS::Region
          - Name: REPOSITORY_URI
            Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
          - Name: ContainerName
            Value: !Ref ContainerName
          - Name: DOCKER_BUILDKIT
            Value: '1'
      Name: !Sub ${Service}-${Env}-xxxx-project1
#      Name: !Ref AWS::StackName

  # ------------------------------------------------------------#
  # CodePipeline 
  # ------------------------------------------------------------#
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      Name: !Sub ${ServiceName}-xxxx-pipeline1
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: '1'
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                PollForSourceChanges: false
                BranchName: master
              RunOrder: 1
              OutputArtifacts:
                - Name: App
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: '1'
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              RunOrder: 1
              InputArtifacts:
                - Name: App
              OutputArtifacts:
                - Name: BuildOutput

        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: '1'
                Provider: CodeDeployToECS
              Configuration:
                AppSpecTemplateArtifact: App
                AppSpecTemplatePath: appspec.yml
                TaskDefinitionTemplateArtifact: App
                TaskDefinitionTemplatePath: taskdef.json
                ApplicationName: !Ref CodeDeployAppName
                DeploymentGroupName: !Ref CodeDeployGrpName
                Image1ArtifactName: BuildOutput
                Image1ContainerName: IMAGE1_NAME
              RunOrder: 1
              InputArtifacts:
                - Name: App
                - Name: BuildOutput
              Region: !Ref AWS::Region
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
  PipelineDev:
    Description: Dev xxxx Pipeline
    Value: !Ref Pipeline
    Export:
      Name: xxxxPipeline
```

このテンプレートには IAM リソースが含まれるので CAPABILITY_IAM を指定する必要がある。

CloudFormationスタックを作成するコマンドは下記になる(phld.sh)。

```phld.sh
#!/bin/bash
CHANGESET_OPTION="--no-execute-changeset"
#if [ $# = 1 ] && [ $1 = "deploy" ]; then
if [ $1 = "deploy" ]; then
echo "deploy mode"
CHANGESET_OPTION=""
fi

CFN_TEMPLATE=$2
CFN_STACK_NAME=xxxx-pipeline
# テンプレートの実⾏
aws cloudformation deploy --stack-name ${CFN_STACK_NAME} --template-file ${CFN_TEMPLATE} ${CHANGESET_OPTION} \
    --capabilities CAPABILITY_NAMED_IAM --parameter-overrides `cat parameters.phld`
```

./phld.sh deploy pholder-pipeline.yml

スタックが作成されたら、ECSのサービス及びタスクを調べて、作成されたコンテナまたはALBのDNS名にがアクセスしてみると、以下のメッセージが表示される。

http://fargate-cluster-alb1-xxxxxx.xxxxxxxx.elb.amazonaws.com

![phld-blue-app.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/5413feb3-424e-8569-ef6e-129a0f4363f5.jpeg)

２. 開発系（グリーン）に切り替え
Dockfileerファイルを変更して、グリーン系のNginxコンテナを作り直す。CloudWatchでDocker変更を検知しデプロイが走る。その後、開発系のコンテナに切り替えます。
コンテナが実行されたらWebBrowserに“Welcome to nginx!...”を表示される。

CodeDeploy の グリーン系への切り替え過程は以下になる。

![phld-deploy-switchover.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/e026db7d-c705-9adc-343a-c67547d6a1bc.jpeg)


３．開発系（グリーン）へのアクセス
開発系のコンテナに切り替え後、再び。ALBのDNS名にがアクセスしてみると、以下のメッセージが表示される。切り替え途中でもALBへのアクセスは一定の確率（例90%）で開発系にルートされたことも確認できる。

http://fargate-cluster-alb1-xxxxxx.xxxxxxxx.elb.amazonaws.com

![phld-green-app.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1657390/faec8767-f393-3458-c0c5-9591f75df554.jpeg)

４．５分間のCodeDeploy　TerminationWaitTimeInMinutes待ってから、ブルー系コンテナが停止され、コンテナにアクセスすると返事がありません。

#（オプション）JSONからapplicationとdeployment groupを作成する

若しECS サービスが既に作成されて、CodeDeploy用のapplicationとdeployment groupが分からない場合は、aws cliからapplicationとdeployment groupを作れる。また、現在のCloudFormation(1.4.0バージョン)では、CodeDeployのapplicationも作れるが、deployment groupは作れません。

ECS Blue/Greenデプロイ用のアプリケーションを作成する。

$ aws deploy create-application \
--application-name AppECS-xxxxxx-service1 \
--compute-platform ECS

若しくは、CloudFormationからDeploy applicationを作る。

```YAML
# Create Code deploy application
  CodeDeployApplication:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: !Ref CodeDeployAppName
      ComputePlatform: ECS
```

上記で作ったアプリケーションに紐付けるデプロイグループを作成する。JSONフォーマットの定義ファイルから作成する。

定義ファイルdeploy-grp-create.json:

```deploy-grp-create.json
{
  "applicationName": "xxxxxxxx-app1",
  "deploymentGroupName": "xxxxxxxx-grp1",
  "deploymentConfigName": "CodeDeployDefault.ECSAllAtOnce",
  "serviceRoleArn": "arn:aws:iam::xxxxxxxxxxxx:role/xxxxxxxx-xxxxxxxx-role",
  "alarmConfiguration": {
    "enabled": false,
    "ignorePollAlarmFailure": false,
    "alarms": []
  },
  "autoRollbackConfiguration": {
    "enabled": true,
    "events": [
      "DEPLOYMENT_STOP_ON_ALARM",
      "DEPLOYMENT_FAILURE",
      "DEPLOYMENT_STOP_ON_REQUEST"
    ]
  },
  "deploymentStyle": {
    "deploymentType": "BLUE_GREEN",
    "deploymentOption": "WITH_TRAFFIC_CONTROL"
  },
  "blueGreenDeploymentConfiguration": {
    "terminateBlueInstancesOnDeploymentSuccess": {
      "action": "TERMINATE",
      "terminationWaitTimeInMinutes": 5
    },
    "deploymentReadyOption": {
      "actionOnTimeout": "CONTINUE_DEPLOYMENT",
      "waitTimeInMinutes": 0
    }
  },
  "loadBalancerInfo": {
    "targetGroupPairInfoList": [
      {
        "targetGroups": [
          {
            "name": "xxx-xxx-tg"
          },
          {
            "name": "xxx-xxx-tg2"
          }
        ],
        "prodTrafficRoute": {
          "listenerArns": [
            "arn:aws:elasticloadbalancing:xxxxxx:xxxxxxxxxxxx:listener/net/xxx-xxx-nlb/0b7c7c53ede509d4/0a8e14e46fa40eae"
          ]
        },
        "testTrafficRoute": {
          "listenerArns": [
            "arn:aws:elasticloadbalancing:xxxxxx:xxxxxxxxxxxx:listener/net/xxx-xxx-nlb/0b7c7c53ede509d4/9ebf467664c0f617"
          ]
        }
      }
    ]
  },
  "ecsServices": [
    {
      "serviceName": "xxx-xxx-service",
      "clusterName": "xxx-xxx-fargate"
    }
  ],
  "tags": [
    {
      "Key": "tag-key",
      "Value": "tag-value"
    }
  ]
}
```

--cli-input-json をオプション利用して定義ファイルを読み込み、デプロイグループを作成する。

$ create-grp.sh
```
＃!/bin/bash
aws deploy create-deployment-group \
    --application-name AppECS-xxxxxx \
    --deployment-group-name DrpECS-xxxxxx \
    --cli-input-json file://deploy-grp-create.json
```

#所感
今回、CodeDeployのプレースホルダ方法を使って、簡単なWebアプリを作るによりCloudFormationのECS Blue/Greenデプロイを構築と検証した。ECS FargateのBule/Green　CodeDeployが動くことを確認できました。CodeDeploy::BlueGreenフックの実現方法により、CodeDeployのプレースホルダ方法は、事前にECSサービスやALB、タスク、コンテナ等を作成しており、又は既存のECSサービスとの連携が可能で、実際の複雑な商用システムでは対応できると思います。