【Terraform】ECS のタスク定義リソース (aws_ecs_task_definition) にある track_latest オプションを理解する

記事タイトルとURLをコピーする

こんにちは🐱
カスタマーサクセス部の山本です。

興味深い機能を教えてもらったので、調査してみました!!

Terraform で管理している AWS 環境における、 ECS のタスク定義

ECS サービスや ECS タスクのデプロイは、他の AWS サービス、例えば CodePipeline などを利用して行う場合があります。
参考:チュートリアル: を使用した Amazon ECS 標準デプロイ CodePipeline - AWS CodePipeline

その際に、ECS のタスク定義は、Terraform のコード(tf) 以外の方法で更新されることになります。
初期構築時に ECS のタスク定義を Terraform のコード(tf) で作成したものの、後から CodePipeline などでデプロイパイプラインを作って、それ以降の更新は パイプライン が担うという場合です。
そういった場合に、Terraform のコード(tf)では、ignore_changes 属性を ECS タスク定義に付与し、terraform apply の更新対象から外すと良いでしょう。そのように運用しているケースも多いかと思います。
ignore_changes 属性を付与することで、Terraform apply した場合にも、タスク定義は更新しません。

ignore_changes に関する参考記事もあるので参照ください。
【Terraform】plan / apply と状態ファイル (tfstate) の関係 - サーバーワークスエンジニアブログ

コード例:

resource "aws_ecs_task_definition" "latest-trial" {
  family                   = "task1"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 2048
  memory                   = 4096

  container_definitions = jsonencode([
    {
      name       = "task"
      image      = "public.ecr.aws/nginx/nginx:1.25"
      essenntial = "true"
      portMappings = [{
        name          = "nginx-80-tcp"
        containerPort = 80
        protocol      = "tcp"
        appProtocol   = "http"
      }]
    }
  ])
  lifecycle { ignore_changes = [container_definitions]}
}

lifecycle { ignore_changes = [container_definitions]}

この部分で、タスク定義内のコンテナ定義 ( container_definitions ) (7-19行目)が、terraform apply の更新対象から外れるように記載しています。

ignore_changes 属性を使った際に困ること

aws_ecs_task_definition に関する issue を読むと、「パイプラインなど、Terraform のコード(tf) 以外の方法で更新した ECS タスク定義を Terraform にも認識させたい」という意見が多く見られました。

例1 : r/aws_ecs_task_definition: add track_latest attribute by GerardSoleCa · Pull Request #30154 · hashicorp/terraform-provider-aws · GitHub

例2 : aws_ecs_task_definition and continuous delivery to ecs · Issue #632 · hashicorp/terraform-provider-aws · GitHub

確かに、ignore_changes 属性を付与すると「パイプラインなど、Terraform のコード(tf) 以外の方法で更新を行うことになったため、Terraform のコード(tf) では ECS タスク定義のリソースを管理しない。」ということになります。
コードを用いてITインフラを管理する(IaC の)目的は、必要に応じて環境を簡単に再作成するためだったり、構成の同じ新しい環境を迅速にデプロイするためです。
Terraform で管理しないインフラリソースがあると、そのリソースは別の方法で作成することになり、インフラ環境を再作成する時の品質や、新しいインフラ環境の迅速なデプロイに影響が出ます。

Terraform のコード(tf) 以外の方法で更新した ECS タスク定義について、 Terraform に認識させる track_latest オプション

そもそも ECS のタスク定義はどのように更新されるか

ECS のタスク定義で既存のリビジョンの設定内容を変更するには、新しいリビジョンを作成する必要があります。
これは Terraform でも、CodePipeline でも、マネジメントコンソールでも同じです。
例えば、CodePipeline で Blue/Green デプロイを構成している場合には、新しいリビジョンのタスク定義を使ってデプロイします。もし、ロールバックを行う場合には、以前のタスク定義のリビジョンに戻すという方式をとります。
文字通り、リビジョンを管理できるので、ECSサービスやタスクの状態を戻したり、次の状態に進めるのに便利です。

ECS の タスク定義 (aws_ecs_task_definition) を plan / apply した時のデフォルト動作

Terraformでは、コード (tf) に記載した 1 つのリソース (resource) と、Terraform が環境上に作成したリソースを 1 対 1 でマッピングして、状態管理しています。
そのため、terraform plan では、「コード (tf)」 と「実環境上にある、Terraform で作成した タスク定義のリビジョン」の差分を比較し、コード (tf)を適用する計画を作ります
差分がある場合、terraform apply では、「実環境上にある、Terraform で作成した タスク定義のリビジョン」を削除し、「コード (tf)」を元に新しいリビジョンを作成します。
このように状態管理をしているため、「Terraform のコード(tf) 以外の方法で更新した ECS タスク定義のリビジョン」を、 Terraform に認識させることはできません。

(今回のアップデート)ECS のタスク定義 (aws_ecs_task_definition) に追加になった track_latest オプション

2024/2/15 に aws_ecs_task_definition リソースに、track_latest オプションが追加になりました。

参考:terraform-provider-aws/CHANGELOG.md at main · hashicorp/terraform-provider-aws · GitHub

resource/aws_ecs_task_definition: Add track_latest argument

track_latest = true を指定すると、terraform plan の実行時には、
「コード (tf)」と 「実環境上にある 同じ ECS のタスク定義の最新リビジョン」 を比較します。

参考:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/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 オプションを試す

以下、track_latest オプションを有効にしたサンプルコードです。track_latest = true

