kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Terraform で「最新の」Amazon ECS タスク定義を追跡できる aws_ecs_task_definition の track_latest オプション

Terraform で Amazon ECS タスク定義を作りつつ,アプリケーションのライフサイクルとして GitHub Actions などの「Terraform 以外で」イメージタグを差し替えて Amazon ECS タスク定義を更新(正確には更新ではなくリビジョン追加)する運用を選択することがあると思う.さらにデプロイを繰り返すと使わなくなった Amazon ECS タスク定義が増えるため,定期的に「登録解除 (INACTIVE)」をすることもあると思う.ちなみに2023年2月からは削除もできるようになっているけど〜 \( 'ω')/

track_latest とは

しかし Amazon ECS タスク定義を登録解除すると Terraform でリソースを追跡できず,terraform plan を実行すると Amazon ECS タスク定義を作り直そうとしてしまう.少し前置きは長くなったけど,2024年2月16日にリリースされた Terraform AWS Provider v5.37.0 aws_ecs_task_definition に新しく track_latest オプションが追加された❗️

track_latest - (Optional) Whether should track latest task definition or the one created with the resource. Default is false.

この track_latest を使うと Terraform 側は常に「最新の」Amazon ECS タスク定義を追跡して比較してくれるため,Amazon ECS タスク定義の作り直しがなくなり,運用しやすくなる可能性がある👏

github.com

とは言え実際に試さないとイメージが沸かず,プルリクエストに書いてあるシナリオを参考に試してみた.

github.com

デフォルト or track_latest = false の場合

Step.1

今回はイメージの例として amazonlinux のタグを 2.0.20231218.02.0.20240109.02.0.20240131.0 と更新する流れを試す.まずは Terraform で Amazon ECS タスク定義(リビジョン1)を作る.

resource "aws_ecs_task_definition" "sandbox_track_latest_false" {
  family                   = "sandbox-track-latest-false"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"

  container_definitions = jsonencode([
    {
      name  = "amazonlinux",
      image = "amazonlinux:2.0.20231218.0"
    }
  ])
}

Terraform で Amazon ECS タスク定義(リビジョン1)を作った

Step.2

次は Terraform 以外(今回は AWS CLI を使う)でイメージタグを更新した Amazon ECS タスク定義を2個を作る(リビジョン2・リビジョン3).この状態でアプリケーションとして稼働する最新の Amazon ECS タスク定義は「リビジョン3」となる.

$ aws ecs register-task-definition --cli-input-json fileb://taskdefinition-2.0.20240109.0.json
$ aws ecs register-task-definition --cli-input-json fileb://taskdefinition-2.0.20240131.0.json

📝 taskdefinition-2.0.20240109.0.json

{
    "family": "sandbox-track-latest-false",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "containerDefinitions": [
        {
            "name": "amazonlinux",
            "image": "amazonlinux:2.0.20240109.0"
        }
    ]
}

📝 taskdefinition-2.0.20240131.0.json

{
    "family": "sandbox-track-latest-false",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "containerDefinitions": [
        {
            "name": "amazonlinux",
            "image": "amazonlinux:2.0.20240131.0"
        }
    ]
}

AWS CLI で Amazon ECS タスク定義(リビジョン2・リビジョン3)を作った

Step.3

ここで使わなくなった Amazon ECS タスク定義(リビジョン1・リビジョン2)を登録解除する.

$ aws ecs deregister-task-definition --task-definition sandbox-track-latest-false:1
$ aws ecs deregister-task-definition --task-definition sandbox-track-latest-false:2

Amazon ECS タスク定義(リビジョン1・リビジョン2)を登録解除した

Step.4

最後に terraform plan を実行すると Amazon ECS タスク定義を作り直そうとする😨 ちなみに個人用の Terraform Cloud のキャプチャを載せている.

terraform plan を実行した

track_latest = true の場合

Step.1

基本的には同じだけど,名前を sandbox-track-latest-true に変更して,track_latest = true も設定した.まずは Terraform で Amazon ECS タスク定義(リビジョン1)を作る.

resource "aws_ecs_task_definition" "sandbox_track_latest_true" {
  family                   = "sandbox-track-latest-true"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  track_latest             = true

  container_definitions = jsonencode([
    {
      name  = "amazonlinux",
      image = "amazonlinux:2.0.20231218.0"
    }
  ])
}

Step.2

Step.2 も基本的には同じ.Amazon ECS タスク定義を2個作る(リビジョン2・リビジョン3).

$ aws ecs register-task-definition --cli-input-json fileb://taskdefinition-2.0.20240109.0.json
$ aws ecs register-task-definition --cli-input-json fileb://taskdefinition-2.0.20240131.0.json

📝 taskdefinition-2.0.20240109.0.json

{
    "family": "sandbox-track-latest-true",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "containerDefinitions": [
        {
            "name": "amazonlinux",
            "image": "amazonlinux:2.0.20240109.0"
        }
    ]
}

📝 taskdefinition-2.0.20240131.0.json

{
    "family": "sandbox-track-latest-true",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "containerDefinitions": [
        {
            "name": "amazonlinux",
            "image": "amazonlinux:2.0.20240131.0"
        }
    ]
}

Step.3

ここで使わなくなった Amazon ECS タスク定義(リビジョン1・リビジョン2)を登録解除する.

$ aws ecs deregister-task-definition --task-definition sandbox-track-latest-true:1
$ aws ecs deregister-task-definition --task-definition sandbox-track-latest-true:2

Amazon ECS タスク定義(リビジョン1・リビジョン2)を登録解除した

Step.4

track_latest = true を設定してると Step.4 から挙動が変わってくる💡ここで terraform plan を実行すると Amazon ECS タスク定義を作り直しではなく「最新の」Amazon ECS タスク定義を追跡して比較してくれる.実際に containerDefinitions.image の値を最新の amazonlinux:2.0.20240131.0 から,Terraform コードで指定している amazonlinux:2.0.20231218.0 に戻すような差分が出ている❗️

terraform plan を実行した

次に Terraform コードを修正して最新の Amazon ECS タスク定義と一致させる📝

resource "aws_ecs_task_definition" "sandbox_track_latest_true" {
  family                   = "sandbox-track-latest-true"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "256"
  memory                   = "512"
  track_latest             = true

  container_definitions = jsonencode([
    {
      name  = "amazonlinux",
      image = "amazonlinux:2.0.20240131.0"
    }
  ])
}

もう一度 terraform plan を実行すると No changes で差分なしになる👌

なるほどー \( 'ω')/

terraform plan を実行した

まとめ

2024年2月16日にリリースされた Terraform AWS Provider v5.37.0 で追加された aws_ecs_task_definition の新しいオプション track_latest オプションを試してみた❗️ドキュメントを読むだけではイメージが沸かず,実際に試してみて良かった.とは言え track_latest = true を設定するとアプリケーションを頻繁にデプロイするときに変更の追随も大変そうなので ignore_changes で変更を無視する運用も引き続き残りそう.まずは track_latest = true の存在を覚えておこうー💡