🍇

Terraform で list や count を使ってはいけない

2022/11/28に公開

Terraform の繰り返し処理(類似リソースの作成)には、

  • list ではなく set
  • count ではなく for_each

を使いましょう。

listcount を使うことの問題点

一言で言うと、「terraform のリソース名に list の index が含まれるため、list の中身の変更が容易ではなくなってしまうこと」です。

具体的に説明します。

下記は、list(配列) と count を使い、Google Cloud の API を有効化する例です。

https://developer.hashicorp.com/terraform/language/meta-arguments/count

locals {
  services = [
    "artifactregistry.googleapis.com",
    "cloudapis.googleapis.com",
    "compute.googleapis.com",
    "iam.googleapis.com",
    "storage.googleapis.com",
  ]
}

resource "google_project_service" "default" {
  count    = length(local.services)
  service  = local.services[count.index]
  project  = data.google_project.default.id
}

これを terraform apply すると、リソース名は

google_project_service.default[0]
google_project_service.default[1]
...
google_project_service.default[4]

のようになります。このケースでは、

storage.googleapis.comgoogle_project_service.default[4]

に対応しています。

ここで、例えば iam.googleapis.com API を無効化すべく、対応する行を削除したとします。
そうすると、list での index が -1 された影響で、

storage.googleapis.comgoogle_project_service.default[3]

に変わってしまいます。

リソース名が変わってしまうと、Terraform では完全に別物とされるので、リソースの削除と作成(再作成)が発生します。この例では API の無効化 → 有効化が行われてしまいます。
これは全く望ましい挙動ではありません。

ここでは API の無効化を例として挙げましたが、API の有効化も同様で、 index がズレると意図しないリソースの再作成が行われてしまいます。つまり、listcount を使うと、list の中身の変更が容易ではなくなってしまうわけです。

setfor_each を使うことで解決

上記の問題は setfor_each を使うことで解決できます。

https://developer.hashicorp.com/terraform/language/meta-arguments/for_each

下記は、set(重複しない配列) と for_each を使い、上例と同様に Google Cloud の API を有効化する例です。

locals {
  services = [
    "artifactregistry.googleapis.com",
    "cloudapis.googleapis.com",
    "compute.googleapis.com",
    "iam.googleapis.com",
    "storage.googleapis.com",
  ]
}

resource "google_project_service" "default" {
  for_each = toset(local.services) # toset で set に変換
  service  = each.value # each.key でも可
  project  = data.google_project.default.id
}

これを terraform apply すると、リソース名は

google_project_service.default["artifactregistry.googleapis.com"]
google_project_service.default["cloudapis.googleapis.com"]
...
google_project_service.default["storage.googleapis.com"]

のようになります。リソース名から list の index が消え、list の値がそのまま使われるようになりました。

storage.googleapis.comgoogle_project_service.default["storage.googleapis.com"]

に対応しています。set が利用されているので、list の中での順序が変わっても、リソース名に変更はなく、意図しないリソースの再作成が行われずに済むようになりました。

Discussion