Terraform: инфраструктура на уровне кода — страница 28 из 65

• Выражение for_each для циклического перебора ресурсов и их вложенных блоков.

• Выражение for для циклического перебора списков и ассоциативных массивов.

• Строковая директива for для циклического перебора списков и ассоциативных массивов внутри строк.

Рассмотрим их одну за другой.


Циклы с параметром count

В главе 2 с помощью консоли AWS вы создали учетную запись AWS и пользователя Access Management (IAM). Теперь с помощью этого пользователя вы можете создавать и администрировать всех будущих пользователей IAM прямо в коде Terraform. Рассмотрим следующий код, который должен находиться в файле live/global/iam/main.tf:

provider "aws" {

  region = "us-east-2"

}

resource "aws_iam_user" "example" {

  name = "neo"

}

Здесь используется ресурс aws_iam_user для создания одного нового пользователя IAM. Но если необходимо создать трех пользователей? В языке программирования общего назначения вы бы применили цикл for:

# Это просто псевдокод. Он не будет работать в Terraform.

for (i = 0; i < 3; i++) {

  resource "aws_iam_user" "example" {

    name = "neo"

  }

}

В языке Terraform нет встроенной поддержки циклов for и другой традиционной процедурной логики, поэтому такой синтаксис работать не будет. Однако у каждого ресурса Terraform есть метапараметр под названием count. Это самая старая, простая ограниченная разновидность итератора в Terraform: она просто определяет, сколько копий ресурса нужно создать. Вот как с помощью этого параметра создать трех пользователей IAM:

resource "aws_iam_user" "example" {

  count = 3

  name  = "neo"

}

У этого кода есть одна проблема: у всех трех пользователей IAM будет одно и то же имя. Это приведет к ошибке, так как имена пользователей должны быть уникальными. Если бы у вас был доступ к стандартному циклу for, вы могли бы использовать индекс i, чтобы изменить каждое имя:

# Это просто псевдокод. Он не будет работать в Terraform.

for (i = 0; i < 3; i++) {

  resource "aws_iam_user" "example" {

    name = "neo.${i}"

  }

}

Чтобы добиться того же в Terraform и получить индекс каждой итерации в цикле, можно воспользоваться ссылкой count.index:

resource "aws_iam_user" "example" {

  count = 3

  name  = "neo.${count.index}"

}

Если выполнить команду plan для представленного выше кода, можно увидеть, что Terraform собирается создать трех пользователей IAM с разными именами ("neo.0", "neo.1", "neo.2"):

Terraform will perform the following actions:

  # aws_iam_user.example[0] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

    + name          = "neo.0"

    + path          = "/"

    + unique_id     = (known after apply)

  }

  # aws_iam_user.example[1] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

    + name          = "neo.1"

    + path          = "/"

    + unique_id     = (known after apply)

  }

  # aws_iam_user.example[2] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

    + name          = "neo.2"

    + path          = "/"

    + unique_id     = (known after apply)

  }

Plan: 3 to add, 0 to change, 0 to destroy.

Конечно, такое имя, как "neo.0", будет не очень полезным. Но если совместить count.index с некоторыми встроенными в Terraform функциями, каждую итерацию этого цикла можно изменить еще сильнее.

Например, все нужные вам имена пользователей IAM можно перечислить во входной переменной внутри live/global/iam/variables.tf:

variable "user_names" {

  description = "Create IAM users with these names"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]

}

В языке программирования общего назначения с циклами и массивами вы бы назначили каждому пользователю IAM отдельное имя путем поиска значений в массиве var.user_names по индексу i:

# Это просто псевдокод. Он не будет работать в Terraform.

for (i = 0; i < 3; i++) {

  resource "aws_iam_user" "example" {

    name = vars.user_names[i]

  }

}

В Terraform то же самое можно сделать с помощью count в сочетании:

• с синтаксисом доступа к массиву по индексу, который похож на синтаксис большинства других языков:

ARRAY[]

Например, вот как взять из массива var.user_names элемент с индексом 1:

var.user_names[1]

