Edited at

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


はじめに

今朝方こんな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 |
+--------------------------------------+-----------+--------------------------------------+--------+

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