Day 16-infrastructure 也可以 for each 之三: Count meta-argument

infrastructure 也可以 for each 之三

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

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

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

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


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

实际 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 binding multiple variables

count 历史悠久,可以分享一些常见的用法

variable "vm_names" {
  default = ["vm-1", "vm-2", "vm-3"]
  type = list
}

variable "vm_sizes" {
  default = ["Standard_DS1_v2", "Standard_DS2_v2", "Standard_DS4_v2"]
  type = list
}


resource "azurerm_virtual_machine" "main" {
  count                 = length(var.vm_names)

  name                  = var.vm_names[count.index]
  vm_size               = var.vm_sizes[count.index]
}

上面这个用法的问题

  • 使用两个参数来定义同一个物件,很不直觉
  • 这也是历史缘故,旧版的 type constraint 并不像现在这麽完整,有办法从 map of map 中轻易取值。可能会需要调用许多 function 来取得正确的值
  • 最大的问题是上面提过的,count.index 不保证有序的问题(这个例子是安全的,因为 list 有 built-in index,list[index] 可以取得正确的值)
  • 如果 variable type 是 map 或是 any 的话就会有问题

新版 terraform 请使用

  • vm_namesvm_sizes 合并成为单一 variable
  • 使用 for each 来 iterate 上面这个 map

Count conditional

count 可以接受 0 为参数,意思是就产生 0 个 resource block

resource "azurerm_virtual_machine" "main" {
  count                 = 0

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

这又产生了另外一个用法,来有条件的控制 resource block 与 module

variable "enable_azurerm_virtual_machine" {
  type = bool
  default = false
}

resource "azurerm_virtual_machine" "main" {
  count                 = var.enable_azurerm_virtual_machine ? 1 : 0

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

上面这个范例

  • variable enable_azurerm_virtual_machine == true 的时候,会产生 count = 1,也就是产生一个 azurerm_virtual_machine
  • variable enable_azurerm_virtual_machine == false 的时候,会产生 count = 0,也就是产生一个 azurerm_virtual_machine
  • 每一次 plan variable 都是定值,因此这样的写法,虽然看起来是 dynamic expression,但实际上以 root module 的角度看是 deterministic

实务上这样子的使用情境还算蛮多的,一个 resource / module 启用或不启用

  • for each 也能达成相同效果(如果 for_each 的 argument literate 下去是 empty 的话,for each 出来就会产生 0 个 resource / module

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


<<:  特徵萃取 | ML#Day8

>>:  【Day2】:初识STM32

Multicasting for RxJava

在进入正题之前先让大家看看在 Reactive Programming 中的一种使用案例: val ...

【Day 2】Google Apps Script - 平台介绍

什麽是 Google Apps Script ??。 今日要点: 》Google Apps Scr...

[前端暴龙机,Vue2.x 进化 Vue3 ] Day5. Vue的起手开发

接下来,开始看看如何着手进行 Vue 的开发吧 这边都是透过最原始、最简单的网页开发模式进行,所以不...

MySQL学习_Day3

学习内容 资料型态、取得资料 简介 资料型态(Data Type),又称资料类型或资料型别,是用来约...

Day 04 - 资料库服务也一把抓的RDS

来到了第四天,我们一起来看看RDS是什麽。 RDS是Relational Database Serv...