сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Terraform и Ansible

Модули Terraform: count против for_each

Модули, списки и размер

Замер, который стоит увидеть один раз. Три одинаковых ресурса созданы по списку с номерами. Удаляю из списка СРЕДНИЙ элемент - план говорит «1 to add, 0 to change, 2 to destroy»: два ресурса уничтожаются и один создаётся, хотя убрать хотели ровно один. Переписываю то же самое на ключи вместо номеров, удаляю тот же средний элемент - план говорит «0 to add, 0 to change, 1 to destroy». Один в один то, что просили.

Стержень: адресация по номеру привязывает ресурс к позиции в списке, поэтому любое смещение середины пересоздаёт хвост; адресация по ключу этого лишена.

// Формулировки: «чем count отличается от for_each?», «когда выносить в модуль?», «почему план идёт десять минут?»

Номер против ключа

Состояние - это записная книжка инструмента о том, какой ресурс из описания какому реальному объекту соответствует. При адресации по номеру ресурсы записываются в неё как элемент ноль, элемент один, элемент два. Убрали второй элемент - третий сдвинулся на его место, и с точки зрения инструмента это ДРУГОЙ ресурс: старый надо уничтожить, новый создать. Именно это и показал мой замер: 2 уничтожения и 1 создание вместо одного уничтожения.

При адресации по ключу ресурсы записываются как элемент «api», элемент «bot», элемент «worker». Удаление одного не двигает остальных - в состоянии они лежат под своими именами. Замер подтвердил: ровно одно уничтожение, остальные не тронуты.

// Отсюда правило: по номеру адресуют только там, где элементы действительно взаимозаменяемы и безымянны, например пять одинаковых виртуальных машин. Всё, у чего есть имя (сервисы, базы, очереди), адресуют по ключу. На проде цена ошибки прямая: пересоздание базы вместе с данными вместо аккуратного удаления одной.

адресация по номеру
ресурсы нумеруются позицией; смещение середины пересоздаёт хвост
адресация по ключу
ресурсы адресуются именами; удаление одного не трогает остальные

Модули: когда и зачем

Модуль - это кусок описания с параметрами, который переиспользуется. Смысл появляется, когда одно и то же надо развернуть несколько раз с разными значениями: три среды, десять однотипных сервисов, набор из сети и правил доступа.

Ошибка на этом месте типовая: выносят в модуль всё подряд с самого начала. Получается обёртка вокруг одного ресурса, которая ничего не упрощает, зато добавляет слой параметров, через который надо каждый раз пробрасывать любое новое поле. Хороший ориентир: выносить в модуль стоит то, что реально используется в двух-трёх местах, и после того, как форма устоялась.

// Второй важный вопрос - версионирование модулей. Модуль, подключённый из репозитория без указания версии, меняется под всеми пользователями одновременно, и однажды чужая правка приезжает вам в план. Поэтому подключают по тегу или по отпечатку коммита, а обновление делают осознанно, отдельным изменением.

модуль
переиспользуемый кусок описания с параметрами
версия модуля
тег или коммит, к которому привязано подключение; иначе правки приезжают сами

Размер и скорость

План идёт медленно не потому, что инструмент плохой, а потому что он опрашивает КАЖДЫЙ ресурс из состояния в реальной системе. Сто ресурсов - сотня запросов, тысяча - тысяча, и это ещё до вывода разницы.

Отсюда и связка размера с болью: большой набор ресурсов означает долгий план, широкий радиус ошибки и очередь на замке. Лечится это разделением на слои и среды, а не терпением. Границу удобно проводить по частоте изменений: то, что трогают ежедневно, не должно лежать в одном состоянии с тем, что трогают раз в квартал.

// Полезные привычки для больших описаний: ограничивать план подмножеством ресурсов при отладке, держать параметры сред в отдельных файлах значений, а не разводить их условиями внутри описания, и не тащить в описание то, что меняется само (счётчики, метки времени) - иначе план будет вечно показывать разницу, и его перестанут читать.

опрос ресурсов
инструмент запрашивает фактическое состояние каждого ресурса перед сравнением
файл значений
параметры конкретной среды отдельно от общего описания

Как отвечать: «Чем count отличается от for_each?»

Способом адресации в состоянии, и это не косметика. При count ресурсы нумеруются позицией в списке, поэтому удаление элемента из середины сдвигает всё, что за ним. Я это мерил: три ресурса, убрал средний - план показал два уничтожения и одно создание, хотя убрать хотели один. При for_each ресурсы адресуются ключами, и в состоянии они лежат под своими именами: тот же самый эксперимент дал ровно одно уничтожение, остальные не тронуты. Отсюда правило: count годится только для действительно безымянных взаимозаменяемых штук вроде пяти одинаковых машин, а всё, у чего есть имя, - сервисы, базы, очереди - описывают через for_each. Цена ошибки на проде прямая: пересоздание базы вместе с данными вместо аккуратного удаления одной лишней.

Ответ объясняет причину через устройство состояния и подкрепляет измеренным планом. Формулировка «цена ошибки - пересозданная база» показывает, что человек понимает последствия.

