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

ANU*_*uri 10 amazon-web-services terraform terraform-provider-aws

当尝试通过 terraform 在 AWS 中创建 elb(经典负载均衡器)时,我发送了从另一个模块创建的公共子网 ID 列表。在本例中,我有 4 个子网,跨越 3 个可用区。当我尝试运行 terraform 时,我有来自 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]
}
Run Code Online (Sandbox Code Playgroud)

有什么方法可以从给定列表中选择子网,这样我只能从不同的 AZ 获取子网 id。

subnetid1 -- az1-a
subnetid2 -- az1-b
subnetid3 -- az1-c
subnetid4 -- az1-a
Run Code Online (Sandbox Code Playgroud)

现在我需要获得子网 1,2 和 3 或子网 2,3 和 4 的输出。

Mar*_*ins 18

听起来这个问题分解为两个更小的问题:

  1. 确定每个子网的可用区域。
  2. 对于每个不同的可用区域,选择属于该区域的任一子网。(我在这里假设,如果两个子网都位于同一可用区,则没有理由优先选择一个子网而不是另一个子网。)

对于第一步,如果我们还没有由当前配置管理的相关子网(这里似乎就是这种情况 - 您正在从输入变量接收它们),那么我们可以使用数据aws_subnet来读取有关的信息给定其 ID 的子网。因为这里有多个子网,所以我们将使用资源for_each来查找每个子网。

data "aws_subnet" "public" {
  for_each = toset(var.public_subnets)

  id = each.key
}
Run Code Online (Sandbox Code Playgroud)

上面的内容将data.aws_subnet.public显示为从子网 ID 到子网对象的映射,每个子网对象都具有availability_zone指定每个子网属于哪个区域的属性。对于我们的第二步,反转该映射会更方便,因此键是可用区域,值是子网 ID:

locals {
  availability_zone_subnets = {
    for s in data.aws_subnet.public : s.availability_zone => s.id...
  }
}
Run Code Online (Sandbox Code Playgroud)

上面是一个for表达式,在本例中使用...后缀来激活分组模式,因为我们期望每个可用区找到多个子网。因此,local.availability_zone_subnets将生成一张从可用区域名称到一个或多个子网 ID 列表的映射,如下所示:

{
  "az1-a" = ["subnetid1", "subnetid4"]
  "az1-b" = ["subnetid2"]
  "az1-c" = ["subnetid3"]
}
Run Code Online (Sandbox Code Playgroud)

这为我们提供了实现问题第二部分所需的信息:从每个列表中选择任何一个元素。“任何一个”最简单的定义是通过使用[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"
  }
}
Run Code Online (Sandbox Code Playgroud)

上述解决方案有一些需要考虑的重要注意事项:

  • 取每个子网 id 列表的第一个元素意味着配置可能对 中 元素的顺序敏感var.public_subnets,但是上面的这种特定组合隐式地避免了toset(var.public_subnets)最初的 中的元素for_each,这会丢弃 的原始顺序var.public_subnets并导致所有下游表达式按子网 ID 的词法排序对结果进行排序。换句话说,这将在进行词法排序时选择 id 为“最低”的子网。

    我真的不喜欢这种决定被隐含,因为它可能会让未来的维护人员感到困惑,他们可能会改变设计,并惊讶地发现它现在为每个可用区域选择不同的子网。我可以看到几种不同的方法来缓解这种情况,如果我正在编写一个长期存在的模块,我可能会同时使用这两种方法:

    • 确保其类型约束是variable "public_subnets"has ,而不是,明确该模块丢弃调用者给出的子网顺序。如果这样做,您可以更改为 just ,因为它已经是一个集合了。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]
      ]
      
      Run Code Online (Sandbox Code Playgroud)

这些更改实际上都不会立即影响行为,但是这样的添加对未来的维护人员很有帮助,因为他们阅读以前可能不熟悉的模块,因此他们不需要阅读整个模块来理解较小的部分它的。