Terraform:使用for_each创建多个实例

时间:2020-03-17 05:13:47

标签: terraform terraform-provider-aws

我希望获得Terraform的count / for_each函数的帮助。

目标是将多个json文件(当前两个)读取到地图列表中,并使用特定的命名约定创建特定数量的aws_instances。

配置

cat test_service_1.json
{
      "instance_name": "front",
      "instance_count": "3",
      "instance_type": "t2.micro",
      "subnet_type": "private",
      "elb": "yes",
      "data_volume": ["no", "0"]
}

cat test_service_2.json
{
      "instance_name": "back",
      "instance_count": "3",
      "instance_type": "t2.micro",
      "subnet_type": "private",
      "elb": "yes",
      "data_volume": ["no", "0"]
}

cat main.tf
locals {
  services = [jsondecode(file("${path.module}/test_service_1.json")),
  jsondecode(file("${path.module}/test_service_2.json"))]
}

resource "aws_instance" "test_instance" {
  ami           = "amzn-ami-hvm-2018.03.0.20200206.0-x86_64-gp2"
  instance_type = "t2.micro"
  tags = merge(
    map("Name", "prod-app-?"),
    map("env", "prod")
  )
}

最终,我希望代码遍历两个json文件并创建:

prod-front-1
prod-front-2
prod-front-3
prod-back-1
prod-back-2
prod-back-3

我可以使用[count.index +1]来做到这一点,但我不知道如何遍历一张以上的地图。

1 个答案:

答案 0 :(得分:5)

使用资源for_each时,我们的任务始终是编写一个生成映射的表达式,其中每个实例我们要创建一个元素。在这种情况下,这似乎是一个表达式,可以将包含一个计数的单个对象扩展为该计数中给定数量的多个对象。

我们可以在Terraform中使用的构建基块是:

让我们逐步进行此操作。我们将从您现有的表达式开始,以从JSON文件加载数据:

locals {
  services = [
    jsondecode(file("${path.module}/test_service_1.json")),
    jsondecode(file("${path.module}/test_service_2.json")),
  ]
}

此表达式的结果是对象列表,每个文件一个。接下来,我们将每个对象扩展为对象列表,其长度在instance_count中给出:

locals {
  service_instance_groups = [
    for svc in local.services : [
      for i in range(1, svc.instance_count+1) : {
        instance_name = "${svc.instance_name}-${i}"
        instance_type = svc.instance_type
        subnet_type   = svc.subnet_type
        elb           = svc.elb
        data_volume   = svc.data_volume
      }
    ]
  ]
}

此操作的结果是对象列表的列表,由于将值instance_name连接到末尾,每个对象将具有唯一的i值。

要使用for_each,尽管我们需要一个每个实例一个元素的平面集合,所以我们将使用flatten函数来实现:

locals {
  service_instances = flatten(local.service_instance_groups)
}

现在我们又有了一个对象列表,但是有六个元素(两个输入对象中的三个),而不是两个。

最后,我们需要将该列表投影为一个地图,其键是Terraform用来跟踪实例的唯一标识符。我通常更喜欢直接在for_each参数内部执行此最后一步,因为此结果特定于该用例,并且不太可能在模块的其他任何地方使用:

resource "aws_instance" "test_instance" {
  for_each = {
    for inst in local.service_instances : inst.instance_name => inst
  }

  ami           = "amzn-ami-hvm-2018.03.0.20200206.0-x86_64-gp2"
  instance_type = each.value.instance_type
  tags = {
    Name = "prod-app-${each.key}"
    Env  = "prod"
  }
}

这应该导致Terraform规划创建具有aws_instance.test_instance["front-2"]之类地址的实例。

我分别编写了上述每个步骤,以解释每个步骤都实现了什么,但是实际上,我通常会在一个表达式中一起执行service_instance_groupsservice_instances步骤,因为该中间步骤service_instance_groups结果不太可能在其他地方重用。将所有内容整合到一个示例中,然后:

locals {
  services = [
    jsondecode(file("${path.module}/test_service_1.json")),
    jsondecode(file("${path.module}/test_service_2.json")),
  ]
  service_instances = flatten([
    for svc in local.services : [
      for i in range(1, svc.instance_count+1) : {
        instance_name = "${svc.instance_name}-${i}"
        instance_type = svc.instance_type
        subnet_type   = svc.subnet_type
        elb           = svc.elb
        data_volume   = svc.data_volume
      }
    ]
  ])
}

resource "aws_instance" "test_instance" {
  for_each = {
    for inst in local.service_instances : inst.instance_name => inst
  }

  ami           = "amzn-ami-hvm-2018.03.0.20200206.0-x86_64-gp2"
  instance_type = each.value.instance_type
  tags = {
    Name = "prod-app-${each.key}"
    Env  = "prod"
  }
}

作为奖励,除了您在此处提出的要求之外,如果为这些JSON文件提供系统名称并将它们分组到一个子目录中,则可以使用Terraform's fileset function自动拾取其中添加的任何新文件。目录,而不更改Terraform配置。例如:

locals {
  services = [
    for fn in fileset("${path.module}", "services/*.json") :
    jsondecode(file("${path.module}/${fn}"))
  ]
}

上面的代码将产生一个列表,其中包含services子目录中每个名称以.json结尾的文件的对象。