# count и for_each: несколько ресурсов из одного блока _Ресурсы и data-источники · TerraformLab Knowledge Base_ **TL;DR:** count создаёт N одинаковых ресурсов по индексам 0..N-1. for_each: ресурсы по ключам из set или map. Правило: count для одинаковых, for_each когда у каждого свои настройки. Если сомневаешься, бери for_each. ## Зачем нужны Допустим, нужно создать пять S3-бакетов: `logs-dev`, `logs-staging`, `logs-prod`, `data-dev`, `data-prod`. Можно написать пять resource-блоков. Можно, один с count или for_each. Это про **повторяющиеся ресурсы**. Если что-то отличается только индексом или ключом, выносим в count/for_each и не дублируем код. ## count. Это самый простой способ ```hcl resource "aws_s3_bucket" "logs" { count = 3 bucket = "logs-bucket-${count.index}" } ``` Это создаст три бакета: - `aws_s3_bucket.logs[0]` с именем `logs-bucket-0` - `aws_s3_bucket.logs[1]` с именем `logs-bucket-1` - `aws_s3_bucket.logs[2]` с именем `logs-bucket-2` Внутри блока доступна переменная `count.index`, текущий номер от 0 до N-1. Чтобы поменять количество, поменяйте число. Terraform увидит «было 3, надо 5» и создаст ещё два. ## for_each, когда у каждого свой ключ ```hcl resource "aws_s3_bucket" "regional" { for_each = toset(["us", "eu", "ap"]) bucket = "data-bucket-${each.key}" } ``` Создастся три бакета: - `aws_s3_bucket.regional["us"]` с именем `data-bucket-us` - `aws_s3_bucket.regional["eu"]` с именем `data-bucket-eu` - `aws_s3_bucket.regional["ap"]` с именем `data-bucket-ap` Доступно: `each.key` (ключ) и `each.value` (значение): for_each принимает **set** или **map**: ```hcl # set, простые ключи, each.key == each.value for_each = toset(["us", "eu", "ap"]) # map, пара ключ-значение, each.key и each.value разные for_each = { us = { region = "us-east-1", tier = "primary" } eu = { region = "eu-central-1", tier = "secondary" } } # then: each.key, "us"/"eu", each.value.region, each.value.tier ``` ## Главное различие: что происходит при изменении списка Скажем, у вас три ресурса. Уберите средний. **С count:** ```hcl count = 3 → count = 2 ``` Terraform думает так: «было 3 элемента по индексам 0, 1, 2. Теперь надо 2». Удаляется элемент с индексом 2. **Если вы хотели удалить элемент 1**. Terraform этого не понимает; он просто сдвинет: индекс 2 пропадёт, элементы 0 и 1 останутся теми же. Проблема: если убрать элемент **из середины списка через count**. Terraform пересоздаст всё, что после него. Потому что индексы сдвинутся. **С for_each:** ```hcl for_each = toset(["us", "eu", "ap"]) → for_each = toset(["us", "ap"]) ``` Terraform увидит «был ключ `eu`, его больше нет», удалит **только** `aws_s3_bucket.regional["eu"]`. Остальные не тронет. **Правило большого пальца:** если список может меняться в середине, берите for_each. count, только для случаев когда количество растёт/убывает с конца или когда элементы реально взаимозаменяемы. ## Когда что использовать - **count**, три одинаковые виртуалки в auto-scaling-like подходе, отказоустойчивость через дублирование, ресурсы где порядок не важен. - **for_each**, почти всегда остальное. Бакеты для разных окружений, IAM-роли для разных сервисов, sg-rules с разными портами. Если не уверены, берите **for_each**. Он более явный и безопасный. ## Ссылки и аутпуты ```hcl resource "aws_s3_bucket" "regional" { for_each = toset(["us", "eu", "ap"]) bucket = "data-${each.key}" } # одна конкретная output "us_bucket_arn" { value = aws_s3_bucket.regional["us"].arn } # все ARN'ы списком output "all_arns" { value = [for k, b in aws_s3_bucket.regional : b.arn] } # все ARN'ы по ключу output "arns_by_region" { value = { for k, b in aws_s3_bucket.regional : k => b.arn } } ``` ## Подводные камни - **count и for_each нельзя одновременно.** Только один из них в одном ресурсе. - **`count = 0` или `for_each = []`, валидно.** Терраформ создаст ноль ресурсов. Это легальный способ условно отключить блок: `count = var.create ? 1 : 0`. - **for_each требует statically-knowable ключей.** Ключи должны быть известны на этапе `plan`. Нельзя `for_each = data.aws_some_thing.dynamic` если эти данные становятся известны только после apply другого ресурса. Лечится через зависимости или статические значения. - **При миграции с count на for_each, пересоздание.** Терраформ считает это другим ресурсом. Адреса меняются (`x[0]` → `x["key"]`). Лечится через `moved {}` блок (advanced). - **Map ключи всегда строки.** `for_each = { 1 = "a", 2 = "b" }` ругнётся: числа надо в строки конвертить: `{ "1" = "a", "2" = "b" }`. - **Большие for_each = медленный plan.** Если у вас for_each по 500 элементам, plan будет дёргать API 500 раз. Думайте о масштабе. ## Команды ```bash terraform state list ``` Покажет все ресурсы с индексами/ключами: aws_s3_bucket.regional["us"], aws_s3_bucket.regional["eu"]... ```bash terraform state show 'aws_s3_bucket.regional["us"]' ``` Один конкретный из for_each. Обратите внимание на одинарные кавычки вокруг: без них shell съест двойные. ```bash terraform plan -target='aws_s3_bucket.regional["us"]' ``` Точечный plan только для одного элемента for_each. ## См. также - [Блок resource: главный кирпич Terraform](/terraform/kb/tf-resource-block.md) - [Ссылки в HCL: как читать aws_s3_bucket.demo.bucket](/terraform/kb/tf-references.md) - [Типы данных в HCL: string, number, list, map, object](/terraform/kb/hcl-types.md) - [locals: вычисляемые внутренние имена](/terraform/kb/tf-locals.md)