Help us understand the problem. What is going on with this article?

Terraform 0.12.6からつかえるようになったresourceのfor_eachを試してみた

More than 1 year has passed since last update.

はじめに

今朝方こんなTweetがあったのを見た方も多いかと思います。

リソースでfor_eachがつかえる のか、なるほど。

これがどういうことなのかを実際に試してみました。

結論

例えばこんな書き方ができます。

variable "keyname_servername" {
  type = map(string)
  default = {
    server_1 = "keypair_1"
    server_2 = "keypair_2"
    server_3 = "keypair_3"
  }
}

resource "ecl_compute_keypair_v2" "keypair_1" {
  for_each = var.keyname_servername
  name     = each.value
}

resource "ecl_compute_instance_v2" "instance_1" {
  for_each = var.keyname_servername

  name        = each.key
  flavor_name = "1CPU-4GB"
  image_name  = "Ubuntu-18.04.1_64_virtual-server_02"
  key_pair    = ecl_compute_keypair_v2.keypair_1["${each.key}"].name
}
  • 繰り返し処理内で作っているKeypairを、それぞれ繰り返し内で処理しているインスタンスに埋め込みます。
  • その際対応関係もしっかり指定します
    • 1つ目のkeypairを1つ目のインスタンスに埋め込む
    • 2つ目のkeypairを2つ目のインスタンスに埋め込む
    • 3つ目のkeypairを3つ目のインスタンスに埋め込む

では、上記のHCLがどういう意味になるのかを1つずつ書いてみます。

for_eachを書くと何が起きるのか

まずは繰り返し処理が起きるということなのです

リソースの定義内にfor_eachを書くと繰り返し処理が行われます。
これは、直接mapやlistをかいてもいいのですが、変数から参照することも可能です。

ということで、まずは変数を定義しdefault値を付与。それがここ。

variable "keyname_servername" {
  type = map(string)
  default = {
    server_1 = "keypair_1"
    server_2 = "keypair_2"
    server_3 = "keypair_3"
  }
}

key-valueなmapを生成しています。

で、それをリソース定義内から参照しています。
それがここ。(ここで関係がない部分は省略)

resource "ecl_compute_keypair_v2" "keypair_1" {
  for_each = var.keyname_servername <-- ここと
  ()
}

resource "ecl_compute_instance_v2" "instance_1" {
  for_each = var.keyname_servername <-- ここ

  ()
}

で、その繰り返し処理で作られたリソースをHCL内で参照するには?

例えばここだけを見てみます。

variable "servername_keynamr" {
  type = map(string)
  default = {
    server_1 = "keypair_1"
    server_2 = "keypair_2"
    server_3 = "keypair_3"
  }
}

resource "ecl_compute_keypair_v2" "keypair_1" {
  for_each = var.keyname_servername <-- ここと
  ()
}

この時作られるリソースは、以下のような識別子になります。

  • ecl_compute_keypair_v2.keypair_1["server_1"]
  • ecl_compute_keypair_v2.keypair_1["server_2"]
  • ecl_compute_keypair_v2.keypair_1["server_3"]

つまりfor_eachのkeyが、 [] 付きでリソース識別子の後ろにつくわけですね。
この書き方を使えば、for_eachで作られたリソースの属性を参照することが出来ます。

each.key, each.value ってなに?

一番最初の例に、 each.key , each.value ってのが出てましたよね。
これは何なのかというと、 for_each 指定している対象のkeyとvalueです。

今回はfor_eachのソースはこれ。

variable "keyname_servername" {
  type = map(string)
  default = {
    server_1 = "keypair_1"
    server_2 = "keypair_2"
    server_3 = "keypair_3"
  }
}

ということで、 each.key は、 server_1, server_2 , server_3 になりますし、each.value は、 keypair_1, keypair_2 , keypair_3 になります。

最初に戻って・・・

改めて見てみます。

variable "keyname_servername" {
  type = map(string)
  default = {
    server_1 = "keypair_1"
    server_2 = "keypair_2"
    server_3 = "keypair_3"
  }
}

resource "ecl_compute_keypair_v2" "keypair_1" {
  for_each = var.keyname_servername
  name     = each.value
}

resource "ecl_compute_instance_v2" "instance_1" {
  for_each = var.keyname_servername

  name        = each.key
  flavor_name = "1CPU-4GB"
  image_name  = "Ubuntu-18.04.1_64_virtual-server_02"
  key_pair    = ecl_compute_keypair_v2.keypair_1["${each.key}"].name
}

