AWS ECS + CodeDeploy + CodePipeline ( + ちょっとTerraform )  – デプロイ方法を ローリングアップデートからblue/greenデプロイに変える時のまとめ

ECSへのデプロイ方法をローリングアップデートからblue/greenに変える手順を記す

# 事前確認

ローリングアップデートとblue/greenデプロイのどちらを使っているかの確認

- ECSへの直接デプロイはローリングアップデート
- CodeBuildを利用したデプロイはblue/greenデプロイ

主にこの構成のようだ。(2023/08/18現在)

デプロイ時にCodeDeployの履歴に何も増えなければ恐らくECSへの直接デプロイがされてるはず。


# ターゲットグループを作る

特にblue用とgreen用のターゲットグループがあるというわけではなく
blue/greenデプロイでは2個のターゲットグループがスイッチングされる

- ALBのリスナールールに紐づくターゲットグループが更新される
- ECSに紐づくターゲットグループが更新される

という動作のようだ。

なので既にローリングアップデートしている場合は利用しているターゲットグループが1個はあるはずなので、それと同じようなターゲットグループをもう1個作れば良い。

2個のターゲットグループは同じALBに紐づくようにしておく。


# サービスロールの作成

AWSCodeDeployRoleForECS のセット(？)でロールを作っておけば良いようだ

<img width="1069" alt="image" src="https://github.com/YumaInaura/YumaInaura/assets/13635059/5220c58a-635e-4e56-88b1-f5696f0cb590">

JSONを見てみると次の通り

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ecs:DescribeServices",
                "ecs:CreateTaskSet",
                "ecs:UpdateServicePrimaryTaskSet",
                "ecs:DeleteTaskSet",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:ModifyRule",
                "lambda:InvokeFunction",
                "cloudwatch:DescribeAlarms",
                "sns:Publish",
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "iam:PassRole"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "iam:PassedToService": [
                        "ecs-tasks.amazonaws.com"
                    ]
                }
            }
        }
    ]
}
```

# CodeDeployでデプロイアプリを作る

コンピューティングタイプ : Amazon ECS

# デプロイグループを作る

## Web Console の場合

- ECSのクラスタやサービス
- ECSで利用しているALB
- 2個のターゲットグループ

などを指定していく

この
サービスロールは事前に作っておけば選択肢に出てくる

<img width="877" alt="image" src="https://github.com/YumaInaura/YumaInaura/assets/13635059/c274608a-2bdb-4f0b-bc6a-7753fe6809b5">


ポートの指定は本番稼働用とテスト用で別のものを選ぶ
ALBのリスナールールに少なくとも2種類のポートのルールがなければいけないかもしれない

## Terraformの場合

サンプルのコードを参考に組み立てる

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codedeploy_deployment_group.html

```tf
resource "aws_codedeploy_app" "example" {
  compute_platform = "ECS"
  name             = "example"
}

resource "aws_codedeploy_deployment_group" "example" {
  app_name               = aws_codedeploy_app.example.name
  deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
  deployment_group_name  = "example"
  service_role_arn       = aws_iam_role.example.arn

  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }

  blue_green_deployment_config {
    deployment_ready_option {
      action_on_timeout = "CONTINUE_DEPLOYMENT"
    }

    terminate_blue_instances_on_deployment_success {
      action                           = "TERMINATE"
      termination_wait_time_in_minutes = 5
    }
  }

  deployment_style {
    deployment_option = "WITH_TRAFFIC_CONTROL"
    deployment_type   = "BLUE_GREEN"
  }

  ecs_service {
    cluster_name = aws_ecs_cluster.example.name
    service_name = aws_ecs_service.example.name
  }

  load_balancer_info {
    target_group_pair_info {
      prod_traffic_route {
        listener_arns = [aws_lb_listener.example.arn]
      }

      target_group {
        name = aws_lb_target_group.blue.name
      }

      target_group {
        name = aws_lb_target_group.green.name
      }
    }
  }
}
```

# デプロイ設定

AWSに最初からデプロイ設定のひながたが数種類あるので特に自分では設定しなくてOK

# ECSサービスのデプロイタイプを変える

## WebConsoleの場合

単に設定を変えるということは出来なさそう。
ECSサービス作成時にはデプロイタイプでブルー/グリーンが選べるのでいちどECSサービスを削除してから再度作成するのが良さそう。
(ちなみに新しいUIだとローリングアップデートしか選べなくなっているようだ)

## Terraformの場合

デプロイタイプを変えて terraform apply をするとECSサービスが破棄・再作成されるようなので注意

blue/greenデプロイの場合はECSサービスに紐づくタスク定義の更新が出来なくなるようなのでタスク定義の変更をignoreしておく
ロードバランサも
(本当にこの対処で良いのかは調べきれていないが)

```tf
resource "aws_ecs_service" "default" {

  ...

  deployment_controller {
    type = "CODE_DEPLOY"
  }

  lifecycle {
    ignore_changes        = [task_definition, load_balancer]
  }
}