resource "aws_ecs_task_definition" "latest-trial" {
  family                   = "task1"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 2048
  memory                   = 4096

  container_definitions = jsonencode([
    {
      name       = "task"
      image      = "public.ecr.aws/nginx/nginx:1.25"
      essenntial = "true"
      portMappings = [{
        name          = "nginx-80-tcp"
        containerPort = 80
        protocol      = "tcp"
        appProtocol   = "http"
      }]
    }
  ])
  track_latest = true
  # lifecycle { ignore_changes = [container_definitions]}
}

上記のサンプルコードを用いて、 cpu = 2048 、memory = 4096 のタスク定義を作成しました。

また、「Terraform のコード(tf) 以外の方法で更新した ECS タスク定義のリビジョン」を作ります。
サンプルコードで作成したタスク定義から、AWS マネジメントコンソールcpu = 512 、memory = 1024新しいリビジョンを作りました。

この状態で terraform plan を実行すると、「実環境上にある 同じ ECS のタスク定義の最新リビジョン」 である cpu = 512 、memory = 1024 のタスク定義を、元々のサンプル「コード(tf)」にあるcpu = 2048 、memory = 4096 に更新しようとします。

このように、Terraform の「コード(tf)」に書いたタスク定義の内容 と 「実環境上にある 同じ ECS のタスク定義の最新リビジョン」 を比較してくれます

では、上記の比較を元にコード側を修正します。 コードの差分については自分で直す必要があります。 cpu = 512 、memory = 1024 にします。

resource "aws_ecs_task_definition" "latest-trial" {
  family                   = "task1"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 512
  memory                   = 1024

  container_definitions = jsonencode([
    {
      name       = "task"
      image      = "public.ecr.aws/nginx/nginx:1.25"
      essenntial = "true"
      portMappings = [{
        name          = "nginx-80-tcp"
        containerPort = 80
        protocol      = "tcp"
        appProtocol   = "http"
      }]
    }
  ])
  track_latest = true
  # lifecycle { ignore_changes = [container_definitions]}
}

terraform plan を実行すると、"No changes. "(変更なし)と出ました。
「コード (tf)」と 「実環境上にある 同じ ECS のタスク定義の最新リビジョン」が揃ったためです。

その後にterraform apply を実行しても、"No changes. "(変更なし)と出ました。

豆知識(本筋からは外れるので、この見出しは読まなくて大丈夫です。)

細かい話、上で"No changes. "(変更なし)となったterraform applyでは tfstate ファイルが更新されています。
「コード(tf)」を「実環境上にある 同じ ECS のタスク定義の最新リビジョン」 に合わせて、変更した結果、tfstate ファイルだけが、初回にサンプルコード(tf)を terraform apply した状態で残っていたためです。
terraform apply の前にterraform plan -refresh-only を実行した結果です。
「実環境上にある 同じ ECS のタスク定義の最新リビジョン」を元に tfstate ファイルを更新する計画を表示しています。

terraform apply の後にterraform plan -refresh-only を実行した結果です。
terraform applyで「実環境上にある 同じ ECS のタスク定義の最新リビジョン」を元に tfstate ファイルを更新したため "No changes. "(変更なし)と出ました。

-refresh-only については以下の記事も参照ください。
【Terraform】plan / apply と状態ファイル (tfstate) の関係 - サーバーワークスエンジニアブログ

track_latest オプションの注意点、その1

track_latest オプションでは、「コード (tf)」と 「実環境上にある 同じ ECS のタスク定義の最新リビジョン」を比較します。
Terraform のコード(tf) 以外の方法で更新した ECS タスク定義を、 Terraform に認識させることができます。 しかし、例えば、「CodePipeline で Blue/Green デプロイをして、ロールバックを行った結果、タスク定義の最新バージョンを現在は使っていない。 」という状況では、terraform plan をして比較しても、嬉しくないでしょう。
記事内で紹介した以下の部分ですね。

例えば、CodePipeline で Blue/Green デプロイを構成している場合には、新しいリビジョンのタスク定義を使ってデプロイします。もし、ロールバックを行う際には、以前のタスク定義のリビジョンに戻すという形をとります。

そのため、track_latest オプションを有効にする際には「最新だけど使っていないタスク定義」は「登録解除」する必要があります。

track_latest オプションの注意点、その2

通常、Terraformでは、コード (tf) に記載した 1 つのリソース (resource) と、Terraform が環境上に作成したリソースを 1 対 1 でマッピングして、状態管理しています。
しかし、track_latest オプションでは、「コード (tf)」と 「実環境上にある 同じ ECS のタスク定義の最新リビジョン」を比較します track_latest オプションは Terraform の通常の状態管理とは異なる管理をする、特殊な機能だということです。
理解した上で、慎重に使う必要があります。 そのためなのか、デフォルトは false (無効)です。

まとめ

ECS のタスク定義 (aws_ecs_task_definition) に追加になった track_latest オプションについて解説しました。
この機能によって、パイプラインなど、「Terraform のコード(tf) 以外の方法で更新した ECS タスク定義」を、 Terraform に認識させることができます。
Terraform のコード(tf)からできる限り ignore_changes 属性を減らして、コードを用いたインフラ環境の再利用性や一貫性を高めたい場合には、有用なオプションだと感じました。
一方で、Terraform の通常の状態管理とは異なる管理をする、少し特殊な機能ですので、理解した上で、慎重に使う必要があります。 便利な機能であるものの、デフォルトは false (無効)です。
上手に使っていきたいですね。

併せて読みたい

【Terraform】plan / apply と状態ファイル (tfstate) の関係 - サーバーワークスエンジニアブログ

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア(一応)

好きなサービス:ECS、ALB

趣味:トレラン、登山(たまに)