当试图通过terraform在AWS中创建elb(经典负载均衡器(时,我发送了一个从另一个模块创建的公共子网ID列表。在这种情况下,我有4个子网,它们横跨3个az。当我试图运行地形时,我有来自az-1a的2个子网,我收到一个错误,说same az can't be used twice for ELB
resource "aws_elb" "loadbalancer" {
name = "loadbalancer-terraform"
subnets = var.public_subnets
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
depends_on = [aws_autoscaling_group.private_ec2]
}
有没有任何方法可以让我从给定的列表中选择子网,这样我就只能从不同的AZ中获得子网id。
subnetid1 -- az1-a
subnetid2 -- az1-b
subnetid3 -- az1-c
subnetid4 -- az1-a
现在我需要得到一个输出子网-1,2和3或子网-2,3和4。
听起来这个问题分解为两个较小的问题:
- 确定每个子网的可用性区域
- 对于每个不同的可用性区域,选择属于它的任何一个子网
对于第一步,如果我们还没有由当前配置管理的有问题的子网(这里似乎是这样——您从输入变量接收它们(,那么我们可以使用aws_subnet
数据源读取给定ID的子网的信息。因为您在这里有多个子网,所以我们将使用资源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
表达式,在这种情况下,它使用...
后缀来激活分组模式,因为我们预计每个可用性区域会找到多个子网。因此,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
中的元素顺序敏感,但上述特定组合隐含地避免了初始for_each
中的toset(var.public_subnets)
,其丢弃CCD_ 13的原始排序并且使得所有下游表达式按照子网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] ]
这两项更改实际上都不会立即影响行为,但像这样的添加对未来的维护人员有帮助,因为他们阅读了以前可能不熟悉的模块,所以他们不需要阅读整个模块来理解其中的一小部分。