当使用资源for_each 时,我们的任务始终是编写一个表达式来生成一个映射,其中每个我们要创建的实例都有一个元素。在这种情况下,这似乎是一个表达式,可以将包含计数的单个对象扩展为计数中给定数字的多个对象。
我们可以在 Terraform 中使用的构建块是:
让我们一步一步来。我们将从您现有的表达式开始从 JSON 文件加载数据:
locals {
services = [
jsondecode(file("${path.module}/test_service_1.json")),
jsondecode(file("${path.module}/test_service_2.json")),
]
}
这个表达式的结果是一个对象列表,每个文件一个。接下来,我们将这些对象中的每一个展开成一个对象列表,其长度在instance_count 中给出:
locals {
service_instance_groups = [
for svc in local.services : [
for i in range(1, svc.instance_count+1) : {
instance_name = "${svc.instance_name}-${i}"
instance_type = svc.instance_type
subnet_type = svc.subnet_type
elb = svc.elb
data_volume = svc.data_volume
}
]
]
}
此结果是对象列表的列表,由于将值 i 连接到末尾,每个对象将具有唯一的 instance_name 值。
要使用for_each,虽然我们需要一个每个实例一个元素的平面集合,所以我们将使用flatten 函数来实现:
locals {
service_instances = flatten(local.service_instance_groups)
}
现在我们又得到了一个对象列表,但有六个元素(两个输入对象各三个)而不是两个。
最后,我们需要将该列表投影为地图,其键是 Terraform 将用于跟踪实例的唯一标识符。我通常更喜欢直接在 for_each 参数中执行此最后一步,因为此结果特定于该用例,不太可能在模块中的其他任何地方使用:
resource "aws_instance" "test_instance" {
for_each = {
for inst in local.service_instances : inst.instance_name => inst
}
ami = "amzn-ami-hvm-2018.03.0.20200206.0-x86_64-gp2"
instance_type = each.value.instance_type
tags = {
Name = "prod-app-${each.key}"
Env = "prod"
}
}
这应该会导致 Terraform 计划使用aws_instance.test_instance["front-2"] 之类的地址创建实例。
我分别编写了上述每个步骤以解释每个步骤的目标,但实际上我通常会在一个表达式中同时执行 service_instance_groups 和 service_instances 步骤,因为中间的 service_instance_groups 结果不太可能在其他地方重复使用。将所有内容整合到一个示例中,然后:
locals {
services = [
jsondecode(file("${path.module}/test_service_1.json")),
jsondecode(file("${path.module}/test_service_2.json")),
]
service_instances = flatten([
for svc in local.services : [
for i in range(1, svc.instance_count+1) : {
instance_name = "${svc.instance_name}-${i}"
instance_type = svc.instance_type
subnet_type = svc.subnet_type
elb = svc.elb
data_volume = svc.data_volume
}
]
])
}
resource "aws_instance" "test_instance" {
for_each = {
for inst in local.service_instances : inst.instance_name => inst
}
ami = "amzn-ami-hvm-2018.03.0.20200206.0-x86_64-gp2"
instance_type = each.value.instance_type
tags = {
Name = "prod-app-${each.key}"
Env = "prod"
}
}
作为奖励,除了您在此处询问的内容之外,如果您为这些 JSON 文件提供系统名称并将它们组合到一个子目录中,那么您可以使用 Terraform's fileset function 来自动拾取稍后添加到该目录中的任何新文件,而不更改 Terraform 配置。例如:
locals {
services = [
for fn in fileset("${path.module}", "services/*.json") :
jsondecode(file("${path.module}/${fn}"))
]
}
上面将生成一个列表,其中包含services 子目录中名称以.json 结尾的每个文件的对象。