• с функцией length. У Terraform есть встроенная функция под названием length, которая имеет следующий синтаксис:

length()

Как вы уже догадались, функция length возвращает количество элементов в заданном массиве. Она также работает со строками и ассоциативными массивами.

Если все это объединить, получится следующее:

resource "aws_iam_user" "example" {

  count = length(var.user_names)

  name  = var.user_names[count.index]

}

Теперь, если выполнить команду plan, можно увидеть, что Terraform собирается создать трех пользователей IAM с уникальными именами:

Terraform will perform the following actions:

  # aws_iam_user.example[0] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

    + name          = "neo"

    + path          = "/"

    + unique_id     = (known after apply)

  }

  # aws_iam_user.example[1] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

    + name          = "trinity"

    + path          = "/"

    + unique_id     = (known after apply)

  }

  # aws_iam_user.example[2] will be created

  + resource "aws_iam_user" "example" {

    + arn           = (known after apply)

    + force_destroy = false

    + id            = (known after apply)

  + name          = "morpheus"

    + path          = "/"

    + unique_id     = (known after apply)

  }

Plan: 3 to add, 0 to change, 0 to destroy.

Обратите внимание: если в ресурсе используется параметр count, он превращается в массив ресурсов. Поскольку aws_iam_user.example теперь является массивом пользователей IAM, вместо стандартного синтаксиса для чтения атрибутов (_..) необходимо указывать, какой именно пользователь вас интересует. Для этого применяется тот же синтаксис доступа к элементам массива по его индексу:

_..

Например, если вы хотите предоставить в качестве выходной переменной ARN одного из пользователей IAM, нужно сделать следующее:

output "neo_arn" {

  value       = aws_iam_user.example[0].arn

  description = "The ARN for user Neo"

}

Если вам нужны ARN всех пользователей IAM, вы должны указать символ * вместо индекса:

output "all_arns" {

  value       = aws_iam_user.example[*].arn

  description = "The ARNs for all users"

}

Если выполнить команду apply, вывод neo_arn будет содержать только ARN пользователя Neo, тогда как all_arns выведет список всех ARN:

$ terraform apply

(...)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

neo_arn = arn:aws:iam::123456789012:user/neo

all_arns = [

  "arn:aws:iam::123456789012:user/neo",

  "arn:aws:iam::123456789012:user/trinity",

  "arn:aws:iam::123456789012:user/morpheus",

]

К сожалению, у параметра count есть два ограничения, которые делают его куда менее полезным. Во-первых, с помощью count можно пройтись по всему ресурсу, но при этом нельзя перебирать его вложенные блоки. Вложенный блок — это аргумент, который устанавливается внутри ресурса в следующем формате:

resource "xxx" "yyy" {

{

    [CONFIG...]

  }

}

NAME — это имя вложенного блока (например, tag), а CONFIG состоит из одного или нескольких аргументов, предназначенных специально для него (вроде key и value). Посмотрите, как устанавливаются теги в ресурсе aws_autoscaling_group:

resource "aws_autoscaling_group" "example" {

  launch_configuration = aws_launch_configuration.example.name

  vpc_zone_identifier  = data.aws_subnet_ids.default.ids

  target_group_arns    = [aws_lb_target_group.asg.arn]

  health_check_type    = "ELB"

  min_size = var.min_size

  max_size = var.max_size

  tag {

    key                 = "Name"

    value               = var.cluster_name

    propagate_at_launch = true

  }

}

Каждый тег требует создания нового вложенного блока со значениями для key, value и propagate_at_launch. В предыдущем листинге вручную указан единственный тег, но можно разрешить пользователям передавать собственные. У вас может появиться соблазн воспользоваться параметром count для циклического перебора этих тегов и генерации динамических вложенных блоков tag, но, к сожалению, применение count внутри вложенного блока не поддерживается.

Второе ограничение параметра count даст о себе знать, когда вы попытаетесь его изменить. Рассмотрим созданный ранее список пользователей IAM:

variable "user_names" {

  description = "Create IAM users with these names"

  type        = list(string)

  default     = ["neo", "trinity", "morpheus"]