Day 15-infrastructure 也可以 for each 之二: for_each 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


Terraform meta-argument: for each

这边细讲 for_each meta-argument 的相关使用

  • 使用 for_each 的 resource block 中,可以在使用 each object,来取得 for_each 内的 key 与 value

node_pools 这个变数为例,本身是 module 的 intput variable

  • input type 定义是一个 map(any)
  • 实际上 map of object({ name=string, ... })
  • 使用 any 只是偷懒,terraform 不会 validate input 内部 object 的 type constraint
  • 由於 validate 时没有检查,就是看 runtime 的时候,去 input variable 内部取值有无错误
  • 使用 any 太偷懒了,应该改成明确的 type constraint
  • type 的细节,请见後面的 Terraform Type 说明
# modules/kubernetes_cluster/variables.tf

# var.node_pools is a map of any
variable "node_pools" {
  type    = map(any)
  default = {}
}

实际 node_pools input variable 内容物可能涨这样

  • 现阶段先看这个 variable map 的 member,有两个 member
  • spot: {...}
  • on-demand: {...}
node_pools = {
  spot = {...},
  on-demand = {...}
}

然後回头看 for_each

  • for_each = var.node_pools 指的是,为每个 node_pools 的 member 产生一组 resource
resource "azurerm_kubernetes_cluster_node_pool" "main" {
  for_each              = var.node_pools

  name                  = each.value.name
  ...
}

将 meta-argument 展开後,实际上会是

  • resource block {} 原本应该 evaluate 对应一个 azurerm_kubernetes_cluster_node_pool resource
  • 使用 for_each meta-argument,让 terraform 知道
  • each.key 会变成 map member 的 key,ex spot, on-demand, ...
  • each.value 会变成 map member 的 value,以目前这个 input 的 type,member value 会是 {...} 仍是一个 map
  • each.value.name 会变成取得 member value,然後在尝试 value 内部,取得 name 这个 field 的值
resource "azurerm_kubernetes_cluster_node_pool" "main[spot]" {
  name                  = var.node_pools.spot.name
  ...
}

resource "azurerm_kubernetes_cluster_node_pool" "main[on-demand]" {
  name                  = var.node_pools.on-demand.name
  ...
}

至於实际 each.value.name 会取到什麽值,就依照各个 member value 去寻找

node_pools = {
  spot = {
    name       = "spot"
    ...
  },
  on_demand = {
    name       = "on-demand"
    ...
  }
}

最後变成,两个 resource block

resource "azurerm_kubernetes_cluster_node_pool" "main[spot]" {
  name                  = "spot"
  ...
}

resource "azurerm_kubernetes_cluster_node_pool" "main[on-demand]" {
  name                  = "on-demand"
  ...
}

for each meta-argument pros & cons

比较两种写法

resource "azurerm_kubernetes_cluster_node_pool" "main" {
  for_each              = var.node_pools

  name                  = each.value.name
  kubernetes_cluster_id = azurerm_kubernetes_cluster.main.id
  vm_size               = each.value.vm_size
  node_count            = each.value.node_count
  mode                  = each.value.mode
  priority              = each.value.priority
  node_labels           = each.value.node_labels
  node_taints           = each.value.node_taints
}
resource "azurerm_kubernetes_cluster_node_pool" "main[spot]" {
  name                  = "spot"
  ...
}

resource "azurerm_kubernetes_cluster_node_pool" "main[on-demand]" {
  name                  = "on-demand"
  ...
}

好处

  • 语法精简
  • 同类型的参数可以使用 default,不是重要的 input 不用给就可以使用 default
  • 维护更新时,只要改一个 resource block,就全部的 resource 都更新了
  • 使用 meta-argument 才可能管理更大量的 resource,ex. 10 个或 100 个相似的 resource

坏处

  • 可读性下降,需要人脑
  • 参数取值复杂,需要一层一层 map 下去找参数
  • 增加 debug 的难度

实务上我们都会选择使用 for_each meta-argument,牺牲可读性换取精简的程序码

  • 精简的程序码,维护上还是会有极大的好处
  • 不用担心 spot resource block 与 on-demand block 写法不同,造成额外的问题

For each limitation

Terraform for each meta-argument 也有许多限制

  • for_each 的 variable 必须是 deterministic,意思是必须是定值
  • 不能使用 conditional expression (ex. if else 或是三元判断 ? )
  • 也不能倚赖不定值 function 的 results (ex. uuid, bcrypt, or timestamp...),这些 function 的结果,会在 main evaluation 时延後计算,导致进入 main evaluation 时 for_each 的参数其实仍是 undefined
  • 原因也很简单,需要在计算 meta-argument 时决定最终会有几个 resource,如果不确定 resource block 数量,便无法计算下个 workflow 的内容

sentisive 的参数也无法使用在 for_each

  • for_each 需要的可见度,会无法取得 sensitive 数值

for each chaining

variable "vpcs" {
  type = map(object({
    cidr_block = string
  }))
}

resource "aws_vpc" "example" {
  # One VPC for each element of var.vpcs
  for_each = var.vpcs

  # each.value here is a value from var.vpcs
  cidr_block = each.value.cidr_block
}

resource "aws_internet_gateway" "example" {
  # One Internet Gateway per VPC
  for_each = aws_vpc.example

  # each.value here is a full aws_vpc object
  vpc_id = each.value.id
}

output "vpc_ids" {
  value = {
    for k, v in aws_vpc.example : k => v.id
  }

  # The VPCs aren't fully functional until their
  # internet gateways are running.
  depends_on = [aws_internet_gateway.example]
}

<<:  Day4 - 建立Android模拟器

>>:  Day61 (Vue)

Day15 Loops(Ⅱ)

While顾名思义就是,当…,所以当我们假设的条件成立时,就会执行回圈内的东西,否则就执行回圈外的程...

JS语法学习Day5

学习目标 if判断&switch case 、取得html元素 if判断 if(条件)-&g...

二元树左到右查找 - DAY 16

前序检查(preorder) 中序检查(inorder) 後序检查(postorder) 後序检查来...

The Twelve-Factor App

三十天很快要到了尾声了,今天要来介绍 The Twelve-Factor App(下称 12 Fac...

[Day25] Flutter GetX API AnimatedSwitcher

上一篇介绍了Shimmer这个第三方 并建立了自己想要的Widget 这一篇将实际结合API fet...