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の要素数が変化したり順番が入れ替わったりしても余計な変更を発生させずに済むようになりました。