Инфраструктура как код: цели, декларативная модель, состояние
Урок курса «Курс подготовки DevOps-инженеров»
Хронометраж: занятие 60 минут — теория 30 минут, практика 30 минут. Домашняя работа: 1–2 часа.
Кратко о чём урок: Зачем IaC, декларативный подход против императивного, роль state как связи между кодом и реальностью, место Terraform рядом с Ansible и CI.
1. Что изучаем
После урока вы сможете:
- объяснять, зачем инфраструктуру описывают кодом, и чем это лучше кликов в консоли и разовых команд;
- отличать декларативный подход от императивного и показывать сходимость к описанному состоянию;
- объяснять роль state как связи между кодом и реальностью и что происходит при их расхождении;
- называть место Terraform рядом с Ansible (блок 10) и CI (блок 09);
- запускать первый цикл
init → plan → apply → destroyна модуле, а не на плоском наборе файлов.
2. Зачем это нужно и где применяется
Весь блок 11 вы создавали ресурсы командами yc — по одной, руками. Это работает для опыта, но
не для эксплуатации: команды не повторить одинаково, не проверить на ревью, не откатить. Инфраструктура
как код (IaC) описывает желаемое состояние текстом, который лежит в git, проходит ревью и применяется
одинаково — как приложение, только вместо кода сервиса тут код инфраструктуры.
Ситуации, из-за которых существует этот урок:
- «Развернули руками, повторить не можем» — шаги делались в консоли, записи нет;
- «Прод и стейдж разъехались» — настраивали по отдельности, дрейф никто не видел;
- «Изменение снесло рабочий ресурс» — не было плана, который показал бы это заранее;
- «Кто и что менял в инфраструктуре — неизвестно» — нет истории в git и ревью;
- «Ansible уже есть, зачем ещё Terraform» — не различили провижининг ресурсов и настройку внутри.
3. Теория (30 минут)
Файлы для работы: blocks/12-iac-terraform/examples/12.01-iac-concepts/
Стенд: Terraform 1.5.7 на вашей машине, провайдер
kreuzwerker/docker4.6.0 через зеркало Yandex Cloud (устройство стенда — вexamples/docker/README.md). Подложка — локальный Docker: механику Terraform учим бесплатно, реальный Yandex Cloud будет в уроке 12.09. Всё проверено запуском.
3.1. Императивно против декларативно
Разница ровно та же, что в блоке 10 между bash-скриптом и Ansible (урок 10.01), только для инфраструктуры:
- императивно — вы перечисляете шаги: «создай сеть, создай ВМ, подключи диск». Так работают
команды
ycиз блока 11. Повторный запуск сделает всё заново или упадёт; чтобы понять текущее состояние, надо помнить, что уже сделано; - декларативно — вы описываете желаемое состояние: «должна быть сеть, две ВМ, диск», а инструмент сам решает, что создать, что изменить, а что оставить. Так работает Terraform.
Проверено запуском на модуле из двух веб-контейнеров. Первый apply создал четыре ресурса (образ,
сеть, два контейнера). Повторный apply не сделал ничего:
$ terraform apply
No changes. Your infrastructure matches the configuration.
Это и есть декларативность: Terraform сравнил описанное с существующим и увидел, что менять нечего. Императивный скрипт на повторном запуске либо создал бы дубликаты, либо упал — здесь же «применить» безопасно запускать сколько угодно. Тот же принцип идемпотентности и сходимости, что у Ansible (10.01), только Terraform применяет его к облачным ресурсам, а не к настройке внутри машины.
3.2. Что даёт описание инфраструктуры кодом
Прежде чем разбирать механику, стоит назвать, ради чего всё это, — иначе IaC выглядит как лишний слой
над понятными командами yc. Код инфраструктуры даёт ровно то, что код приложения:
- повторяемость — тот же код разворачивает тот же стенд сколько угодно раз и на скольких угодно окружениях (dev, stage, prod из одного модуля, урок 12.06). Руками в консоли два одинаковых окружения не собрать — они разъедутся;
- история и ревью — инфраструктура лежит в git, изменения проходят pull request и код-ревью, как в блоке 09. Видно, кто, что и зачем менял; спорное изменение отклоняют до применения;
- план до применения —
planпоказывает намерение, и опасное (пересоздание, удаление) видно заранее, а не постфактум; - откат — вернуть предыдущую версию кода и применить проще, чем вспоминать, что и куда кликали;
- самодокументируемость — код и есть описание того, что развёрнуто; отдельная «схема инфраструктуры» не устаревает, потому что её нет — есть сам код.
Цена — это ещё один инструмент и ещё один артефакт (state), который надо беречь. Для одноразового
эксперимента команды yc из блока 11 быстрее; для того, что живёт и меняется, окупается IaC. Тот же
размен, что между разовым bash-скриптом и Ansible в блоке 10.
3.3. State: связь между кодом и реальностью
Откуда Terraform знает, что менять нечего? Из state — файла состояния, где записано соответствие между ресурсами в коде и настоящими ресурсами в облаке. Это третий угол треугольника: код (чего вы хотите), реальность (что есть в облаке), state (что Terraform создал и чем управляет).
Проверено: после apply в state лежат ровно те ресурсы, что описаны, и каждому сопоставлен реальный
объект:
$ terraform state list
module.webstand.docker_container.web[0]
module.webstand.docker_container.web[1]
module.webstand.docker_image.web
module.webstand.docker_network.web
При plan/apply Terraform делает три сверки: читает код, читает state, опрашивает реальность — и
показывает разницу. Отсюда два следствия, важных на весь блок:
- state — источник правды о том, чем Terraform управляет. Нет записи в state — Terraform не знает
о ресурсе, даже если тот существует (его надо
import, урок 12.07); есть запись, а ресурса нет — Terraform его пересоздаст; - state ценен и опасен. В нём лежат идентификаторы, а иногда и секреты (урок 12.04); потерять его или разойтись с реальностью — источник самых неприятных аварий блока.
Первоисточник формулирует назначение state ровно в тех же терминах: «The primary purpose of Terraform
state is to store bindings between objects in a remote system and resource instances declared in your
configuration» — state существует именно ради связки «строка кода ↔ реальный объект», а не как
побочный лог действий. Оттуда же прямой запрет на ручную правку: «Do not directly edit this file»;
точечные операции со state делаются командой terraform state (list, mv, rm — подробно в
уроке 12.04), а не текстовым редактором. Формат при этом самый простой — «State snapshots are stored
in JSON format» — файл действительно открывается и читается как обычный текст, и в этом двойная
опасность: прочитать его руками легко, а вот отредактировать и не сломать связку с реальностью —
уже нет. По умолчанию, локально, state — это один файл terraform.tfstate рядом с кодом в рабочем
каталоге; в примере этого урока он и лежит там же, в envs/dev/. У локального файла нет защиты от
одновременного запуска: если два человека одновременно выполнят apply из одного каталога, оба
прочитают один и тот же state до изменений и оба попытаются его переписать — отсюда требование
удалённого backend с блокировкой для любой работы в команде (урок 12.04), а не только для больших
проектов.
3.4. Сходимость и дрейф
Реальность может измениться в обход кода — кто-то поправил ресурс руками. Это дрейф (то же понятие,
что в 10.01 для конфигурации), и Terraform его видит. Проверено: удалили один контейнер мимо
Terraform (docker rm), и plan заметил расхождение:
$ docker rm -f l1101-dev-web-2
$ terraform plan
# module.webstand.docker_container.web[1] will be created
Plan: 1 to add, 0 to change, 0 to destroy.
apply вернул стенд к описанному состоянию — контейнеров снова два. Это сходимость: Terraform
приводит реальность к коду, а не выполняет шаги вслепую. Ключевое отличие plan от apply — план
показывает намерение до изменения, и это то, что читают на ревью и что ловит опасные операции
(пересоздание, удаление) заранее. В блоке 11 у команд yc такого предварительного показа не было:
выполнил — и узнал результат.
3.5. Место Terraform: не вместо Ansible и CI, а рядом
Terraform не заменяет инструменты из прошлых блоков, а встаёт между ними:
- Terraform создаёт ресурсы — сети, ВМ, диски, кластеры, managed-сервисы (провижининг). Это граница «что должно существовать»;
- Ansible настраивает внутри — пакеты, конфиги, службы на уже созданных машинах (блок 10). Это граница «как настроено то, что существует». Разделение разобрано в 10.08 и подробно — в уроке 12.10;
- CI применяет Terraform — план в pull request на ревью, применение по merge (блок 09). Так инфраструктура получает те же ворота качества, что и код (урок 12.08).
Один из способов запомнить границу: Terraform отвечает на вопрос «какие серверы есть», Ansible — «что на этих серверах установлено», CI — «кто и когда это применил». Пытаться настраивать ОС Terraform-ом или создавать облачные ресурсы Ansible-ом можно, но это работа не тем инструментом.
3.6. Почему модули, а не плоский набор файлов
Можно свалить все ресурсы в один каталог .tf-файлов — Terraform это выполнит. Но правильная единица
организации — модуль: набор ресурсов с входами (variables), выходами (outputs) и понятной
границей, который вызывают из окружений. В этом блоке инфраструктура с самого начала оформляется
модулями (подробно — урок 12.05), и пример 12.01 уже устроен так:
12.01-iac-concepts/
modules/webstand/ # модуль: variables.tf, main.tf, outputs.tf
envs/dev/main.tf # окружение вызывает модуль через module {}
Окружение dev не описывает контейнеры напрямую, а вызывает модуль с параметрами:
module "webstand" {
source = "../../modules/webstand"
name_prefix = "l1101-dev"
replicas = 2
}
Почему так с первого урока: модуль переиспользуется (тот же webstand поднимет и dev, и prod
с разными параметрами — урок 12.06), читается как единица, тестируется и версионируется. Плоский
набор файлов на первый стенд кажется проще, но на втором окружении превращается в копипасту, а
на третьем — в источник расхождений. Это тот же довод, что за роли в Ansible (урок 10.05).
3.7. Первый цикл жизни
Четыре команды, которые повторяются весь блок (подробно — урок 12.03):
init— скачать провайдеры (через зеркало, урок 10.08 и стенд блока) и подготовить каталог;plan— показать, что Terraform собирается сделать, ничего не меняя;apply— применить: привести реальность к коду;destroy— удалить всё, чем управляет этот state.
Проверено, что цикл замкнут: apply создал четыре ресурса, destroy удалил четыре, контейнеров не
осталось. Это и есть базовая гарантия IaC — то, что создано кодом, кодом же и убирается, без ручного
дочищения (сравните с дисциплиной уборки в облаке из блока 11, где за командами yc приходилось
следить yc-inventory.py).
Две детали цикла, важные с самого начала. init скачивает провайдеры через зеркало (в стенде
блока — зеркало Yandex Cloud, находка 10.08): прямой доступ к реестру из региона может не работать, а
провайдер yandex вдобавок большой, поэтому кэш плагинов (TF_PLUGIN_CACHE_DIR) избавляет от
повторной загрузки. А destroy удаляет только то, что записано в state этого каталога — он не
тронет чужие ресурсы, но и не уберёт то, что вы создали мимо Terraform. Отсюда правило: всё, что
должно убираться автоматически, должно быть создано через Terraform, а не руками рядом.
Ещё одна деталь, которая объясняет, почему порядок команд init → plan → apply → destroy не нужно
расписывать самому, как в bash-скрипте: внутри plan/apply Terraform строит граф зависимостей
между ресурсами. Первоисточник формулирует это одной фразой: «Terraform builds a resource graph to
determine resource dependencies and creates or modifies non-dependent resources in parallel». В
примере этого урока сеть и образ не зависят друг от друга — оба нужны раньше контейнеров, но не друг
после друга — и Terraform создаёт их одновременно, а не в порядке, в котором ресурсы написаны в
main.tf. Порядок в файле — вопрос читаемости для человека, а не инструкция для Terraform: реальный
порядок применения он вычисляет сам из ссылок между ресурсами (docker_container ссылается на
docker_image.web.image_id и docker_network.web.name, поэтому оба должны появиться раньше). Это
прямое следствие декларативности из раздела 3.1: вы описываете, что должно быть, а не в каком порядке
это собирать, и граф зависимостей — тот механизм, который порядок восстанавливает.
3.8. Сводка: что показали замеры
| Утверждение | Что показал замер |
|---|---|
| «повторный apply что-то доделает» | No changes — Terraform декларативен, сверяет состояние |
| «Terraform помнит ресурсы сам» | помнит через state; terraform state list показывает, чем управляет |
| «ручную правку Terraform не заметит» | plan увидел удалённый контейнер: «1 to add» |
| «apply выполняет шаги» | apply приводит реальность к коду (сходимость), а не повторяет шаги |
| «модули — это для больших проектов» | окружение вызывает модуль с первого стенда, иначе копипаста на втором |
| «созданное надо дочищать руками» | destroy убрал ровно то, что создал apply |
3.9. Примеры и варианты реализации
Одна задача — описать инфраструктуру так, чтобы её можно было повторить и проверить, — в трёх средах.
| Среда | Как решается | Что нужно иметь |
|---|---|---|
| Локальный стенд (Docker) | Terraform + провайдер docker: контейнеры и сети как код (стенд этого блока) | Terraform, Docker |
| VPS / bare-metal | Terraform + провайдеры для железа/гипервизора (libvirt) или обычные ВМ в облаке | Terraform, доступ к гипервизору/облаку |
| Yandex Cloud | Terraform + провайдер yandex: сети, ВМ, managed-сервисы как код (урок 12.09) | Terraform, каталог, зеркало провайдеров |
Локальный стенд. Механику Terraform (state, plan/apply, модули, дрейф, import) полностью видно на провайдере docker без единого рубля — на нём и построен блок. Понятия те же, что в облаке.
VPS / bare-metal. Terraform умеет и локальные провайдеры (libvirt для ВМ на KVM), и обычные ВМ в облаке без managed-сервисов (урок 12.09). Код тот же, меняется провайдер.
Yandex Cloud. Провайдер yandex-cloud/yandex (ставится через зеркало, урок 10.08) описывает всё,
что в блоке 11 вы делали командами yc, — но кодом. Это урок 12.09.
Что выбрать. Учиться — на docker/local (бесплатно, быстро). Для реальной инфраструктуры — провайдер той среды, где она живёт. Код и подход одни, различается провайдер и цена ошибки.
4. Практика на занятии (30 минут)
Курс проходят самостоятельно, поэтому каждый шаг говорит, что ввести, зачем, что должно получиться
и что делать при неудаче. Опыты — в 12.01-iac-concepts/. Нужны
terraform, docker и настройка зеркала провайдеров (см.
examples/docker/README.md):
export TF_CLI_CONFIG_FILE="$(git rev-parse --show-toplevel)/blocks/12-iac-terraform/examples/terraformrc"
export TF_PLUGIN_CACHE_DIR="$HOME/.terraform.d/plugin-cache"; mkdir -p "$TF_PLUGIN_CACHE_DIR"
# macOS + Docker Desktop: указать сокет
export DOCKER_HOST="unix://$HOME/.docker/run/docker.sock"
cd blocks/12-iac-terraform/examples/12.01-iac-concepts/envs/dev
Стенд — Terraform на вашей машине, провайдер kreuzwerker/docker через зеркало Yandex Cloud (без
зеркала init может не найти провайдер из вашего региона, урок 10.08). Уборка — обязательный шаг 4:
Terraform убирает только то, что сам создал, а не всё, что похоже на его работу.
Шаг 1 (8 минут). Первый цикл init → plan → apply → state list.
- Что ввести:
terraform init
terraform plan # что будет создано
terraform apply # применить
terraform state list
- Зачем: увидеть весь цикл разом — от скачивания провайдера до записи созданных ресурсов в state; без этого шага «state» остаётся абстракцией, а не файлом, который только что заполнился.
- Ожидаемый результат:
applyсоздаёт четыре ресурса (образ, сеть, два контейнера);terraform state listпоказывает ровно эти четыре записи —docker_image.web,docker_network.web,docker_container.web[0],docker_container.web[1]. - Если не так:
initне находит провайдер — проверьте, чтоTF_CLI_CONFIG_FILEуказывает на файл зеркала и что переменная вообще экспортирована в этой оболочке;applyпадает на подключении к Docker — проверьтеDOCKER_HOSTи что Docker Desktop запущен (docker psдолжен отвечать, а не виснуть). - Ответьте: сколько ресурсов в state? Как код
replicas = 2превратился в конкретные контейнеры?
Шаг 2 (5 минут). Декларативность: повторный apply.
- Что ввести:
terraform apply # ещё раз, без единой правки кода
- Зачем: убедиться, что Terraform сверяет состояние, а не выполняет шаги заново — это и есть декларативность, которую урок обещает с первой страницы.
- Ожидаемый результат:
No changes. Your infrastructure matches the configuration.— apply ничего не создаёт и не трогает. - Если не так: если apply что-то меняет — значит, между шагами кто-то (или вы сами) правил файлы
модуля, либо реальность уже разошлась с кодом до начала опыта; сначала выполните
terraform planи прочитайте, что именно он хочет поменять. - Ответьте: почему второй
applyговоритNo changes? Чем это отличается от повторного запуска bash-скрипта?
Шаг 3 (10 минут). Дрейф и сходимость.
- Что ввести:
docker rm -f l1101-dev-web-2 # правим реальность мимо Terraform
terraform plan # Terraform это видит
terraform apply # и возвращает к описанному
- Зачем: показать, что дрейф (правка мимо кода) не проходит незамеченным и что
applyне выполняет шаги вслепую, а приводит реальность к описанному состоянию. - Ожидаемый результат:
planпечатает# module.webstand.docker_container.web[1] will be createdиPlan: 1 to add, 0 to change, 0 to destroy.; послеapplyконтейнеров снова два. - Если не так:
planговоритNo changes— проверьтеdocker ps -a, точно ли удалённый контейнер называлсяl1101-dev-web-2(имя зависит отname_prefixвenvs/dev/main.tf); если имя другое, удалите правильный контейнер и повторите. - Ответьте: что показал
plan? Как это связано с дрейфом конфигурации из урока 10.01?
Шаг 4 (5 минут). Уборка.
- Что ввести:
terraform destroy
docker ps -a --filter name=l1101 # пусто
- Зачем: проверить обратную гарантию IaC — то, что создано кодом, кодом же и убирается, без ручного дочищения контейнеров и сети.
- Ожидаемый результат:
destroyудаляет ровно четыре ресурса (столько же, сколько создалapplyна шаге 1);docker ps -a --filter name=l1101не находит ни одного контейнера. - Если не так: что-то осталось — скорее всего, ресурс был создан мимо Terraform (например,
контейнер, восстановленный вручную под другим именем) и в state не попал; уберите его
docker rmотдельно и запомните:destroyне тронет то, чего нет в state.
Что обсудить в конце занятия. У вас есть Ansible (блок 10) и CI (блок 09). Где в этой связке место Terraform и что он делает такого, чего не делают они? Приведите по одной задаче для каждого инструмента.
5. Домашняя работа (1–2 часа)
Задание. Опишите модулем небольшой стенд (например, три веб-контейнера в своей сети) по заготовке
iac-plan.md: пройдите цикл init → plan → apply, покажите
state, устройте дрейф и верните состояние, разберите, что делает Terraform, а что осталось бы Ansible
и CI. Оформите инфраструктуру модулем с variables/outputs, а не плоским набором файлов.
Критерии приёмки.
- Инфраструктура оформлена модулем (
variables.tf,main.tf,outputs.tf), окружение вызывает его черезmodule {}. - Показан цикл
init → plan → apply, приложен выводterraform state list. - Показана декларативность: повторный
applyдаётNo changes. - Устроен дрейф (правка мимо Terraform) и показано, что
planего видит, аapplyвозвращает состояние. - Для стенда названо: что делает Terraform, что делал бы Ansible, что — CI.
- После работы
terraform destroy, ресурсов не осталось.
6. Частые ошибки и подводные камни
Правят инфраструктуру руками при живом Terraform. Симптом: следующий apply откатывает ваши
правки. Причина: Terraform приводит реальность к коду. Что делать: менять через код и apply, а не
мимо; ручная правка — это дрейф, который Terraform сотрёт.
Потеряли или испортили state. Симптом: Terraform не видит существующие ресурсы или хочет
пересоздать всё. Причина: state — источник правды о том, чем он управляет. Что делать: беречь state
(урок 12.04), не редактировать руками; существующее вводить через import (12.07).
Свалили всё в плоский набор файлов. Симптом: на втором окружении копипаста, на третьем — расхождения. Причина: нет модульной границы. Что делать: с первого стенда оформлять модулем с входами и выходами (урок 12.05).
Применяют без plan. Симптом: apply снёс или пересоздал важный ресурс. Причина: не посмотрели
намерение заранее. Что делать: всегда читать plan перед apply, особенно строки про destroy и
replace (урок 12.03); в CI план идёт на ревью (12.08).
Пытаются настраивать ОС Terraform-ом. Симптом: сложные provisioner и хрупкие конфигурации.
Причина: перепутаны провижининг и настройка. Что делать: Terraform создаёт ресурсы, Ansible настраивает
внутри (урок 12.10).
Провайдер не ставится (init падает). Симптом: init не может скачать провайдер. Причина: прямой
доступ к реестру из региона может не работать (10.08). Что делать: зеркало Yandex Cloud (стенд блока),
кэш плагинов, чтобы качать раз.
7. Проверка усвоения
- Чем декларативный подход Terraform отличается от императивных команд
ycиз блока 11? Что делает повторныйapply? - Что такое state и почему это «источник правды» о том, чем Terraform управляет?
- Кто-то изменил ресурс руками. Как Terraform это обнаружит и что сделает при
apply? - Чем
planотличается отapplyи почему план важен для ревью и безопасности? - Где место Terraform относительно Ansible и CI? Приведите по задаче для каждого.
- Почему инфраструктуру оформляют модулем, а не плоским набором файлов, уже на первом стенде?
- Что гарантирует
destroyи как это связано с дисциплиной уборки из блока 11?
8. Материалы
- Terraform: What is Terraform — цели IaC, декларативная модель, рабочий процесс; отправная точка. На английском.
- Terraform: State — зачем нужен state, что в нём и почему его берегут (подробно — урок 12.04). На английском.
- Infrastructure as Code (Kief Morris) — принципы IaC поверх конкретного инструмента: почему код, дрейф, идемпотентность. На английском.
- Yandex Cloud: начало работы с Terraform — установка, зеркало провайдеров, первый ресурс в Yandex Cloud (пригодится к уроку 12.09).
- Блок 10 этого курса (Ansible) — декларативность и сходимость для настройки; Terraform применяет те же идеи к провижинингу ресурсов.
← Предыдущий: 11.11. Serverless: функции и контейнеры по запросу · Блок 12 · Следующий: 12.02. HCL: провайдеры, ресурсы, переменные, выводы, data-источники →
Материалы для практики
Скрипты, шпаргалки и заготовки к заданиям урока. Текст урока — на этой странице.
-
Материалы к уроку 12.01 «Инфраструктура как код цели, декларативная модель, состояние».zip