```

# CodePipelineのDeployステージの設定を変える

## Web Consoleの場合

アクションプロバイダを Amazon ECS (ブルー/グリーン)に変える
 あとはテストアプリケーション・テストグループなどを選んで行く

今回の例ではビルドステージを使っているので入力にはBuildのアーティファクトを選んでいる

<img width="1440" alt="image" src="https://github.com/YumaInaura/YumaInaura/assets/13635059/c3e452cb-506e-470c-937c-00b698d33b40">

## Terraformの場合

ビルドステージをDeployの入力とする場合は次のような感じ


```tf

  stage {
    name = "Deploy"

    action {
      ... いろいろ省略 ...

      provider        = "CodeDeployToECS"
      input_artifacts = ["Build"]

      configuration = {
        ApplicationName = "deploy-app"
        DeploymentGroupName = "deploy-group"

        Image1ArtifactName = "Build"
        Image1ContainerName = "IMAGE1_NAME"
        TaskDefinitionTemplateArtifact = "Build"
        TaskDefinitionTemplatePath = "taskdef.json"
        AppSpecTemplateArtifact = "Build"
        AppSpecTemplatePath = "appspec.yml"
      }

    }
  }
```



# appspec.yml を追加する

レポジトリのトップディレクトリに appspec.yml または appspec.json を設定する

`<TASK_DEFINITION>` の部分はプレースホルダでCodeDeployがタスク定義のARNに変えてくれる

ContainerName / ContainerPort は既存のタスク定義を参照するなりして確認すればOK

```
version: '0.0'
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: container-name
          ContainerPort: 80
```

ちなみにファイル名はCodePipelineのDeployステージの設定で変更可能

<img width="1047" alt="image" src="https://github.com/YumaInaura/YumaInaura/assets/13635059/d8173636-6174-40d4-a13f-3963a49be04d">


# taskdef.json を作成する

レポジトリのトップにファイルを置いておく

既存のタスク定義の JSON をひな形にして作るのがやりやすい。
(AWS Web Console でもタスク定義画面からJSONが取れるはず)

ただ出力されたJSONと入力に使うJSONは異なるので修正する

- registeredAt
- registeredBy
- requiresAttributes
- status
- revision

とかは不要な項目のようだ

あとtags項目があるとエラーが起こるので自分が試した時は消しておいた

image はCodePipelineで指定したプレースホルダにする

```json
{
    "containerDefinitions": [
        {
            "name": "container-name",
            "image": "<IMAGE1_NAME>",
```

ちなみにlocalファイルを指定してタスク定義登録をしようとすると不要項目がある場合は教えてくれるので試しても良い

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

## どうやってタスク定義の値を動的に変えるのか？

イメージのプレースホルダ以外は動的に変わらなそう。
調べると buildステージ ( buildspec.yml ) でsedコマンドを使うなりして環境に応じて無理やりファイル内容を変えてしまう方法が紹介されていた。

参考:

ECS Fargateデプロイ用CodePipelineのtaskdef.jsonに変数を持たせる方法
https://qiita.com/neruneruo/items/a5313beb3074a1af4df2

他には環境ごとにtaskdef.jsonを用意しておくなどやり方は色々あるだろうが

# buildspec.yml を修正する

- ECS直接デプロイでは `imagedefinitions.json` を作成してたがこれを `imageDetail.json` に変える
- appspec をDeployステージ用の出力に含める
- appspec をDeployステージ用の出力に含める

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


# いざ実行

- ECSサービスのサービス更新を試す
- ソースレポジトリ (Githubとか) に push する

などして動作を試す


# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# Twitter

https://twitter.com/YumaInaura