linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-beginner-08-count

lesson ── terraform-beginner ── ~12 мин ── 3 шагов

count vs for_each: массовое создание

Иногда нужно создать N одинаковых ресурсов: три бакета для разных регионов, пять security-групп для разных портов. Дублировать resource-блоки, больно и неустойчиво. Terraform умеет это через count и for_each.

Эти два инструмента похожи, но имеют важное различие, которое всплывает при изменении списка. См. tf-count-for-each.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Создай три бакета через count

    Создай main.tf:

    hcl
    resource "aws_s3_bucket" "many" {
      count  = 3
      bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}"
      tags = {
        Index = tostring(count.index)
      }
    }
    resource "random_id" "suffix" {
      byte_length = 4
    }

    Здесь:

    • count = 3, создать три экземпляра.
    • count.index, индекс текущего (0, 1, 2).
    • tostring(count.index), преобразовать число в строку для тега.
    bash
    cd /home/student/tf-count
    terraform init -input=false
    terraform apply -auto-approve -input=false

    В state увидишь:

    aws_s3_bucket.many[0]
    aws_s3_bucket.many[1]
    aws_s3_bucket.many[2]
    подсказка

    Если apply ругается «duplicate bucket name»: random_id.suffix должен подставиться. Проверь что random_id ресурс есть в HCL.

    ✓ Три бакета с индексами 0, 1, 2: count работает.

  2. 02

    Добавь три бакета через for_each

    Добавь в main.tf ещё один ресурс:

    hcl
    resource "aws_s3_bucket" "regional" {
      for_each = toset(["us", "eu", "ap"])
      bucket = "linuxlab-regional-${each.key}-${random_id.suffix.hex}"
      tags = {
        Region = each.key
      }
    }

    Здесь:

    • for_each = toset([...]), создать для каждого элемента set.
    • each.key, текущий ключ ("us", "eu" или "ap").
    • each.value, то же самое для set; для map отличается.
    bash
    terraform apply -auto-approve

    В state добавятся:

    aws_s3_bucket.regional["us"]
    aws_s3_bucket.regional["eu"]
    aws_s3_bucket.regional["ap"]

    Это не индексы, это именованные ключи. Гораздо стабильнее при изменениях списка.

    подсказка

    toset обязательно: for_each не принимает обычный list, только set или map. Если убрать toset: упадёт.

    ✓ for_each создал три бакета по ключам us, eu, ap.

    То же самое на OpenTofu

    OpenTofu держит CLI и state совместимыми с Terraform по командам этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе сделай backup state и прогон на feature-branch - расхождения концентрируются в новых фичах (variables в backend, state-encryption, OCI registry-backed модули). См. tf-opentofu-parity для полной матрицы.

    • → OpenTofu parity
  3. 03

    Удали средний элемент из обоих списков

    Сейчас у тебя 6 бакетов: 3 через count, 3 через for_each. Удалим средний в каждом случае. Поправь HCL:

    hcl
    resource "aws_s3_bucket" "many" {
      count  = 2   # было 3
      bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}"
      tags = {
        Index = tostring(count.index)
      }
    }
    resource "aws_s3_bucket" "regional" {
      for_each = toset(["us", "ap"])   # убрали "eu"
      bucket   = "linuxlab-regional-${each.key}-${random_id.suffix.hex}"
      tags = {
        Region = each.key
      }
    }

    Запусти plan:

    bash
    terraform plan

    Что увидишь:

    • count: Plan: 0 to add, 0 to change, 1 to destroy. Снесётся aws_s3_bucket.many[2]. Это последний по индексу, не средний. Если бы тебе нужен был именно middle, count его не «понимает».
    • for_each: Plan: 0 to add, 0 to change, 1 to destroy. Снесётся именно aws_s3_bucket.regional["eu"]. Точно тот, что ты убрал. us и ap не тронуты.

    В этом и есть главное различие: for_each стабильнее.

    bash
    terraform apply -auto-approve
    подсказка

    Если plan показывает destroy + create в count: это норма: count.index сдвигается. Для middle-удалений переходи на for_each.

    ✓ Удалили 2 бакета (по одному из каждого блока), осталось 4. for_each сделал это аккуратно.

    Правило выбора между count и for_each

    Если ресурсы реально одинаковы и количество растёт/убывает только с конца, count нормально. Если у каждого свой смысл (регион, имя, окружение): for_each. Если сомневаешься, бери for_each. Стоимость рефакторинга count → for_each болезненная: нужны moved {} блоки или ручной state mv. Лучше сразу делать правильно.

    • → count vs for_each
    • → Адресация ресурсов

Что ты узнал

Ты создал три бакета через count (адресуются по индексу [0], [1], [2]) и три через for_each (адресуются по ключу ["us"], ["eu"], ["ap"]). Увидел что for_each стабильнее при изменении списка, он не сдвигает все остальные индексы.

команды

  • terraform state listувидеть индексы и ключи всех элементов
  • terraform state show 'aws_s3_bucket.regional["us"]'одна штука из for_each
  • terraform plan -target='aws_s3_bucket.regional["eu"]'точечный plan для одного элемента

концепции

  • · count = одинаковые ресурсы, адресуются по индексу 0..N-1
  • · for_each = ресурсы по ключу, адресуются по строке
  • · Удаление середины списка: count пересоздаст всё после, for_each, нет

← предыдущий

Capstone, VPC + ALB + ECS Fargate + Lambda

следующий →

Декларативный import: захват существующего ресурса

Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки