Terraform aws_ecs_task_difinition リソースを更新してapplyしたときに、古いrevisionが削除されないようにする方法



# はじめに
terraform で ecs のタスク部分を管理するコードを書いていたところ、更新するたびに古いrevisionにdestroyが走ってしまい、困っていました。

古いrevisionを削除せずに、新しいrevisionとしてapply出来る方法がわかったので、記事にさせていただきます。

スマートな方法ではないと思うので、何か他にいい方法や新しいオプション等が出ていたら教えていただきたく思います。

# 環境

Terraform v0.11.14
+ provider.aws v1.60.0

macbook
OS:mojave 10.14.5

Fargate×ECSでAWSが提供するnginxのコンテナを動かしてみます。
コンテナ定義の環境変数を追加したり削除したりして、挙動をみていきます。

## ディレクトリ構成

```
> tree
.
├── test_workspace
│   ├── cloudwatch.tf
│   ├── config.tf
│   ├── data_remote_state.tf
│   ├── ecs_service.tf
│   ├── ecs_task.tf
│   ├── iam.tf
│   ├── region.tf
│   ├── service_discovery.tf
│   ├── subnet.tf
│   ├── container-definitions
│   │   └── container_definitions.json
│   ├── terraform.tfstate.d/...
│   └── variables.tf
└── workspace
    ├── acm.tf
    ├── alb.tf
    ├── config.tf
    ├── ecr.tf
    ├── ecs_cluster.tf
    ├── iam.tf
    ├── outputs.tf
    ├── region.tf
    ├── securitygroup.tf
    ├── terraform.tfstate.d/...
    ├── variables.tf
    └── vpc.tf

```
今回ディレクトリを分けて構築しようと思った理由は3つあります。
1つ目は、tfstateの肥大化を抑え、オペミスの影響範囲を狭くするため。
2つ目は、サービスとタスクはスケール対応したいときに迅速に対応できるよう切り分けておきたいため。
3つ目は、今後、コンテナ周りのリソースに関しては、インフラエンジニアだけでなくデプロイに関わるアプリケーションエンジニアも他のリソースを気にせずterraformの編集ができ、環境変数の変更やスケール対応等を行えるよう環境を整備したいため。


下記リソースは事前に他のディレクトリ(workspaceディレクトリ)で構築済みの状態です。

今回は構築済みリソースに関しては詳細を省きます。

vpc
acm
alb
ecs_cluster
iam_role
securitygroup
variables


# 古いrevisionが削除されるパターン

## taskのコード

```
> cat ecs_task.tf

resource "aws_ecs_task_definition" "maint_test_task_definition" {
  family                   = "maint_test_task_definition"
  container_definitions    = "${file("container_definitions/container_definitions.json")}"
  task_role_arn            = "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
  execution_role_arn       = "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
  network_mode             = "awsvpc"
  memory                   = "2048"
  cpu                      = "1024"
  requires_compatibilities = ["FARGATE"]

}

```


## container_definitions.jsonに環境変数を追加

```
[
  {
    "name": "nginx",
    "image": "nginx:alpine",
    "cpu": 256,
    "memory": 512,
    "essential": true,
    "network_mode": "awsvpc",
    "portMappings": [
      {
        "hostPort": 80,
        "protocol": "tcp",
        "containerPort": 80
      }
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "secretOptions": null,
        "options": {
            "awslogs-group": "/ecs/test",
            "awslogs-region": "us-west-2",
            "awslogs-stream-prefix": "ecs"
        }
    },
    "environment": [
      {
        "name": "API_ENV",
        "value": "develop"
      },
      {
        "name": "MYSQL_ENV",
        "value": "mysql-dev"
      }
    ]
  }
]

```


下記の行が追記された内容です。環境変数を1つ追加しました。

```
,
      {
        "name": "MYSQL_ENV",
        "value": "mysql-dev"
      }
```


## serviceのコード

```
> cat ecs_service.tf

resource "aws_ecs_service" "maint-test" {
  name                               = "maint-test-service"
  cluster                            = "${data.terraform_remote_state.maint.ecs.maint_test_ecs_cluster_id}"
  task_definition                    = "${aws_ecs_task_definition.maint_test_task_definition.arn}"
  platform_version                   = "LATEST"
  desired_count                      = 2
  launch_type                        = "FARGATE"
  deployment_minimum_healthy_percent = "50"
  deployment_maximum_percent         = "100"
  health_check_grace_period_seconds  = "180"

  load_balancer {
    target_group_arn = "${data.terraform_remote_state.maint.alb.maint_test_ecs_alb_tg_arn}"
    container_name   = "nginx"
    container_port   = 80
  }

  network_configuration {
    subnets          = ["${data.terraform_remote_state.maint.vpc.subnet-public-a}","${data.terraform_remote_state.maint.vpc.subnet-public-c}"]
    security_groups  = ["${data.terraform_remote_state.maint.sg.security_group_WEB_id}"]
    assign_public_ip = "true"
  }

  service_registries {
    registry_arn = "${aws_service_discovery_service.maint_test.arn}"
  }

}
```

## terraform plan 結果

このようにdestroyが出てしまいます。


```
> terraform plan

省略

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

-/+ aws_ecs_task_definition.maint_test_task_definition (new resource required)
      id:                                  "maint_test_task_definition" => <computed> (forces new resource)
      arn:                                 "arn:aws:ecs:us-west-2:012345678910:task-definition/maint_test_task_definition:13" => <computed>
      container_definitions:               "[{\"cpu\":256,\"environment\":[{\"name\":\"API_ENV\",\"value\":\"develop\"}],\"essential\":true,\"image\":\"nginx:alpine\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/maint-test\",\"awslogs-region\":\"us-west-2\",\"awslogs-stream-prefix\":\"ecs\"}},\"memory\":512,\"mountPoints\":[],\"name\":\"nginx\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}],\"volumesFrom\":[]}]" => "[{\"cpu\":256,\"environment\":[{\"name\":\"API_ENV\",\"value\":\"develop\"},{\"name\":\"MYSQL_ENV\",\"value\":\"mysql-dev\"}],\"essential\":true,\"image\":\"nginx:alpine\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/maint-test\",\"awslogs-region\":\"us-west-2\",\"awslogs-stream-prefix\":\"ecs\"},\"secretOptions\":null},\"memory\":512,\"name\":\"nginx\",\"network_mode\":\"awsvpc\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}]}]" (forces new resource)
      cpu:                                 "1024" => "1024"
      execution_role_arn:                  "arn:aws:iam::012345678910:role/ecsTaskExecutionRole" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
      family:                              "maint_test_task_definition" => "maint_test_task_definition"
      memory:                              "2048" => "2048"
      network_mode:                        "awsvpc" => "awsvpc"
      requires_compatibilities.#:          "1" => "1"
      requires_compatibilities.3072437307: "FARGATE" => "FARGATE"
      revision:                            "13" => <computed>
      task_role_arn:                       "arn:aws:iam::012345678910:role/ecsTaskExecutionRole" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"


Plan: 1 to add, 0 to change, 1 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

```

## terraform apply 結果

```
 > terraform apply

省略

  Enter a value: yes

aws_ecs_task_definition.maint_test_task_definition: Destroying... (ID: maint_test_task_definition)
aws_ecs_task_definition.maint_test_task_definition: Destruction complete after 1s
aws_ecs_task_definition.maint_test_task_definition: Creating...
  arn:                                 "" => "<computed>"
  container_definitions:               "" => "[{\"cpu\":256,\"environment\":[{\"name\":\"API_ENV\",\"value\":\"develop\"},{\"name\":\"MYSQL_ENV\",\"value\":\"mysql-dev\"}],\"essential\":true,\"image\":\"nginx:alpine\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/maint-test\",\"awslogs-region\":\"us-west-2\",\"awslogs-stream-prefix\":\"ecs\"},\"secretOptions\":null},\"memory\":512,\"name\":\"nginx\",\"network_mode\":\"awsvpc\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}]}]"
  cpu:                                 "" => "1024"
  execution_role_arn:                  "" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
  family:                              "" => "maint_test_task_definition"
  memory:                              "" => "2048"
  network_mode:                        "" => "awsvpc"
  requires_compatibilities.#:          "" => "1"
  requires_compatibilities.3072437307: "" => "FARGATE"
  revision:                            "" => "<computed>"
  task_role_arn:                       "" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
aws_ecs_task_definition.maint_test_task_definition: Creation complete after 2s (ID: maint_test_task_definition)

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

```

コンソールで確認をすると、maint\_test\_task\_definition:13のrevisionが削除されてmaint\_test\_task\_definition:14が新しく作成されてしまっています。

![スクリーンショット 2019-06-07 14.54.55.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148264/0abb947e-f899-c56b-679f-9c000fac813f.jpeg)

![スクリーンショット 2019-06-07 14.59.46.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148264/59d7f67e-4ac0-d197-bcf1-abd61dad6373.jpeg)


# 古いrevisionが削除されない方法

下記コマンドでstateファイルでタスク定義を管理しないようにします。リソースは削除されないので安心してください。あくまでterraformの管理下から外すだけです。

こちらのgithubのやりとりを参考にさせていただきました。

https://github.com/terraform-providers/terraform-provider-aws/issues/258#issuecomment-460981864

```
> terraform state rm aws_ecs_task_definition.maint_test_task_definition
1 items removed.
Item removal successful.
```

タスク定義をterraformの管理から外した状態でapplyすると、新しいrevisionで更新したタスクが作成されて、古いrevisionも削除されないで残しておくことができました。


## container_definitions.jsonに環境変数を削除

今回は先程増やした環境変数を削除する変更を加えてみます。

```
> cat container_definitions.json
[
  {
    "name": "nginx",
    "image": "nginx:alpine",
    "cpu": 256,
    "memory": 512,
    "essential": true,
    "network_mode": "awsvpc",
    "portMappings": [
      {
        "hostPort": 80,
        "protocol": "tcp",
        "containerPort": 80
      }
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "secretOptions": null,
        "options": {
            "awslogs-group": "/ecs/maint-test",
            "awslogs-region": "us-west-2",
            "awslogs-stream-prefix": "ecs"
        }
    },
    "environment": [
      {
        "name": "API_ENV",
        "value": "develop"
      }
    ]
  }
]

```

## terraform plan 結果

destroyがでなくなり、createのみとなりました。

```
> terraform plan

省略

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_ecs_task_definition.maint_test_task_definition
      id:                                  <computed>
      arn:                                 <computed>
      container_definitions:               "[{\"cpu\":256,\"environment\":[{\"name\":\"API_ENV\",\"value\":\"develop\"}],\"essential\":true,\"image\":\"nginx:alpine\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/maint-test\",\"awslogs-region\":\"us-west-2\",\"awslogs-stream-prefix\":\"ecs\"},\"secretOptions\":null},\"memory\":512,\"name\":\"nginx\",\"network_mode\":\"awsvpc\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}]}]"
      cpu:                                 "1024"
      execution_role_arn:                  "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
      family:                              "maint_test_task_definition"
      memory:                              "2048"
      network_mode:                        "awsvpc"
      requires_compatibilities.#:          "1"
      requires_compatibilities.3072437307: "FARGATE"
      revision:                            <computed>
      task_role_arn:                       "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

```

## terraform apply 結果

こちらでもdestroyが出ずに、createのみです。

```
> terraform apply

省略

  Enter a value: yes

aws_ecs_task_definition.maint_test_task_definition: Creating...
  arn:                                 "" => "<computed>"
  container_definitions:               "" => "[{\"cpu\":256,\"environment\":[{\"name\":\"API_ENV\",\"value\":\"develop\"}],\"essential\":true,\"image\":\"nginx:alpine\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/maint-test\",\"awslogs-region\":\"us-west-2\",\"awslogs-stream-prefix\":\"ecs\"},\"secretOptions\":null},\"memory\":512,\"name\":\"nginx\",\"network_mode\":\"awsvpc\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}]}]"
  cpu:                                 "" => "1024"
  execution_role_arn:                  "" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
  family:                              "" => "maint_test_task_definition"
  memory:                              "" => "2048"
  network_mode:                        "" => "awsvpc"
  requires_compatibilities.#:          "" => "1"
  requires_compatibilities.3072437307: "" => "FARGATE"
  revision:                            "" => "<computed>"
  task_role_arn:                       "" => "arn:aws:iam::012345678910:role/ecsTaskExecutionRole"
aws_ecs_task_definition.maint_test_task_definition: Creation complete after 1s (ID: maint_test_task_definition)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

```


コンソールからも確認してみます。
先程は、maint\_test\_task\_definition:13が削除されてmaint\_test\_task\_definition:14が作成されましたが、今回は、maint\_test\_task\_definition:14は削除されず、maint\_test\_task\_definition:15が作成されました。

![スクリーンショット 2019-06-07 15.14.10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148264/19f3423d-ab7b-2af5-af9f-26ff5c8c0c07.jpeg)



# 最後に
最後まで読んでいただきありがとうございました。
今回はじめてterraform state rm コマンド使いましたが、このコマンドでまたterraformの構成管理の仕様の理解が深まりました。
変更がある度に、state rm コマンドを打つのはスマートじゃないと思うので、何かいい方法はないかと探しています。
知っている方いたらおしえてください！もしわかれば追記するか、新たに記事にさせていただきます。

task_definitionの内容に変更がなければ、そのままの状態を維持し、変更があった場合のみ、新しいrevisionで追加してくれるようになったらいいなと思います。


# 参考リンク
「Resource: aws\_ecs\_task\_definition」

https://www.terraform.io/docs/providers/aws/r/ecs_task_definition.html

「aws_ecs_task_definition overwrites previous revision」

https://github.com/terraform-providers/terraform-provider-aws/issues/258

「Optionally skip destroy previous aws_ecs_task_definition resource 」

https://github.com/hashicorp/terraform/issues/18047