На чём валятся

  • Адресуют по номеру именованные ресурсы и пересоздают половину при удалении одного.
  • Выносят в модуль всё подряд и получают лишний слой параметров без выигрыша.
  • Подключают модуль без версии: чужая правка приезжает вам в план.
  • Держат всю инфраструктуру в одном состоянии и ждут план по десять минут.
  • Тащат в описание поля, которые меняются сами, и приучают команду не читать план.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.

  1. #dvo_tf_structure1 / 5
    Ресурсы созданы через count по списку. Удалили элемент из середины — план хочет пересоздать половину. Почему?
    A)Список нужно было отсортировать перед применением
    B)Count не поддерживает изменение длины после создания
    C)Провайдер не умеет менять ресурсы, созданные пачкой
    D)Адрес ресурса привязан к индексу, и элементы сдвинулись
    показать ответ и разбор
    +D)Адрес ресурса привязан к индексу, и элементы сдвинулись

    // разбор: При count ресурс адресуется порядковым номером. Удаление элемента из середины сдвигает все последующие: то, что было третьим, становится вторым — для Terraform это другой ресурс, значит уничтожить и создать заново. for_each адресует по ключу из карты или множества, поэтому удаление одного элемента трогает ровно его. Отсюда правило: count — для однотипных безымянных копий, for_each — для всего остального.

  2. #dvo_tf_structure2 / 5
    Вся инфраструктура компании живёт в одном стейте. Чем это плохо?
    A)Состояние переполнится: у бэкендов есть предел размера
    B)Радиус поражения ошибки — вся инфраструктура, и план долгий
    C)Модули перестанут работать при большом числе ресурсов
    D)Провайдеры разных облаков не уживаются в одной конфигурации
    показать ответ и разбор
    +B)Радиус поражения ошибки — вся инфраструктура, и план долгий

    // разбор: Один стейт означает одну блокировку на всех, планы по десять минут и общий радиус поражения: опечатка в сетевом модуле выводит план, который трогает и базы, и кластер. Инфраструктуру режут по границам изменений и ответственности — сеть, кластер, данные, приложения, — а связи между слоями отдают через выходные значения или data-источники. Мелкие независимые стейты применяются параллельно и чинятся отдельно.

  3. #dvo_tf_structure3 / 5
    Сетевой стек создаёт VPC, а стек приложения должен знать её id. Как передать значение между стеками?
    A)Прописать id VPC строкой в коде приложения руками
    B)Слить оба стека в один, других вариантов нет
    C)Скопировать весь state сети в стек приложения
    D)Через output стека и remote state / data source
    показать ответ и разбор
    +D)Через output стека и remote state / data source

    // разбор: Значения между стеками передают через outputs: сетевой стек объявляет output (id VPC/подсетей), а стек приложения читает его — terraform_remote_state (данные из чужого state) или через data source по тегам/имени. Так стеки остаются раздельными, но связанными, и id не хардкодят. Захардкоженный id ломается при пересоздании ресурса, а копирование state — антипаттерн.

  4. #dvo_tf_structure4 / 5
    Модуль подключён ссылкой на ветку: source с ref=main. Чем это грозит и как правильно?
    A)Ничего: ссылка на ветку и есть рекомендуемый способ подключения
    B)Плавающая ветка меняет поведение без правок — пинить на тег/версию
    C)Надо вынести модуль в тот же репозиторий, тогда безопасно
    D)Достаточно закешировать модуль в .terraform локально
    показать ответ и разбор
    +B)Плавающая ветка меняет поведение без правок — пинить на тег/версию

    // разбор: source с ref=ветка (main) при следующем init тянет любой новый коммит модуля — поведение и план могут измениться без единой правки в твоём коде. Модули пинят на неизменяемую версию: ref=тег (?ref=v1.4.0) или version= для модулей из реестра. Тогда обновление модуля — осознанный шаг с ревью diff, а сборки воспроизводимы. Та же идея, что и lock провайдеров.

  5. #dvo_tf_structure5 / 5
    Dev, stage и prod держат в трёх terraform workspace одного бэкенда. Чем это рискованно для прода?
    A)Ничего: workspace ровно для того и созданы, чтобы делить среды
    B)Workspace заметно замедляют apply на больших окружениях
    C)Общий бэкенд и код: ошибка или креды заденут все среды разом
    D)Отдельные переменные на среду в workspace не поддерживаются
    показать ответ и разбор
    +C)Общий бэкенд и код: ошибка или креды заденут все среды разом

    // разбор: terraform workspace держит несколько state в ОДНОМ бэкенде и переключается между ними — код, бэкенд и креды общие. Для прода это опасно: не тот выбранный workspace, общие права, битый apply или утёкший ключ задевают сразу все среды. Прод изолируют отдельным бэкендом/каталогом и своими кредами (dev/stage/prod — разные state, а лучше и разные аккаунты). Workspaces хороши для эфемерных/похожих копий, а не для разделения сред с разным уровнем риска.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.