特にここ。

  key_pair    = ecl_compute_keypair_v2.keypair_1["${each.key}"].name

これはつまり、

  • for_eachで作られるキーペア3点はそれぞれ以下の識別子になる
    • ecl_compute_keypair_v2.keypair_1["server_1"]
    • ecl_compute_keypair_v2.keypair_1["server_2"]
    • ecl_compute_keypair_v2.keypair_1["server_3"]
  • これをinterpolation syntaxで書くとこうなる
    • ecl_compute_keypair_v2.keypair_1["${each.key}"]
  • で、それをインスタンス内から参照しつつ、そのname属性を埋め込むとこうなる
    • key_pair = ecl_compute_keypair_v2.keypair_1["${each.key}"].name

・・・ということをやっていることになります。

実際には今回の例はあまりfor_eachしなくてもいい気がしてしまいますが、こんな感じで書けるようですね。

実際にapplyしてみた

こんな感じになりました。

% terraform apply                                                                              

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:

  # ecl_compute_instance_v2.instance_1["server_1"] will be created
  + resource "ecl_compute_instance_v2" "instance_1" {
      + access_ip_v4        = (known after apply)
      + all_metadata        = (known after apply)
      + availability_zone   = (known after apply)
      + flavor_id           = (known after apply)
      + flavor_name         = "1CPU-4GB"
      + id                  = (known after apply)
      + image_id            = (known after apply)
      + image_name          = "Ubuntu-18.04.1_64_virtual-server_02"
      + key_pair            = "keypair_1"
      + name                = "server_1"
      + power_state         = "active"
      + region              = (known after apply)
      + stop_before_destroy = false

      + network {
          + access_network = (known after apply)
          + fixed_ip_v4    = (known after apply)
          + mac            = (known after apply)
          + name           = (known after apply)
          + port           = (known after apply)
          + uuid           = (known after apply)
        }
    }

  # ecl_compute_instance_v2.instance_1["server_2"] will be created
  + resource "ecl_compute_instance_v2" "instance_1" {
      + access_ip_v4        = (known after apply)
      + all_metadata        = (known after apply)
      + availability_zone   = (known after apply)
      + flavor_id           = (known after apply)
      + flavor_name         = "1CPU-4GB"
      + id                  = (known after apply)
      + image_id            = (known after apply)
      + image_name          = "Ubuntu-18.04.1_64_virtual-server_02"
      + key_pair            = "keypair_2"
      + name                = "server_2"
      + power_state         = "active"
      + region              = (known after apply)
      + stop_before_destroy = false

      + network {
          + access_network = (known after apply)
          + fixed_ip_v4    = (known after apply)
          + mac            = (known after apply)
          + name           = (known after apply)
          + port           = (known after apply)
          + uuid           = (known after apply)
        }
    }

  # ecl_compute_instance_v2.instance_1["server_3"] will be created
  + resource "ecl_compute_instance_v2" "instance_1" {
      + access_ip_v4        = (known after apply)
      + all_metadata        = (known after apply)
      + availability_zone   = (known after apply)
      + flavor_id           = (known after apply)
      + flavor_name         = "1CPU-4GB"
      + id                  = (known after apply)
      + image_id            = (known after apply)
      + image_name          = "Ubuntu-18.04.1_64_virtual-server_02"
      + key_pair            = "keypair_3"
      + name                = "server_3"
      + power_state         = "active"
      + region              = (known after apply)
      + stop_before_destroy = false

      + network {
          + access_network = (known after apply)
          + fixed_ip_v4    = (known after apply)
          + mac            = (known after apply)
          + name           = (known after apply)
          + port           = (known after apply)
          + uuid           = (known after apply)
        }
    }

  # ecl_compute_keypair_v2.keypair_1["server_1"] will be created
  + resource "ecl_compute_keypair_v2" "keypair_1" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + name        = "keypair_1"
      + private_key = (known after apply)
      + public_key  = (known after apply)
      + region      = (known after apply)
    }

  # ecl_compute_keypair_v2.keypair_1["server_2"] will be created
  + resource "ecl_compute_keypair_v2" "keypair_1" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + name        = "keypair_2"
      + private_key = (known after apply)
      + public_key  = (known after apply)
      + region      = (known after apply)
    }

  # ecl_compute_keypair_v2.keypair_1["server_3"] will be created
  + resource "ecl_compute_keypair_v2" "keypair_1" {
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + name        = "keypair_3"
      + private_key = (known after apply)
      + public_key  = (known after apply)
      + region      = (known after apply)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

ecl_compute_keypair_v2.keypair_1["server_2"]: Creating...
ecl_compute_keypair_v2.keypair_1["server_3"]: Creating...
ecl_compute_keypair_v2.keypair_1["server_1"]: Creating...
ecl_compute_keypair_v2.keypair_1["server_2"]: Creation complete after 2s [id=keypair_2]
ecl_compute_keypair_v2.keypair_1["server_3"]: Creation complete after 2s [id=keypair_3]
ecl_compute_keypair_v2.keypair_1["server_1"]: Creation complete after 2s [id=keypair_1]
ecl_compute_instance_v2.instance_1["server_3"]: Creating...
ecl_compute_instance_v2.instance_1["server_1"]: Creating...
ecl_compute_instance_v2.instance_1["server_2"]: Creating...
ecl_compute_instance_v2.instance_1["server_1"]: Still creating... [10s elapsed]
ecl_compute_instance_v2.instance_1["server_3"]: Still creating... [10s elapsed]
ecl_compute_instance_v2.instance_1["server_2"]: Still creating... [10s elapsed]
ecl_compute_instance_v2.instance_1["server_3"]: Creation complete after 13s [id=a3bf0f56-ad5f-4c32-aefa-9b2137041bba]
ecl_compute_instance_v2.instance_1["server_2"]: Creation complete after 13s [id=6f6b1979-f651-4dd4-9395-bd5b4385c9c7]
ecl_compute_instance_v2.instance_1["server_1"]: Creation complete after 13s [id=bfef921c-0be8-4c29-b691-5f887be0cbe6]

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

こんな感じでうまくいきました。

もう少し実用的な例

ただ、あんまりこれは実用的じゃなかったかな・・・と思ったので、もう少し実用的な例。
というのは、上記のインスタンスとキーペアの例だと、本来 (known after apply) として出てくれたほうがそれっぽいはずのキーペア名が事前にわかっちゃってますからね。

variable "keyname_servername" {
  type = map(string)
  default = {
    net_1 = "192.168.1.0/24"
    net_2 = "192.168.2.0/24"
    net_3 = "192.168.3.0/24"
  }
}

resource "ecl_network_network_v2" "network_1" {
  for_each = var.keyname_servername
  name     = each.key
}

resource "ecl_network_subnet_v2" "subnet_1" {
  for_each   = var.keyname_servername
  cidr       = each.value
  network_id = ecl_network_network_v2.network_1["${each.key}"].id
  no_gateway = true
}

これだとapply時にこんな感じ。

% terraform apply                                                            (git)-[master]

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:

  # ecl_network_network_v2.network_1["net_1"] will be created
  + resource "ecl_network_network_v2" "network_1" {
      + admin_state_up = (known after apply)
      + id             = (known after apply)
      + name           = "net_1"
      + plane          = "data"
      + region         = (known after apply)
      + shared         = (known after apply)
      + status         = (known after apply)
      + subnets        = (known after apply)
      + tenant_id      = (known after apply)
    }

  # ecl_network_network_v2.network_1["net_2"] will be created
  + resource "ecl_network_network_v2" "network_1" {
      + admin_state_up = (known after apply)
      + id             = (known after apply)
      + name           = "net_2"
      + plane          = "data"
      + region         = (known after apply)
      + shared         = (known after apply)
      + status         = (known after apply)
      + subnets        = (known after apply)
      + tenant_id      = (known after apply)
    }

  # ecl_network_network_v2.network_1["net_3"] will be created
  + resource "ecl_network_network_v2" "network_1" {
      + admin_state_up = (known after apply)
      + id             = (known after apply)
      + name           = "net_3"
      + plane          = "data"
      + region         = (known after apply)
      + shared         = (known after apply)
      + status         = (known after apply)
      + subnets        = (known after apply)
      + tenant_id      = (known after apply)
    }

  # ecl_network_subnet_v2.subnet_1["net_1"] will be created
  + resource "ecl_network_subnet_v2" "subnet_1" {
      + cidr              = "192.168.1.0/24"
      + dns_nameservers   = (known after apply)
      + enable_dhcp       = (known after apply)
      + gateway_ip        = (known after apply)
      + id                = (known after apply)
      + ip_version        = 4
      + ipv6_address_mode = (known after apply)
      + ipv6_ra_mode      = (known after apply)
      + network_id        = (known after apply)
      + no_gateway        = true
      + region            = (known after apply)
      + status            = (known after apply)
      + tenant_id         = (known after apply)

      + allocation_pools {
          + end   = (known after apply)
          + start = (known after apply)
        }
    }

  # ecl_network_subnet_v2.subnet_1["net_2"] will be created
  + resource "ecl_network_subnet_v2" "subnet_1" {
      + cidr              = "192.168.2.0/24"
      + dns_nameservers   = (known after apply)
      + enable_dhcp       = (known after apply)
      + gateway_ip        = (known after apply)
      + id                = (known after apply)
      + ip_version        = 4
      + ipv6_address_mode = (known after apply)
      + ipv6_ra_mode      = (known after apply)
      + network_id        = (known after apply)
      + no_gateway        = true
      + region            = (known after apply)
      + status            = (known after apply)
      + tenant_id         = (known after apply)

      + allocation_pools {
          + end   = (known after apply)
          + start = (known after apply)
        }
    }

  # ecl_network_subnet_v2.subnet_1["net_3"] will be created
  + resource "ecl_network_subnet_v2" "subnet_1" {
      + cidr              = "192.168.3.0/24"
      + dns_nameservers   = (known after apply)
      + enable_dhcp       = (known after apply)
      + gateway_ip        = (known after apply)
      + id                = (known after apply)
      + ip_version        = 4
      + ipv6_address_mode = (known after apply)
      + ipv6_ra_mode      = (known after apply)
      + network_id        = (known after apply)
      + no_gateway        = true
      + region            = (known after apply)
      + status            = (known after apply)
      + tenant_id         = (known after apply)

      + allocation_pools {
          + end   = (known after apply)
          + start = (known after apply)
        }
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

ecl_network_network_v2.network_1["net_2"]: Creating...
ecl_network_network_v2.network_1["net_1"]: Creating...
ecl_network_network_v2.network_1["net_3"]: Creating...
ecl_network_network_v2.network_1["net_1"]: Creation complete after 6s [id=3a6533b5-da6e-4bd8-8c56-73bfd90feee4]
ecl_network_network_v2.network_1["net_3"]: Creation complete after 6s [id=7172d43f-b1a9-4661-991e-e9c048021bbf]
ecl_network_network_v2.network_1["net_2"]: Creation complete after 6s [id=2b380a27-6b95-43f5-a580-f72e4df2d566]
ecl_network_subnet_v2.subnet_1["net_1"]: Creating...
ecl_network_subnet_v2.subnet_1["net_3"]: Creating...
ecl_network_subnet_v2.subnet_1["net_2"]: Creating...
ecl_network_subnet_v2.subnet_1["net_1"]: Creation complete after 5s [id=ae222a23-7384-473b-9fcf-2d94f3b08b8b]
ecl_network_subnet_v2.subnet_1["net_3"]: Creation complete after 5s [id=aeb6d40c-3755-4a45-baf5-eeab076675ed]
ecl_network_subnet_v2.subnet_1["net_2"]: Creation complete after 5s [id=8c157345-8a0e-4d20-9280-313297090269]

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

作成後のリソースを見てみると、ちゃんとsubnetからidを参照して、リソース感が結合できてます。

% ecl --os-cloud=jp4 network logical-network list
+--------------------------------------+-----------+--------------------------------------+--------+
| ID                                   | Name      | Subnets                              | Status |
+--------------------------------------+-----------+--------------------------------------+--------+
| 2b380a27-6b95-43f5-a580-f72e4df2d566 | net_2     | 8c157345-8a0e-4d20-9280-313297090269 | ACTIVE |
| 3a6533b5-da6e-4bd8-8c56-73bfd90feee4 | net_1     | ae222a23-7384-473b-9fcf-2d94f3b08b8b | ACTIVE |
| 7172d43f-b1a9-4661-991e-e9c048021bbf | net_3     | aeb6d40c-3755-4a45-baf5-eeab076675ed | ACTIVE |
+--------------------------------------+-----------+--------------------------------------+--------+

以上、まずは取り急ぎ検証してみた結果でした。

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