Terraform: создание нескольких экземпляров с помощью for_each - PullRequest
0 голосов
/ 17 марта 2020

Мне бы хотелось помочь с функциями Terraform count / for_each.

Цель - прочитать несколько json файлов (текущих двух) в список карт и создать определенное c количество aws_instances. с указанием c соглашения об именах.

Конфигурации

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")
  )
}

В конце концов я хочу, чтобы код go для обоих файлов json и создать:

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

Я могу сделать это с [count.index +1], но я не знаю, как l oop через более чем одну карту.

1 Ответ

0 голосов
/ 26 марта 2020

При использовании ресурса for_each наша задача всегда состоит в том, чтобы написать выражение, которое создает карту, в которой есть один элемент на экземпляр, который мы хотим создать. В этом случае это, кажется, выражение, которое может расширить один объект, содержащий счет, чтобы вместо этого быть несколькими объектами из числа, указанного в счетчике.

Строительные блоки, которые мы можем использовать для этого в Terraform: :

  • for выражений для проецирования одного значения коллекции в другое.
  • Функция range для генерации последовательностей целых чисел с учетом числа.
  • Функция flatten для преобразования нескольких вложенных списков в один плоский список.

Давайте рассмотрим этот шаг шаг за шагом. Мы начнем с вашего существующего выражения для загрузки данных из файлов 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, потому что этот результат указывает c на этот вариант использования и вряд ли будет использоваться где-либо еще в модуле:

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_groups и service_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 файлам систематические c имена и группу их можно объединить в подкаталог, после чего вы можете использовать fileset функцию *1056* Terraform , чтобы автоматически выбирать любые новые файлы, добавленные в этот каталог позже, без изменения конфигурации Terraform. Например:

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

В приведенном выше примере будет создан список, содержащий объект для каждого из файлов в подкаталоге services, имена которых заканчиваются на .json.

...