はじめに
今朝方こんなTweetがあったのを見た方も多いかと思います。
Terraform 0.12.6 is released and includes FOR EACH FOR RESOURCES. 👾 https://t.co/ZVWOzZKDQU
— Mitchell Hashimoto (@mitchellh) July 31, 2019
Send your 👏 to @pamasaur for implementing this amazing feature! pic.twitter.com/wZbOxCx7zZ
リソースで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 |
+--------------------------------------+-----------+--------------------------------------+--------+
以上、まずは取り急ぎ検証してみた結果でした。