跳至主要内容

for_each 元参数

默认情况下,资源块 配置一个真实的 基础架构对象(类似地,模块块 将子模块的内容包含到配置中一次)。但是,有时您希望管理几个类似的对象(例如固定数量的计算实例),而无需为每个对象编写单独的块。OpenTofu 有两种方法可以做到这一点:countfor_each

如果资源或模块块包含一个 for_each 参数,其值为一个映射或一组字符串,OpenTofu 将为该映射或集合中的每个成员创建一个实例。

基本语法

for_each 是 OpenTofu 语言定义的元参数。它可以与模块一起使用,也可以与每种资源类型一起使用。

for_each 元参数接受一个映射或一组字符串,并为该映射或集合中的每个项目创建一个实例。每个实例都具有与之关联的不同的基础架构对象,并且在应用配置时,每个实例都会分别创建、更新或销毁。

映射

代码块
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}

字符串集

代码块
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}

子模块

代码块
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
代码块
# publish_bucket/bucket-and-cloudfront.tf
variable "name" {} # this is the input parameter of the module

resource "aws_s3_bucket" "example" {
# Because var.name includes each.key in the calling
# module block, its value will be different for
# each instance of this module.
bucket = var.name

# ...
}

resource "aws_iam_user" "deploy_user" {
# ...
}

each 对象

在设置了 for_each 的块中,表达式中可以使用额外的 each 对象,这样您就可以修改每个实例的配置。此对象具有两个属性

  • each.key — 与该实例对应的映射键(或集合成员)。
  • each.value — 与该实例对应的映射值。(如果提供的是集合,则它与 each.key 相同。)

for_each 中使用的值的限制

映射的键(或字符串集的情况下所有值)必须是已知的值,否则您将收到错误消息,指出 for_each 具有在应用之前无法确定的依赖项,并且可能需要 -target

for_each 键不能是(或依赖于)不纯函数的结果,包括 uuidbcrypttimestamp,因为它们的计算是在主计算步骤期间延迟的。

敏感值,例如 敏感输入变量敏感输出敏感资源属性,不能用作 for_each 的参数。for_each 中使用的值用于标识资源实例,并且始终会在 UI 输出中公开,因此不允许使用敏感值。尝试使用敏感值作为 for_each 参数会导致错误。

如果您将包含敏感数据的 值转换为 for_each 中使用的参数,请注意 OpenTofu 中的大多数函数如果给定的参数包含任何敏感内容,都会返回敏感结果。在许多情况下,您可以通过使用 for 表达式来实现与用于此目的的函数类似的结果。例如,如果您想调用 keys(local.map),其中 local.map 是一个包含敏感值(但非敏感键)的对象,您可以使用 toset([for k,v in local.map : k]) 创建一个要传递给 for_each 的值。

for_each 中使用表达式

for_each 元参数接受 map 或 set 表达式。但是,与大多数参数不同,for_each 的值必须在 OpenTofu 执行任何远程资源操作之前已知。这意味着 for_each 无法引用任何直到配置应用后才已知的资源属性(例如,在创建对象时由远程 API 生成的唯一 ID)。

for_each 的值必须是 map 或 set,每个元素对应一个所需的资源实例。要使用序列作为 for_each 值,必须使用显式返回 set 值的表达式,例如 toset 函数。为了防止转换过程中出现意外情况,for_each 参数不会隐式将列表或元组转换为 set。如果您需要根据嵌套的数据结构或来自多个数据结构的元素的组合来声明资源实例,可以使用 OpenTofu 表达式和函数来推导出合适的值。例如

在资源之间链接 for_each

因为使用 for_each 的资源在其他地方用作表达式时表现为对象 map,所以您可以在两个对象集之间存在一对一关系的情况下,直接将一个资源用作另一个资源的 for_each

例如,在 AWS 中,aws_vpc 对象通常与提供该 VPC 额外服务的多个其他对象相关联,例如“Internet 网关”。如果您使用 for_each 声明多个 VPC 实例,那么您可以将该 for_each 链接到另一个资源,为每个 VPC 声明一个 Internet 网关

代码块
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]
}

这种链接模式明确且简洁地声明了 Internet 网关实例和 VPC 实例之间的关系,这告诉 OpenTofu 预期两者的实例键始终一起更改,并且通常也使配置更容易让人类维护人员理解。

引用实例

当设置了 for_each 时,OpenTofu 区分块本身及其关联的多个资源或模块实例。实例通过从提供给 for_each 的值中获取的 map 键(或 set 成员)来标识。

  • <类型>.<名称>module.<名称>(例如,azurerm_resource_group.rg)引用块。
  • <类型>.<名称>[<键>]module.<名称>[<键>](例如,azurerm_resource_group.rg["a_group"]azurerm_resource_group.rg["another_group"] 等)引用单个实例。

这与没有 countfor_each 的资源和模块不同,后者无需索引或键即可引用。

同样,来自具有多个实例的子模块的资源在计划输出和其他 UI 位置中会以 module.<名称>[<键>] 为前缀。对于没有 countfor_each 的模块,地址将不包含模块索引,因为模块的名称足以引用该模块。

使用 Set

OpenTofu 语言没有 set 值 的字面语法,但可以使用 toset 函数将字符串列表显式转换为 set

代码块
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}

resource "aws_instance" "server" {
for_each = local.subnet_ids

ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set

tags = {
Name = "Server ${each.key}"
}
}

从列表到 set 的转换会丢弃列表中项目的顺序并删除任何重复的元素。toset(["b", "a", "b"]) 将生成一个仅包含 "a""b" 的 set,顺序不定;第二个 "b" 被丢弃。

如果您正在编写一个使用 输入变量 作为 for_each 的字符串集的模块,可以将其类型设置为 set(string) 以避免需要显式类型转换

代码块
variable "subnet_ids" {
type = set(string)
}

resource "aws_instance" "server" {
for_each = var.subnet_ids

# (and the other arguments as above)
}