Terraform v0.12.6から追加されたfor_eachを試してみる
2019-08-16

tl; dr

  • 複数のリソースをまとめて作成する方法として count の他に for_each が追加された
  • 参照しているlistの要素に変更があると count の場合は意図しない変更が入る可能性がある
  • for_each を使うことで意図しない変更を避けることができる状態を保てる
  • listを参照しないようなシンプルに複数リソースを作成するような場合は count が便利, それ以外は for_each が良さそう

for_each?

v0.12で導入されたdynamic-blocksで使えるfor_eachではなく, v0.12.6で導入された複数のリソース作成に使うことができるfor_eachです。
以前からあるcountに近い機能を提供します。

countとfor_eachの違い

count では作成するリソースに対して0から始まるインデックスを割り当てて管理します。インデックスを用いてlistから要素を参照し, それをリソース作成に使用することも可能です。

複数のIAMユーザーをcountを使って作成する例です。


locals {
  users = [
    "user1",
    "user2",
    "user3"
  ]
}

resource "aws_iam_user" "users" {
  count = length(local.users)
  name  = local.users[count.index]
}

このコードで terraform apply すると3つのIAMユーザーが作成されます。

次にこの状態から user1 だけを削除しようとします。


locals {
  users = [
    //"user1",
    "user2",
    "user3"
  ]
}

resource "aws_iam_user" "users" {
  count = length(local.users)
  name  = local.users[count.index]
}

この状態で terraform plan を確認すると3つのリソースに対して変更が発生すると表示されます。


$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place
  - destroy

Terraform will perform the following actions:

  # aws_iam_user.users[0] will be updated in-place
  ~ resource "aws_iam_user" "users" {
        arn           = "arn:aws:iam::912174252555:user/user1"
        force_destroy = false
        id            = "user1"
      ~ name          = "user1" -> "user2"
        path          = "/"
        tags          = {}
        unique_id     = "AIDA5IYOSQIF7MST5RXY2"
    }

  # aws_iam_user.users[1] will be updated in-place
  ~ resource "aws_iam_user" "users" {
        arn           = "arn:aws:iam::912174252555:user/user2"
        force_destroy = false
        id            = "user2"
      ~ name          = "user2" -> "user3"
        path          = "/"
        tags          = {}
        unique_id     = "AIDA5IYOSQIF5CH4OUE5E"
    }

  # aws_iam_user.users[2] will be destroyed
  - resource "aws_iam_user" "users" {
      - arn           = "arn:aws:iam::912174252555:user/user3" -> null
      - force_destroy = false -> null
      - id            = "user3" -> null
      - name          = "user3" -> null
      - path          = "/" -> null
      - tags          = {} -> null
      - unique_id     = "AIDA5IYOSQIFSQKLGN7WU" -> null
    }

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

user1だけが削除されるのではなく, user2,user3にも変更が発生しています。
多くの場合, 意図する挙動ではないと思います。


次に for_each を使って同じようにユーザーを作成してみます。


locals {
  users = [
    "user1",
    "user2",
    "user3"
  ]
}

resource "aws_iam_user" "users" {
  for_each = toset(local.users)
  name     = each.value
}

terraform apply を実行することでcountのときと同じように3つのIAMユーザーを作成できます。
ここからuser1だけを削除しようとすると, 期待通りにuser1だけを削除することができます。


locals {
  users = [
    //"user1",
    "user2",
    "user3"
  ]
}

resource "aws_iam_user" "users" {
  for_each = toset(local.users)
  name     = each.value
}

$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_iam_user.users["user1"] will be destroyed
  - resource "aws_iam_user" "users" {
      - arn           = "arn:aws:iam::912174252555:user/user1" -> null
      - force_destroy = false -> null
      - id            = "user1" -> null
      - name          = "user1" -> null
      - path          = "/" -> null
      - tags          = {} -> null
      - unique_id     = "AIDA5IYOSQIF3BWKJ7JAX" -> null
    }

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

count はリソースの管理にインデックスを用いて aws_iam_user.users[0] のように行われていましたが, for_each では aws_iam_user.users["user1"] のように参照するキーを使用するようになったためlistの要素数が変化したり順番が入れ替わったりしても余計な変更を発生させずに済むようになりました。