Terraform на AWS: провайдеры, state и remote backend
Terraform — облачно-агностичный IaC на HCL: AWS-провайдер маппит ресурсы на API AWS, но, в отличие от CloudFormation, state-файлом владеешь ты. Освой remote backend, locking и чтение plan — иначе порча state и тихий replace возьмут верх над тобой.
Два инженера запускают terraform apply против одного конфига в одну и ту же минуту. Lock нет, потому что state лежит в одном S3-бакете с выключенным locking «чтобы было проще». Оба чтения видят одно стартовое состояние; оба записи гонятся перезаписать объект. Вторая запись побеждает, только что созданный первым инженером NAT-шлюз теперь сирота, больше не отслеживаемый в state, а счётчик serial в state-файле рассинхронизирован с реальностью. Следующий apply пытается «создать» ресурсы, которые уже существуют, и падает на дублирующихся именах — а неотслеживаемый NAT-шлюз тихо набегает на $32 в месяц, которые никто не найдёт до отчёта по затратам. С CloudFormation этот класс багов почти невозможен: AWS держит state стека на своей стороне и сериализует изменения. Terraform отдал тебе эту ответственность, а ты её пропустил.
Провайдеры: HCL поверх API любого облака
С CloudFormation и CDK ты познакомился в предыдущем уроке — это AWS-нативные инструменты, где AWS парсит твой шаблон и отслеживает стек за тебя. Terraform от HashiCorp делает другую ставку: один инструмент, один язык (HCL, HashiCorp Configuration Language) на все облака. Мост к каждой платформе — провайдер, плагин, который маппит HCL-блоки ресурсов на вызовы API конкретного вендора. Провайдер aws превращает блок aws_instance в вызовы EC2 RunInstances; провайдер cloudflare или google делает то же для своих API. Один конфиг может объявлять ресурсы сразу через несколько провайдеров — в этом весь смысл: мульти-облачное или мульти-вендорное хозяйство, описанное одним языком и одним workflow.
Ты объявляешь провайдер и ресурс, и это и есть единица работы. Блок ниже фиксирует версию AWS-провайдера, задаёт регион и просит бакет:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-central-1"
}
resource "aws_s3_bucket" "assets" {
bucket = "acme-prod-assets"
tags = { Team = "platform" }
}Ограничение версии ~> 5.0 значит больше, чем кажется: провайдеры выкатывают ломающие изменения, и незафиксированный провайдер может переписать половину твоего plan после несвязанного апгрейда. Фиксируй его так же, как фиксируешь transform в CloudFormation.
State: то, что CloudFormation прячет, а Terraform отдаёт тебе
Вот определяющее различие уровня senior. Когда ты делаешь apply, Terraform пишет state-файл, который записывает связки между объектами в реальном облаке и инстансами ресурсов в твоём конфиге — фактические ID ресурсов, атрибуты и метаданные зависимостей. На следующем запуске Terraform читает этот state, обновляет (refresh) его против живого API, чтобы обнаружить drift (реальный мир расходится с тем, во что верит state), вычисляет разницу с твоим конфигом и планирует только дельту. State и есть память Terraform; без него инструмент не отличит «create» от «update».
У CloudFormation такая же память, но AWS держит её на своей стороне, и ты её не трогаешь. Terraform делает её файлом, за который ты отвечаешь, — и у этой ответственности острые края:
- Локальный state опасен для команд.
terraform.tfstateна одном ноутбуке нельзя расшарить, он теряется, если ноутбук умрёт, и ничего не сериализует — двое не могут безопасно работать вместе. - State может содержать секреты. Пароль RDS или сгенерированный ключ попадает в state открытым текстом. Коммит state в git его утекает; backend обязан шифровать его в покое.
- Никогда не правь state руками. Это производный индекс, а не исходник. Ручная правка рассинхронизирует его с реальностью; используй сабкоманды
terraform stateилиterraform import, чтобы взять существующий ресурс под управление.
Все три правила сводятся к одному принципу: относись к state как к общей, зашифрованной, блокируемой базе данных — потому что именно этим он и является. Пропусти любое из них, и ты на один параллельный apply или на один git push от утечки учёток или потери ресурсов.
Remote backend и state locking
Фикс для всех опасностей state — remote backend. S3-backend хранит объект state в S3-бакете (bucket + key + region), чтобы вся команда читала и писала одну общую долговечную копию, и шифрует этот state в покое. Вторая половина — state locking: перед изменением state Terraform берёт lock, так что параллельный apply обязан ждать, а не гнаться. Исторически ты задавал таблицу DynamoDB (partition key с именем LockID) через dynamodb_table; этот путь ещё работает, но теперь deprecated в пользу S3-native locking — задай use_lockfile = true, и Terraform пишет объект tflock рядом со state для координации, без отдельной таблицы.
terraform {
backend "s3" {
bucket = "acme-tf-state"
key = "prod/network/terraform.tfstate"
region = "eu-central-1"
encrypt = true
use_lockfile = true
}
}С этим гонка из Hook невозможна: apply второго инженера блокируется на локе, пока первый не закончит и не отпустит его. Это та конфигурация, которую команда «чтобы было проще» пропустила.
| Аспект | CloudFormation / CDK | Terraform |
|---|---|---|
| Охват облаков | Только AWS | Мульти-облако через провайдеры |
| Владение state | Управляется AWS, на сервере | State-файлом владеешь ты |
| Безопасность параллелизма | Сериализуется AWS | Locking настраиваешь ты |
| Написание | YAML/JSON (CFN) или реальный язык (CDK) | DSL HCL |
| Секреты в state | Скрыты AWS | В state; backend надо шифровать |
Чтение plan и модули
terraform plan — это предохранитель, и чтение его — навык уровня senior. Он печатает diff с четырьмя действиями: + создать, - удалить, ~ обновить на месте и -/+ replace — удалить, затем создать заново. Последний символ — опасный. Поле, которое нельзя изменить на месте (переименование идентификатора БД, смена неизменяемого атрибута), вынуждает replace, а replace базы данных значит, что её уничтожают и создают свежую пустую. Plan говорит тебе об этом простым текстом — -/+ destroy and then create replacement — и инженер, который применяет, не прочитав, теряет данные. Всегда читай plan перед apply; это и есть вся причина, по которой plan — отдельная команда.
terraform init # скачать провайдеры, настроить backend
terraform plan # обновить state, показать diff create/update/replace/destroy
terraform apply # взять lock, выполнить, записать state обратноПоследний строительный блок — модули, переиспользуемые параметризованные наборы ресурсов, подтягиваемые из публичного реестра или локального пути, чтобы ты написал VPC один раз и вызывал его по окружениям, а не копипастил HCL. Модули — то, как кодовая база Terraform остаётся DRY на масштабе.
▸Почему это работает
Почему «ты владеешь state» — фича, а не просто бремя? Потому что state-файл — это ещё и портируемая, инспектируемая запись всего твоего хозяйства, не привязанная к control plane одного вендора. Ты можешь через terraform import взять существующие руками собранные ресурсы под управление, рефакторить через провайдеры и запускать ровно тот же workflow, лежит ли ресурс в AWS, Cloudflare или SaaS-API. Серверный state CloudFormation безопаснее по умолчанию, но говорит он только на AWS. State-файл — это и цена, и механизм портируемости Terraform.
Команда держит prod через AWS, Cloudflare и Postgres-SaaS. Платформенные инженеры свободно владеют HCL, спокойно владеют remote backend и хотят один инструмент и один workflow на всех трёх вендоров. Выбери IaC.
Какое ключевое операционное различие между Terraform и CloudFormation порождает большинство специфичных для Terraform сбоев?
Plan показывает `-/+ aws_db_instance.primary (destroy and then create replacement)`. Что делать?
- 01Почему state-файл Terraform — центральное понятие, и какие три правила накладывает владение им?
- 02Что даёт remote backend с locking и как безопасно читать plan?
Terraform — облачно-агностичный IaC от HashiCorp, написанный на HCL, где плагин-провайдер маппит блоки ресурсов на API вендора — провайдер aws на AWS, другие на Cloudflare, Google и SaaS-API — так что один конфиг и один workflow охватывают несколько облаков. Определяющее на уровне senior отличие от CloudFormation и CDK, с которыми ты познакомился в предыдущем уроке, — это state: Terraform пишет state-файл, маппящий твой конфиг на реальные ID ресурсов, обновляет его для обнаружения drift и планирует дельту, тогда как AWS держит этот state на сервере за тебя. Владение state накладывает три правила — не используй локальный state для команд, шифруй backend, потому что state может держать секреты, и никогда не правь state руками (используй terraform import, чтобы брать существующие ресурсы под управление). Фикс — это remote backend, обычно S3 (bucket + key + region, шифрованный), плюс state locking через таблицу DynamoDB с LockID или более новый S3-native use_lockfile, который сериализует параллельные apply и предотвращает гонку порчи. Workflow — написать HCL, terraform plan, затем terraform apply, и чтение plan необсуждаемо: + создаёт, - удаляет, ~ обновляет на месте, а -/+ заменяет — удалить и создать заново, что для stateful-ресурса значит потерю данных. Модули держат большие кодовые базы DRY. Правило выбора простое: тянись к Terraform, когда ты мульти-облачный или хочешь один инструмент на провайдеров и принимаешь управление state; тянись к CloudFormation или CDK, когда ты целиком на AWS и хочешь, чтобы state вёл за тебя AWS. Теперь, когда откроешь новый Terraform-репозиторий и увидишь terraform.tfstate рядом с .tf-файлами без конфига backend — ты будешь знать, что нужно исправить, прежде чем кто-то другой это тронет.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.