1
Help us understand the problem. What are the problem?

posted at

[小ネタ] TerraformでECSリソースを管理するときのちょっとした問題と解決案

これは「「はじめに」の Advent Calendar 2021」14日目の記事です。

TerraformでECSリソースを作成

terraformでECSのリソースを作る場合、例えばコードは以下のような感じになります。(※リソースは省略しているので、以下のコードそのままではapplyは出来ません。ご了承ください)

ecs.tf
resource "aws_ecs_cluster" "foo" {
  name = "foo"
}

resource "aws_ecs_task_definition" "web" {
  family = "web"
  container_definitions = jsonencode([
    {
      name      = "web"
      image     = "example/web:1.2.3"
      cpu       = 128
      memory    = 512
      essential = true
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
}

resource "aws_ecs_service" "foo" {
  name            = "web"
  cluster         = aws_ecs_cluster.foo.id
  task_definition = aws_ecs_task_definition.web.arn
  desired_count   = 1
  iam_role        = aws_iam_role.foo.arn
}

ECS Taskの問題点

  • Taskが利用するDockerイメージを更新したい
  • TaskのCPU,Memoryを変更したい
  • 環境変数、Secretsなどを追加したい

これらの場合、Task定義aws_ecs_task_definitionを更新する必要があります。
例として、イメージのタグを変更する場合

ecs.tf
(略)
resource "aws_ecs_task_definition" "web" {
  family = "web"
  container_definitions = jsonencode([
    {
      name      = "web"
      # 1.2.3 → 1.2.4
      image     = "example/web:1.2.4"
      cpu       = 128
      memory    = 512
      essential = true
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
}
(略)

このtfをapplyすると、Task定義は再作成されるという差分が出ます。

-/+ aws_ecs_task_definition.web (new resource required)
...

これは、AWSではTask定義はリビジョンごとに別リソースとして扱っており、terraformはAWSのAPIを呼び出していることに起因します。
同じFamilyだとしても別リビジョンは別リソースなので、古いリビジョンを消して新しいリビジョンを作成することになります。

この問題は、terraformのissueでも議論されています。
過去のリビジョンを保持しておきたい場合、terraformでTask定義を更新するのができなくなります。

解決策としては、
- stateを作成直後に消してterraform管理外にする
- lifecycle.ignore_changescontainer_definitions を追加することで更新をしない
- 更新が必要な場合は、ignore_changesをコメントアウトしてapplyする

などがありますが、どちらもハック的なワークアラウンドとなります。

ECS Serviceの問題

aws_ecs_service もtaskのARNが変わるため、差分が出てしまいます。
ま\Task定義を変更しない場合でも、差分が出るときがあります。

デプロイを別のCIツール(CodePipelineやGithub Actionsなど)で実行していることが多いと思われます。
この場合、そもそもterraformで作成したTask定義のリビジョンと実際に利用されているTask定義のリビジョンに差分が出ており、気づかずにそのままapplyすると稼働中のアプリケーションが先祖還りするリスクがあります。

CIでデプロイされている場合など、最新のTask定義を常に参照したい場合、以下のようなコードにすることで実現できます。

ecs.tf
(略)
data "aws_ecs_task_definition" "web" {
  task_definition = aws_ecs_task_definition.web.family
}

resource "aws_ecs_service" "foo" {
  name            = "web"
  cluster         = aws_ecs_cluster.foo.id
  task_definition = "${aws_ecs_task_definition.web.family}:${max(aws_ecs_task_definition.web.revision, data.aws_ecs_task_definition.web.revision)}"
  desired_count   = 1
  iam_role        = aws_iam_role.foo.arn
}

これは、以下のリンクを参考にしています。

デプロイしたリビジョンを、Parameter Store等に入れて同じようにdata sourceとして参照する方法も有効です。

ECSサービスをDataSourceとして使えば、デプロイ中のRevisionをそのまま参照することができそうですが・・・(自己参照的なことになるけど、できるのかな・・・)

task_definitionignore_changes に入れることでそもそも差分を出さないようにすることは出来ますが、ignore_changesに頼りすぎるのは可能であれば避けるべきでしょう。

ECS Serviceはそもそもdesired_countというAutoScalingで変更されうるリソースを持っていたりするので、terraformでECS Serviceを管理するとdesired_countignore_changesに追加せざるを得ない状況です。このあたり、Serviceとしてどのリソースを使うのか、という情報と実際に動いているServiceの状態を別々に管理したさがありますね・・・

※ AWSに Task定義は ecs_task_family というリソースがあったり、 ecs_service_deployment というリソースがあればこういった問題は起きづらい気がします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?