CloudFormationテンプレートを1からしっかり理解しながらECS on Fargateなアプリを自動構築する(中編)

# 前提条件
[前編](https://qiita.com/neruneruo/items/06b073f3846803f415e5)で作ったECSのサービスに対してCodePipelineを使ってCI/CDパイプラインを作るので、前編を理解しておく。

記事中には、備忘のためにリファレンスに書かれていないデフォルト値を整理しておくが、2020年3月時点の情報であり、後でAWSが仕様を変えたとしても追従する予定はないので、挙動が違ったらリファレンスを見直してほしい。あと、今回の構成(ECS on FargateのBlue/Greenデプロイメント)以外の構成以外のデフォルト値まで調査はしていないのであしからず。

# CodeDeployでBlue/Greenデプロイメントのアプリケーションを作る
## アプリケーションの設定
ここはそんなに悩むことはない。以下のような感じで設定しておけばOK。

```YAML
  APPLICATION: 
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName: CFn-test-Application
      ComputePlatform: ECS
```

各プロパティのデフォルト値は以下。

| プロパティ | デフォルト値 |
|:----------|:------------|
| ApplicationName | AWS払い出しの名前 ※リファレンス記載 |
| ComputePlatform | EC2/オンプレミス |

## デプロイメントグループの準備

ちなみに、デフォルト値の確認をしていたところ、DeploymentStyle のプロパティを設定しないと、なぜか

```
For ECS deployment group, ec2TagFilters can not be specified (Service: AmazonCodeDeploy; Status Code: 400; Error Code: InvalidEC2TagException; Request ID: ...)
```

なエラーが出た。謎。```BLUE_GREEN``` を設定すれば解消するので深く追跡はしない。

例によって各プロパティのデフォルト値は以下。

| プロパティ | デフォルト値 |
|:----------|:------------|
| AlarmConfiguration | 無し |
| AutoRollbackConfiguration | よく分からない… |
| AutoScalingGroups | 無し |
| Deployment | よく分からない… |
| DeploymentConfigName | なし？ |
| DeploymentGroupName | AWS払い出しの名前 ※リファレンス記載 |
| Ec2TagFilters | Fargateでは対象外 |
| Ec2TagSet | Fargateでは対象外 |
| LoadBalancerInfo | ECSのBlue/Greenデプロイメントでは必須 |
| OnPremisesInstanceTagFilters | Fargateでは対象外 |
| OnPremisesTagSet | Fargateでは対象外 |
| TriggerConfigurations | なし |

## ここで問題が……
ここから先、どう頑張ってもエラーが出てしまう。
なんと、ユーザーガイドを見ると

>Blue/Green デプロイタイプでは、AWS CloudFormation は、Lambda コンピューティングプラットフォームでのデプロイのみをサポートします。

とか書いてある。ガーン……このままではCloudFormationによる全自動化ができないのか……。

ここまでやって諦めてしまうのは勿体ないので、一部だけ半自動にする。
作りたかったアプリケーションとデプロイメントグループの定義をCLIをラップしたシェルにして…

```shell
#!/bin/sh

APPLICATION_NAME=CFn-test-Application
DEPLOYMENT_GROUP_NAME=CFn-test-DeploymentGroup
DEPLOYMENT_GROUP_JSON=./CLI_createDeploymentGroup.json

aws deploy create-application --application-name ${APPLICATION_NAME} --compute-platform ECS

aws deploy create-deployment-group --application-name ${APPLICATION_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_NAME} --cli-input-json file://${DEPLOYMENT_GROUP_JSON}

```

```JSON
{
  "applicationName": "CFn-test-Application",
  "deploymentGroupName": "CFn-test-DeploymentGroup",
  "serviceRoleArn": "arn:aws:iam::[アカウントID]:role/ecsCodeDeployRole",
  "deploymentStyle": {
    "deploymentType": "BLUE_GREEN",
    "deploymentOption": "WITH_TRAFFIC_CONTROL"
  },
  "blueGreenDeploymentConfiguration": {
    "terminateBlueInstancesOnDeploymentSuccess": {
      "action": "TERMINATE",
      "terminationWaitTimeInMinutes": 30
    },
    "deploymentReadyOption": {
      "actionOnTimeout": "CONTINUE_DEPLOYMENT",
      "waitTimeInMinutes": 0
    }
  },
  "loadBalancerInfo": {
    "targetGroupPairInfoList": [
      {
        "targetGroups": [
          {
            "name": "CFn-test-ALB-TG1"
          },
          {
            "name": "CFn-test-ALB-TG2"
          }
        ],
        "prodTrafficRoute": {
          "listenerArns": [
            "[CFn-test-ALBの本番用リスナーのArn]"
          ]
        },
        "testTrafficRoute": {
          "listenerArns": [
            "[CFn-test-ALBテスト用リスナーのArn]"
          ]
        }
      }
    ]
  },
  "ecsServices": [
    {
      "serviceName": "CFn-test-ECSService",
      "clusterName": "CFn-test-ECSCluster"
    }
  ]
}
```

こうだ！このシェルを実行して、アプリケーションを作成してからCodeDeployでデプロイを作成してみると、ちゃんとテストポートを使ったBlue/Greenデプロイメントが発動する！

あとは、これをCodePipelineに組み込むのみ！
ということで、[後編](https://qiita.com/neruneruo/items/0f27dfc1117b404be8df)に続く。

## 追記
↑のままだと、「半自動」で、何が面倒臭いって、前段でCloudFormationテンプレートで作ったALBのリスナーのARNを、スタックを作り直す都度いちいち書き換えないといけなくいところ。
手動によるミスのリスクは極力減らすべきなのである！

というわけで、前段で作ったALBの属性情報からARNを自動で取得することを考える。

シェルを実行するサーバで、

```
$ sudo yum jq install
```

して、JSONを編集できるようにしておこう。

また、前回作ったCloudFormationテンプレートの Outputs に、

```YAML
  Listener1:
    Description: ALB Listener Arn 1
    Value: !Ref ALBLISTENER1
    Export:
      Name: !Sub ${AWS::StackName}-ListenerArn1
  Listener2:
    Description: ALB Listener Arn 2
    Value: !Ref ALBLISTENER2
    Export:
      Name: !Sub ${AWS::StackName}-ListenerArn2
```

を追加しておく。こうしておくことで、

```
$ aws cloudformation describe-stacks --stack-name [スタック名] | jq -r '.Stacks[].Outputs | map(select(.ExportName=="[スタック名]-ListenerArn1"))[] | .OutputValue'
```

すればリスナー名が取れるのだ。
※jqコマンドの使い方はここの本筋ではないので、[このあたりの記事](https://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af)を読んでおいてもらえれば。

そんな感じで

```shell
#!/bin/sh

############
# Settings #
############
STACK_NAME=CFn-test
ALBLISTENER_NAME1=${STACK_NAME}-ListenerArn1
ALBLISTENER_NAME2=${STACK_NAME}-ListenerArn2
APPLICATION_NAME=${STACK_NAME}-Application
DEPLOYMENT_GROUP_NAME=${STACK_NAME}-DeploymentGroup
DEPLOYMENT_GROUP_JSON=./CLI_createDeploymentGroup.json

###################
# Replace ALB ARN #
###################
aws configure set output json
ALBLISTENER_ARN1=`aws cloudformation describe-stacks --stack-name ${STACK_NAME} | jq -r '.Stacks[].Outputs | map(select(.ExportName=="'${ALBLISTENER_NAME1}'"))[] | .OutputValue'`
ALBLISTENER_ARN2=`aws cloudformation describe-stacks --stack-name ${STACK_NAME} | jq -r '.Stacks[].Outputs | map(select(.ExportName=="'${ALBLISTENER_NAME2}'"))[] | .OutputValue'`
echo '{
  "applicationName": "CFn-test-Application",
  "deploymentGroupName": "CFn-test-DeploymentGroup",
  "serviceRoleArn": "arn:aws:iam::[アカウントID]:role/ecsCodeDeployRole",
  "deploymentStyle": {
    "deploymentType": "BLUE_GREEN",
    "deploymentOption": "WITH_TRAFFIC_CONTROL"
  },
  "blueGreenDeploymentConfiguration": {
    "terminateBlueInstancesOnDeploymentSuccess": {
      "action": "TERMINATE",
      "terminationWaitTimeInMinutes": 30
    },
    "deploymentReadyOption": {
      "actionOnTimeout": "CONTINUE_DEPLOYMENT",
      "waitTimeInMinutes": 0
    }
  },
  "loadBalancerInfo": {
    "targetGroupPairInfoList": [
      {
        "targetGroups": [
          {
            "name": "CFn-test-ALB-TG1"
          },
          {
            "name": "CFn-test-ALB-TG2"
          }
        ],
        "prodTrafficRoute": {
          "listenerArns": [
            "'${ALBLISTENER_ARN1}'"
          ]
        },
        "testTrafficRoute": {
          "listenerArns": [
            "'${ALBLISTENER_ARN2}'"
          ]
        }
      }
    ]
  },
  "ecsServices": [
    {
      "serviceName": "CFn-test-ECSService",
      "clusterName": "CFn-test-ECSCluster"
    }
  ]
}' > ${DEPLOYMENT_GROUP_JSON}

###################
# execute AWS CLI # 
###################
aws deploy create-application --application-name ${APPLICATION_NAME} --compute-platform ECS
aws deploy create-deployment-group --application-name ${APPLICATION_NAME} --deployment-group-name ${DEPLOYMENT_GROUP_NAME} --cli-input-json file://${DEPLOYMENT_GROUP_JSON}
```

これにより、パラメータ以外の部分は触らずほぼ自動で動作するようになった。