听起来这个问题分解成两个更小的问题:
- 确定每个子网的可用区。
- 对于每个不同的可用区,选择属于它的任何一个子网。 (我在这里假设如果两个子网都在同一个 AZ 中,则没有理由偏爱一个子网而不是另一个子网。)
对于第一步,如果我们还没有由当前配置管理的子网(这里似乎就是这种情况——您从输入变量接收它们),那么我们可以使用the aws_subnet data source在给定 ID 的情况下读取有关子网的信息。因为您在这里有多个子网,所以我们将使用resource for_each 来查找每个子网。
data "aws_subnet" "public" {
for_each = toset(var.public_subnets)
id = each.key
}
以上内容将使data.aws_subnet.public 显示为从子网id 到子网对象的映射,并且每个子网对象都有availability_zone 属性指定每个子网属于哪个区域。对于我们的第二步,反转该映射会更方便,因此键是可用区,值是子网 ID:
locals {
availability_zone_subnets = {
for s in data.aws_subnet.public : s.availability_zone => s.id...
}
}
上面是for expression,在本例中使用... 后缀来激活分组模式,因为我们希望每个可用区找到多个子网。因此,local.availability_zone_subnets 将是从可用区名称到一个或多个子网 ID 列表的映射,如下所示:
{
"az1-a" = ["subnetid1", "subnetid4"]
"az1-b" = ["subnetid2"]
"az1-c" = ["subnetid3"]
}
这为我们提供了实现问题第二部分所需的信息:从每个列表中选择任何一个元素。 “任何一个”的最简单定义是获取第一个元素,即使用[0] 获取第一个元素。
resource "aws_elb" "loadbalancer" {
depends_on = [aws_autoscaling_group.private_ec2]
name = "loadbalancer-terraform"
subnets = [for subnet_ids in local.availability_zone_subnets : subnet_ids[0]]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
}
上述解决方案有一些需要注意的注意事项:
-
获取每个子网 id 列表的第一个元素意味着配置可能对 var.public_subnets 中的元素顺序敏感,但上面的这种特殊组合隐含地避免了 @987654338 @ 在初始 for_each 中,它丢弃了 var.public_subnets 的原始排序,并导致所有下游表达式按子网 ID 的词法排序对结果进行排序。换句话说,这将在进行词法排序时选择 id 为“最低”的子网。
我真的不喜欢这种隐含的决定,因为这可能会让未来的维护者感到困惑,他们可能会改变设计并惊讶地看到它现在为每个可用区选择不同的子网。我可以看到几种不同的方法来缓解这种情况,如果我正在编写一个长期存在的模块,我可能会同时这样做:
-
确保variable "public_subnets" 具有type = set(string) 作为其类型约束,而不是type = list(string),以明确该模块丢弃调用者给定的子网顺序。如果这样做,您可以将 toset(var.public_subnets) 更改为 var.public_subnets,因为它已经是一个集合了。
-
在最后的for 表达式中为每个可用区选择第一个子网,包括对sort 的显式调用。这个调用与我的示例中其余部分的实现方式是多余的,但我认为这是一个很好的线索,可以让未来的读者知道它正在使用词法排序来决定使用哪个子网:
subnets = [
for subnet_ids in local.availability_zone_subnets : sort(subnet_ids)[0]
]
这些更改实际上都不会立即影响行为,但是像这样的添加对于未来的维护人员可能会有所帮助,因为他们阅读了以前可能不熟悉的模块,因此他们不需要阅读整个模块即可理解一小部分。