這篇是 infrastructure 也可以 for each 第四篇,上次漏發了,今天補發
本章介紹 terraform 的 for syntax 與 dynamic block,快速迭代 resource block
課程內容與代碼會放在 Github 上: https://github.com/chechiachang/terraform-30-days
賽後文章會整理放到個人的部落格上 http://chechia.net/
For expression 是 terraform configuration language (hcl) 內的 syntax 語法
for each meta-argument 是
for_each
是在計算 resource block {} 的 meta 時使用https://www.terraform.io/docs/language/expressions/for.html
poolNameTuple = { for p in var.node_pools : name => p.name }
poolNameList = [
for p in var.node_pools : "p.name"
]
output "pool_name_list" {
value = [for p in var.node_pools : p.name]
}
兩個語法的定義位階不同
for_each = [for p in var.node_pools : p.name]
for 產出的是 list
for_each = [
var.node_pools["spot"].name
var.node_pools["on-demand"].name
...
]
一樣需要考量 for each 的限制
請依照Terraform 官方文件 for example,嘗試每個 example,以熟悉 for syntax 使用
想要管理很多類似的多個 resource block,我們可以參考使用 meta-argument,來管理 resource block
有個時候,一個 resource block 中,會有 repeatable nested blocks arguments,例如
storage_data_disk
resource "azurerm_virtual_machine" "main" {
name = "${var.prefix}-vm"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"
storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
storage_data_disk {
name = "data1"
caching = "None"
create_option = "Attach"
disk_size_gb = "100"
}
storage_data_disk {
name = "data2"
caching = "None"
create_option = "Attach"
disk_size_gb = "200"
}
storage_data_disk {
name = "data3"
caching = "None"
create_option = "Attach"
disk_size_gb = "300"
}
...
}
這時候,是想要管理一個 block 中的 field block,讓他一據 input 動態產生,這時可以使用 Dynamic block
以上面這個例子,可以改寫成 dynamic blocks
locals {
storage_data_disks = [
{
name = "data1"
disk_size_gb = "100"
},
{
name = "data2"
disk_size_gb = "200"
},
{
name = "data3"
disk_size_gb = "300"
}
]
}
resource "azurerm_virtual_machine" "main" {
name = "${var.prefix}-vm"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
network_interface_ids = [azurerm_network_interface.main.id]
vm_size = "Standard_DS1_v2"
storage_os_disk {
name = "myosdisk1"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
dynamic "storage_data_disk" {
for_each = var.storage_data_disks
content {
name = storage_data_disk.value["name"]
caching = "None"
create_option = "Attach"
disk_size_gb = storage_data_disk.value["disk_size_gb"]
}
}
...
}
許多高階程式語言有提供 nested loop,terraform dynamic block 也提供 nested
variable "load_balancer_origin_groups" {
type = map(object({
origins = set(object({
hostname = string
}))
}))
}
resource "load_balancer" "main" {
dynamic "origin_group" {
for_each = var.load_balancer_origin_groups
content {
name = origin_group.key
dynamic "origin" {
for_each = origin_group.value.origins
content {
hostname = origin.value.hostname
}
}
}
}
}
這個範例,在複雜的網路設定相關的 resource 很有機會看到。double nested dynamic block 的問題
通常比較好的 provider (例如三大公有雲)會提供另外的 resource block 來管理這些 nested block
load_balancer
resourceorigin_group
resource熟 golang 的不妨看一下 source code
要管理重複的 resource block ,Terrafrom 還提供另一個 meta-argument count。範例
azurerm_virtual_machine
resource "azurerm_virtual_machine" "main" {
count = 3
name = "${var.prefix}-vm-${count.index}"
...
}
實際 output 會類似
azurerm_virtual_machine.main
,這邊會是一組 collection,可以使用 index 存取azurerm_virtual_machine.main[0]
azurerm_virtual_machine.main[1]
azurerm_virtual_machine.main[2]
resource "azurerm_virtual_machine" "main[0]" {
name = "${var.prefix}-vm-0"
...
}
resource "azurerm_virtual_machine" "main[1]" {
name = "${var.prefix}-vm-1"
...
}
resource "azurerm_virtual_machine" "main[2]" {
name = "${var.prefix}-vm-2"
...
}
Count 與 for each 是互斥的,意思是 resource block 中只能使用其中一個 meta-argument,一起使用的話會在 validate 出 syntax error
for each 使用 input variable 的 key 作為 key,count 使用則搭配 count.index,在 collection 取得參數值
首先是 node pool 範例,使用 for each meta-argument
# modules/kubernetes_cluster/node_pool.tf
resource "azurerm_kubernetes_cluster_node_pool" "main" {
for_each = var.node_pools
name = each.value.name
...
}
var.node_pools
# modules/kubernetes_cluster/node_pool.tf
resource "azurerm_kubernetes_cluster_node_pool" "main" {
count = length(var.node_pools)
name = var.node_pools[count.index].name
...
}
展開變成
# modules/kubernetes_cluster/node_pool.tf
resource "azurerm_kubernetes_cluster_node_pool" "main[0]" {
name = var.node_pools[0].name
...
}
resource "azurerm_kubernetes_cluster_node_pool" "main[1]" {
name = var.node_pools[1].name
...
}
注意:上面使用 count 與 for each 的取值方式不同,這裡會可能造成 count.index 的錯亂
var.node_pools
有改變, plan 的時候重新計算 resource block,便有可能導致順序錯亂Terraform 官方在 When to use count and for each 說明 count 與 for each 建議的使用時機,已經不建議如此使用 count 了
那為何 count 還是會存在?是歷史緣故 resource 中的 count 支援版本很早,for each 要到 0.12 之後的 terraform 版本才支援。也就是說,古人沒有 for each 可以用被迫使用 count + count.index
Terraform 官方在 When to use count and for each 說明 count 與 for each 建議的使用時機
能用 for each 的時候就用 for each
熟 golang 的不妨看一下 source code