Прямой ответ
Terraform решает одну задачу: инфраструктура описывается декларативным кодом, а не создаётся руками в консоли облака. Вы пишете, что должно существовать (серверы, сети, DNS, бакеты), terraform plan показывает дифф «что изменится», terraform apply приводит реальность к описанию. Три концепции, на которых держится всё: провайдеры (плагины для конкретных облаков/API), ресурсы (объекты инфраструктуры с аргументами) и state (файл состояния — память Terraform о том, что он создал; именно он отличает IaC от «скрипта создания»). Рабочая дисциплина: state хранится удалённо с блокировкой (backend), изменения — только через plan/apply в ревьюируемых PR, секреты в коде не живут. После первой сотни строк HCL становится понятно главное свойство: инфраструктура перестаёт быть «тем сервером, который Петя настроил в марте» и становится кодом с историей, ревью и откатом.
Модель: провайдеры, ресурсы, план
Минимальный файл: блок terraform с версией и backend, блок provider (например, AWS с регионом), ресурсы (resource "aws_instance" "web" { ami = ..., instance_type = ... }). Цикл работы: terraform init (скачивание провайдеров, инициализация backend) → terraform plan (чтение state + API облака, построение диффа) → чтение плана глазами (обязательный шаг, а не формальность: «destroy» незапланированного ресурса виден именно здесь) → terraform apply. Декларативность означает, что вы описываете целевое состояние, а не последовательность действий: изменение instance_type в коде — и apply заменит или обновит инстанс сам, в зависимости от того, поддерживает ли провайдер изменение на месте. Отсюда же — идемпотентность: повторный apply без изменений кода ничего не делает («No changes»), и это главный признак того, что инфраструктура действительно описана, а не «создана один раз».
State: сердце системы и источник главных бед
State-файл (terraform.tfstate) — JSON с маппингом «ресурс в коде → объект в облаке» плюс атрибуты. Три следствия, которые определяют эксплуатацию. Первое: state — это секрет (внутри бывают чувствительные атрибуты) — он лежит в приватном бакете/хранилище, не в git, доступ ограничен. Второе: state нельзя терять или ветвить — удалённый backend (S3+DynamoDB-lock, HTTP-backend, Terraform Cloud) с блокировкой обязателен для команды: два одновременных apply без лока портят состояние; локальный terraform apply с ноутбука при живом CI — авария. Третье: рассинхрон с реальностью (кто-то удалил ресурс руками в консоли) лечится terraform plan (увидит расхождение) и terraform import/terraform state rm (ручная сверка). И бэкап state — часть стратегии резервного копирования инфраструктуры (стратегия 3-2-1): потеря state при живой инфраструктуре — долгая ручная реконструкция.
Переменные, окружения и модули
Параметризация: variable (вход) и output (выход), значения — из *.tfvars-файлов, переменных окружения (TF_VAR_*) и секретных хранилищ; дефолты в коде — только для несекретного. Мультиокружения решаются двумя школами: отдельный state на окружение (dev/stage/prod — разные каталоги или workspaces, один и тот же код с разными tfvars — популярнее и безопаснее: apply в dev физически не может тронуть prod) и одно большое описание с count/for_each по окружениям (компактнее, но ошибка в общем коде бьёт сразу везде). Модули — переиспользуемые наборы ресурсов («модуль веб-сервиса: инстанс + балансировщик + DNS»); разумная гранулярность — по архитектурной единице, а не по каждому ресурсу; чужие модули из реестра читаются до применения (это код с доступом к вашему облаку). Версии провайдеров и модулей пинятся (version = "~> 5.0") — «поплыть» на минорном релизе провайдера в проде неприятно.
Секреты и чувствительные данные
Правило, не имеющее исключений: пароли, ключи и токены не пишутся в HCL — ни открытым текстом, ни «временно». Варианты по возрастанию зрелости: переменные окружения для локальной работы (из секрет-менеджера, не из shell-history); data-источники, читающие секрет из облачного хранилища секретов (AWS Secrets Manager/Vault — Terraform берёт значение в момент apply, в git его нет); для значений, которые должны попасть в state, — пометка sensitive = true (скрывает из вывода plan, но в state значение всё равно есть — отсюда требование приватности state). Подходы к секретам в разработке в целом — в статье про env и секреты; в Terraform они строже, потому что state централизует чувствительное в одном файле.
Практический сценарий: первый прод-стенд за день
Реалистичный порядок для небольшого проекта. Утро: выбираем провайдера (облако или VPS-API — у большинства есть провайдер), настраиваем backend (S3-бакет + таблица блокировок создаются «нулевым» конфигом), заводим репозиторий с каталогами envs/dev и envs/prod. Пишем минимум: сеть, инстанс, security group, DNS-запись — сто строк; plan в dev, чтение диффа, apply — сервер существует, и он уже описан кодом. День: выносим повторяющееся в модуль, добавляем outputs (IP, имя записи — то, что нужно другим скриптам), подключаем ansible для наполнения сервера (разделение ролей: Terraform создаёт железо, Ansible настраивает софт). Вечер: CI — plan на каждый PR (артефактом в комментарий), apply — только из main и только с явным подтверждением; state-бакет включён в бэкапы. Через месяц проверка зрелости: новый стенд (или полная пересоздание dev) поднимается одним apply — если да, инфраструктура действительно стала кодом, а не «описана кодом наполовину».
Типовые ошибки начинающих
Первая — apply без чтения plan: Terraform честно предупреждает о пересоздании базы (destroy + create), но предупреждение надо прочитать; привычка «apply -auto-approve везде» допустима только в dev. Вторая — ручные правки в консоли рядом с Terraform: каждый «быстрый фикс руками» — будущий конфликт, который plan выявит в неподходящий момент; правки делаются в коде. Третья — state в git: и секретность, и конфликты слияния, и гонки — три проблемы одним решением; только удалённый backend с блокировкой. Четвёртая — импорт как откладывание: существующую инфраструктуру тянут в код через import-блоки постепенно, а не «когда-нибудь потом» — неописанная инфраструктура живёт своей жизнью и ломает apply. Пятая — игнорирование версий: провайдеры и сам Terraform развиваются, .terraform.lock.hc в git и плановые апгрейды дешевле аварийных.
FAQ
Terraform или OpenTofu? OpenTofu — форк с открытой лицензией (после смены лицензии Terraform на BSL), совместим по языку и провайдерам; для новых проектов выбор часто организационный (политика лицензий). Язык и практика в этой статье применимы к обоим.
Нужен ли Terraform для одного VPS? Для одного сервера — избыточен (проще скрипт создания + ansible). Окупаться он начинает со второго-третьего ресурса и там, где инфраструктуру нужно воспроизводить (несколько окружений, disaster recovery).
Как откатить неудачный apply? Откат кода в git + повторный apply (инфраструктура вернётся к описанному состоянию); для частично применённых изменений — повторный apply после правки. «Снимка до apply» у Terraform нет — отсюда ценность чтения plan и маленьких инкрементальных изменений.
Что делать с ресурсами, которые Terraform не покрывает? Провайдеров сотни, но дыры остаются: локальные exec-провижнеры (осторожно, с оговорками), null_resource с триггерами, или честное разделение — Terraform для облачных ресурсов, скрипты/ansible для остального, с явной границей ответственности.
Источники: документация Terraform (docs, state), OpenTofu docs — ссылки в шапке.