如何从地形中所有可用区域中选择不同的子网

问题描述

当尝试通过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的输出

解决方法

听起来这个问题分解成两个较小的问题:

  1. 确定每个子网的可用区域。
  2. 对于每个不同的可用性区域,选择属于它的任何一个子网。 (我在这里假设,如果两个子网都位于同一可用区中,则没有理由偏爱一个子网。)

对于第一步,如果我们当前的子网尚未由当前配置管理(在这里似乎是这种情况,您是从输入变量中接收它们的),那么我们可以使用{{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]
      ]
      

这两个更改实际上都不会立即影响行为,但是这样的添加对将来的维护者可能会有所帮助,因为他们阅读了以前可能并不熟悉的模块,因此他们无需阅读整个模块即可理解。一小部分。