问题描述
当尝试通过terraform在AWS中创建elb(经典负载均衡器)时,我正在发送从另一个模块创建的公共子网ID列表。在这种情况下,我有4个子网,跨3个az。当我尝试运行terraform时,我有2个来自az-1a的子网,出现错误,提示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]
}
有什么方法可以从给定列表中选择子网,使得我只能从不同的可用区获得子网ID。
subnetid1 -- az1-a
subnetid2 -- az1-b
subnetid3 -- az1-c
subnetid4 -- az1-a
现在我需要获得子网1、2和3或子网2、3和4的输出。
解决方法
听起来这个问题分解成两个较小的问题:
- 确定每个子网的可用区域。
- 对于每个不同的可用性区域,选择属于它的任何一个子网。 (我在这里假设,如果两个子网都位于同一可用区中,则没有理由偏爱一个子网。)
对于第一步,如果我们当前的子网尚未由当前配置管理(在这里似乎是这种情况,您是从输入变量中接收它们的),那么我们可以使用{{3} }读取有关给定ID的子网信息。由于此处有多个子网,因此我们将使用the aws_subnet
data source查找每个子网。
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...
}
}
上面是一个resource for_each
,在这种情况下,它使用后缀...
来激活分组模式,因为我们希望每个发现多个子网可用区。结果,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
中的元素顺序敏感,但是上述 specific 组合隐式地避免了开头toset(var.public_subnets)
中带有for_each
的字符串,它丢弃var.public_subnets
的原始顺序,并使所有下游表达式按词汇顺序的子网ID排序结果。换句话说,这将在进行词法排序时选择id为“最低”的子网。当这样的决定被隐含起来时,我真的不喜欢它,因为这可能会使将来的维护者感到困惑,他们可能会更改设计,并惊讶地发现现在为每个可用性区域选择一个不同的子网。我可以看到几种减轻这种情况的方法,如果我正在编写一个寿命很长的模块,那么我可能会同时做这两种事情:
-
请确保
variable "public_subnets"
的类型约束是type = set(string)
,而不是type = list(string)
,要明确指出,此模块会丢弃调用者给定的子网顺序。如果这样做,您可以将toset(var.public_subnets)
更改为var.public_subnets
,因为它已经是一个集合。 -
在最后的
for
表达式中,为每个可用性区域选择第一个子网,包括对for
expression的显式调用。在我的示例中,此调用对于其余部分的实现方式是多余的,但我认为这对将来的读者是一个很好的线索,因为它使用词法排序来确定要使用哪个子网:subnets = [ for subnet_ids in local.availability_zone_subnets : sort(subnet_ids)[0] ]
-
这两个更改实际上都不会立即影响行为,但是这样的添加对将来的维护者可能会有所帮助,因为他们阅读了以前可能并不熟悉的模块,因此他们无需阅读整个模块即可理解。一小部分。