Day 19-infrastructure 也可以 for each 之四:for & dynamic block

这篇是 infrastructure 也可以 for each 第四篇,上次漏发了,今天补发

本章介绍 terraform 的 for syntax 与 dynamic block,快速迭代 resource block

课程内容与代码会放在 Github 上: https://github.com/chechiachang/terraform-30-days

赛後文章会整理放到个人的部落格上 http://chechia.net/

追踪粉专可以收到文章的主动推播

https://ithelp.ithome.com.tw/upload/images/20210901/20120327NvpHVr2QC0.jpg


for Expression

For expression 是 terraform configuration language (hcl) 内的 syntax 语法
for each meta-argument 是

  • for_each 是在计算 resource block {} 的 meta 时使用
  • for expression 是在处理 values 的运算

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]
}

两个语法的定义位阶不同

  • 一个是 terraform 的功能
  • 一个是 hcl 就定义的语法

for expression as for each argument

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 的限制

  • for expression evaluation 需要是决定性的,不能有 conditional expression

Homework: for examples

请依照Terraform 官方文件 for example,尝试每个 example,以熟悉 for syntax 使用

Dynamic block

想要管理很多类似的多个 resource block,我们可以参考使用 meta-argument,来管理 resource block

有个时候,一个 resource block 中,会有 repeatable nested blocks arguments,例如

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"]
    }
  }
  ...
}

Multi-level nested dynamic block

许多高阶程序语言有提供 nested loop,terraform dynamic block 也提供 nested

  • 这边范例使用的 variable 是 map of object
  • object 中的 origins field type 是 set of object
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 的问题

  • 可读性已经非常差了
  • debug 的时候更痛苦
  • 由於是 dynamic block,需要 evaluation 的时候才会展开这些 block
  • 需要善用 console debug,才知道展开到底长什麽样子

通常比较好的 provider (例如三大公有云)会提供另外的 resource block 来管理这些 nested block

  • 可以写 load_balancer resource
  • 加上 origin_group resource
  • 後使用 attachment,把两个 resource 关联起来

Note

  • for expression 是 hcl syntax,意思是只要以 hcl 为底层的 configutation language 都可以使用(ex. tcl,vault config,consul config,...)
  • for each meta-argument 是 terraform 的 resource block argument,terraform 中才可以使用,并且用来操作 resource block
  • dynamic block 是 terraform 的 in-resource argument,可以视作 resource block {} 中的一个特别的参数,用来动态产生 repeatable nested block

Source code

熟 golang 的不妨看一下 source code


Count

要管理重复的 resource block ,Terrafrom 还提供另一个 meta-argument count。范例

  • 建立一组 azurerm_virtual_machine
  • 使用 count meta-argument,告诉 terraform 这组 resource block 要有三个
  • 希望 vm.name 是 unique 方便辨识,所以使用 ${count.index} 取得每个 count 产生的 resource 的 index
resource "azurerm_virtual_machine" "main" {
  count                 = 3

  name                  = "${var.prefix}-vm-${count.index}"
  ...
}

实际 output 会类似

  • 产生一组 recource block
  • state 路径在 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

Count index Issue

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
  ...
}
  • 我们可以更改 for each,改用 count meta-argument 来描述
  • 使用 length() function 取得 map of object 的 member 数量,作为 count 参数
  • each 也改用 count.index 来存取 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 的错乱

  • terraform 的 map 是 unordered map,本身没有 index
  • 使用 for each 时,map 是 order 是因为依照 key 的 alphabatical order,依据
  • 使用 count.index 时,不保证 index 与 key 的顺序相同
  • 如果上层 var.node_pools 有改变, plan 的时候重新计算 resource block,便有可能导致顺序错乱
  • 加上由於这个例子中的 resource 没有依赖性,是平行化产生的,本身不保证先後顺序,可能会产生问题。

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 久了,还是有机会在比较旧的 module 立面看到 count 的大量使用

Count vs for each

Terraform 官方在 When to use count and for each 说明 count 与 for each 建议的使用时机

  • 如果产生一组全部都相同的 resource block,可以使用 count
  • 如果内部有变数处理,或是取用 input argument 的值,使用 for each 会比较安全

能用 for each 的时候就用 for each

  • for each 能够使用 map
  • 使用 collection

Source code

熟 golang 的不妨看一下 source code


<<:  Day 4 - 破解骇客的思考模式

>>:  EP 11: Passing Data for Navigation in TopStore App - II

三分钟就可以 CentOS 7 安装 LogAnalyzer

首先 CentOS 7 做 mini install putty ssh 登入安装 LogAnaly...

聊系统文字工具与服务日志可视性浅谈的缘由

这系列只是这两年自己的工作学习笔记 内容相当的杂, 但希望藉由这次的铁人赛期间顺便复习 一开始会复习...

利用大数据分析预测MLB胜负(中)

在上一篇文章中,我们介绍作者如何分析MLB赛事,并找出影响比赛胜负较为重要的因子,而今天我们就来看看...

Day 3 | Dart 基本介绍 - Dart vs JS

Dart是什麽? Dart 是一个静态强型别的语言,同时支援物件导向程序设计(OOP)及函数式程序设...

Day3 自订电脑开机讯息

上一回,我提到 CC: Tweaked 的 Computer 方块有许多基础指令 但我不打算逐一介绍...