ECS/FargateでBlue/Greenデプロイをしてみた

# はじめに
* [チュートリアル](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-blue-green.html)をなぞりながらECS/FargateでBlue/Greenデプロイをしてみたときのメモです。

# デプロイ
## 構成イメージ
* ALB
    * [Application Load Balancer components](https://docs.amazonaws.cn/en_us/elasticloadbalancing/latest/application/introduction.html)から引用
![スクリーンショット 2022-08-06 14.43.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92368/01a2f902-e46d-1853-b339-6e7adf58cfc7.png)

* ECS
    * [terraform-aws-ecs-task-sequential-execution](https://github.com/voyagegroup/terraform-aws-ecs-task-sequential-execution)から引用
![スクリーンショット 2022-08-06 14.54.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92368/a0e7b5af-aa4b-ef5d-e0fc-639def2f3db4.png)

## 手順
### 1. 事前構築手順
#### 1-1. ネットワークの構築
* Terraformで作成
* VPCの作成
    * DNSホスト名有効化（エンドポイントのため）
* サブネットの作成
* インターネットゲートウェイの作成
    * -> ルートテーブルへ追加する
* エンドポイント(Interface)の作成
    * -> エンドポイント用のSGを作成する
    * ecr.dkr
    * ecr.api
    * logs
* エンドポイント(Gateway)の作成
    * -> ルーティングテーブルへ追加する
    * s3

#### 1-2. ALBの構築
* Terraformで作成
* ALB用のSG作成
* ALBの作成
* ターゲットグループの作成
    * 2つ作成する（blue用とgreen用）
    * このターゲットグループは、サービスで設定されている元のタスクにトラフィックをルーティングする
    * 後続で作成するECSサービスで、このターゲットグループを指定する
* リスナーの作成
    * ターゲットグループにリクエストを転送するデフォルトルールが関連付けられた、ロードバランサーのリスナーを作成する

#### 1-3. ECRの構築
* Terraformで作成
* リポジトリの作成
    * タグのイミュータビリティを有効化
        * 同じタグでpushしても更新されない
    * プッシュ時にスキャン
* 補足
    * ecs cliの場合push時の作成もできるが、管理上+リポジトリ設定のため、Terraformで構築する

#### 1-4. ECSクラスターの構築
* Terraformで作成
* Clusterの作成
    * Fargateの場合 -> 「ネットワーキングのみ」
* 補足
    * Cluster: TaskとServiceをグルーピングする概念。アプリケーションごと、環境ごとに用意する。

#### 1-5. ECSサービスの構築
* Terraformで作成
* ECSサービス用のSG（ALBのSGからのインバウンド）
* ECSサービスの作成
    * 初回は先にタスク定義ファイルの登録が必要？（「タスク定義ファイルの更新と登録」）
    * タスク定義バージョンの指定
    * タスクの数の指定( `desired-count` )
* ex. CLIで作成する場合

```json:ecs-service.json
{
    "cluster": "ecs-example-cluster",
    "serviceName": "ecs-example-service",
    "taskDefinition": "ecs-task-def",
    "loadBalancers": [
        {
            "targetGroupArn": "arn:aws:elasticloadbalancing:region:aws_account_id:targetgroup/ecs-example-tg1/XXXXXXXX",
            "containerName": "ecs-example",
            "containerPort": 80
        }
    ],
    "launchType": "FARGATE",
    "schedulingStrategy": "REPLICA",
    "deploymentController": {
        "type": "CODE_DEPLOY"
    },
    "platformVersion": "LATEST",
    "networkConfiguration": {
       "awsvpcConfiguration": {
          "assignPublicIp": "ENABLED",
          "securityGroups": [ "sg-abcd1234" ],
          "subnets": [ "subnet-abcd1234", "subnet-abcd5678" ]
       }
    },
    "desiredCount": 2
}
```

```bash
# 保留中でサービスが作成される
$ aws ecs create-service --cli-input-json file://ecs-service.json
```

* 補足
    * Serviceを作成せずTaskを実行することも可能（バッチ処理など）
    * Service: 指定したtaskDefinitionからtaskを生成する
    * Serviceで定義するもの: ネットワーク、VPC、タスク定義
    * ネットワークモードで awsvpc を使う場合はプライベートサブネットを使う
    * ECS Serviceを作成する際に「deployment_controller」の設定で「type = "CODE_DEPLOY"」を指定する

#### 1-6. CodeDeploy Applicationの構築
* Terraformで作成
* ex. CLIで作成する場合

```bash
aws deploy create-application \
     --application-name ecs-example-deploy-application \
     --compute-platform ECS
```

#### 1-7. CodeDeploy Deployment Groupの構築
* Terraformで作成
* Deployment Groupを作成する
    * Blue/Greenを指定
    * ターゲットグループ（Blue/Green）とリスナーを指定
    * その他、切り替え方式などを指定
* ex. CLIで作成する場合
    * CLIを実行するIAMの権限に注意（code deploy, pass roleが必要）


```json:deployment-group.json
{
   "applicationName": "ecs-example-deploy-application",
   "autoRollbackConfiguration": {
      "enabled": true,
      "events": [ "DEPLOYMENT_FAILURE" ]
   },
   "blueGreenDeploymentConfiguration": {
      "deploymentReadyOption": {
         "actionOnTimeout": "CONTINUE_DEPLOYMENT",
         "waitTimeInMinutes": 0
      },
      "terminateBlueInstancesOnDeploymentSuccess": {
         "action": "TERMINATE",
         "terminationWaitTimeInMinutes": 5
      }
   },
   "deploymentGroupName": "ecs-example-deploy-group",
   "deploymentStyle": {
      "deploymentOption": "WITH_TRAFFIC_CONTROL",
      "deploymentType": "BLUE_GREEN"
   },
   "loadBalancerInfo": {
      "targetGroupPairInfoList": [
        {
          "targetGroups": [
             {
                 "name": "ecs-example-tg1"
             },
             {
                 "name": "ecs-example-tg2"
             }
          ],
          "prodTrafficRoute": {
              "listenerArns": [
                  "arn:aws:elasticloadbalancing:region:aws_account_id:listener/app/ecs-example-alb/e5ba62739c16e642/665750bec1b03bd4"
              ]
          }
        }
      ]
   },
   "serviceRoleArn": "arn:aws:iam::aws_account_id:role/ecsCodeDeployRole",
   "ecsServices": [
       {
           "serviceName": "ecs-example-service",
           "clusterName": "ecs-example-cluster"
       }
   ]
}
```

```bash
$ aws deploy create-deployment-group --cli-input-json file://deployment-group.json
```

### 2. CI/CD手順
#### 2-1. ビルド実行
* GitLabRunnerなどで、コードのマージをトリガーに、①コンテナのビルドとECRへのpush+②タスク定義ファイルの更新と登録を実施する

##### 2-1-1. コンテナのビルドとECRへのpush

```bash
# 手動の場合
# イメージビルド
# もしもどうしても環境ごとに変数を変えたい場合は、ARGインストラクションを使う（参考: [Use argument in Dockerfile](https://blog.lorentzca.me/use-argument-in-dockerfile/) ）
$ docker build ./ -t ecs-example

# （参考）ローカルで確認
$ docker run --name hogehoge ecs-example -p 8080:80

# ECRにログイン
$ aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin XXX.dkr.ecr.us-east-2.amazonaws.com

# タグ付け（ローカルで）
# commit hash値を使う
$ docker tag ecs-example:latest XXX.dkr.ecr.us-east-2.amazonaws.com/ecs-example:v1

# ECRにプッシュ
$ docker push　XXX.dkr.ecr.us-east-2.amazonaws.com/ecs-example:v1
```

##### 2-2-2. タスク定義ファイルの更新と登録
* タスク定義ファイルの更新
    * 基本的には `image` のtagを更新していく

```json:ecs-task-def.json
{
  "family": "ecs-task-def",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecs-exec-role",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [ "FARGATE" ],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "ecs-example",
      "image": "XXX.dkr.ecr.us-east-2.amazonaws.com/ecs-example:v1",
      "essential": true,
      "memory": 384,
      "memoryReservation": 128,
      "portMappings": [
        {
          "containerPort": 80
        }
      ]
    }
  ]
}
```

* タスク定義ファイルの登録
    * `file://` を忘れない

```bash
aws ecs register-task-definition --cli-input-json file://ecs-task-def.json
```

##### 2-2-3. appspec.yamlファイルの更新とS3アップロード
* appspec.yamlの更新
    * タスク定義のバージョン情報を更新する

```yaml:appspec.yaml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "arn:aws:ecs:region:aws_account_id:task-definition/ecs-task-definition:7"
        LoadBalancerInfo:
          ContainerName: "ecs-example"
          ContainerPort: 80
        PlatformVersion: "LATEST"
```

* appspec.yamlのアップロード

```bash
$ aws s3 cp ./appspec.yaml s3://deploy-bucket/appspec.yaml
```

* 補足
    * Task: コンテナの集合体
    * TaskDefinition: docker-compose的なもの
    * TaskDefinitionで定義するもの: タスクメモリ、CPU、コンテナ1のイメージURI・ポートマッピング・ログ設定
    * タスク定義パラメータは[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html)を参照

#### 2-2. デプロイ実行
* 事前にcreate-deployment.jsonを作成しておく（更新不要）

```json
{
    "applicationName": "ecs-example-deploy-application",
    "deploymentGroupName": "ecs-example-deploy-group",
    "revision": {
        "revisionType": "S3",
        "s3Location": {
            "bucket": "deploy-bucket",
            "key": "appspec.yaml",
            "bundleType": "YAML"
        }
    }
}
```

* CodeDeployのデプロイを作成する（デプロイ実行）

```bash
$ aws deploy create-deployment \
     --cli-input-json file://create-deployment.json
```

#### 2-3. 動作確認
* テストポートで動作確認する

#### 2-4. Blue/Green切り替え
* 確認が完了したらCodeDeploy画面で『トラフィックの再ルーティング』を実行する。→ロールバックの必要がないと判断したら『元のタスクセットの終了』を選択する。

# 参考
* [AWSコンテナ設計・構築[本格]入門](https://www.amazon.co.jp/AWS%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E8%A8%AD%E8%A8%88%E3%83%BB%E6%A7%8B%E7%AF%89-%E6%9C%AC%E6%A0%BC-%E5%85%A5%E9%96%80-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BE%E9%87%8E%E6%9D%91%E7%B7%8F%E5%90%88%E7%A0%94%E7%A9%B6%E6%89%80/dp/4815607656)
* [チュートリアル: Blue/Green デプロイを使用するサービスの作成](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-blue-green.html)
* [CodeDeployを利用したECSのデプロイをCLIから試す](https://dev.classmethod.jp/articles/codedeploy-ecs-from-cli/)