Когда вы впервые описываете инфраструктуру кодом и запускаете apply на своей машине — всё работает, и это вдохновляет. Но между «работает у меня в одиночку» и «работает в команде на продакшене» лежит несколько важных вещей, о которых учебники часто молчат. State, секреты, разделение окружений и автоматическая доставка — вот что превращает учебный пример в настоящий проект.

Эта статья — обзорная карта этих тем для тех, кто уже попробовал инфраструктуру как кода (Infrastructure as Code, IaC — описание серверов, сетей и сервисов в виде файлов конфигурации) и хочет понять, чего не хватает до «по-взрослому». Если базовые понятия пока в тумане, начните с раздела Основы IaC, а потом возвращайтесь.

Где хранить state и зачем его блокировать

State (состояние) — это файл, в котором Terraform запоминает, что он уже создал в облаке: какие ресурсы, с какими ID и настройками. Без него Terraform не знает, что менять, а что оставить. Думайте о нём как о бухгалтерской книге: в реальности — серверы в AWS, в книге — записи о них, и они должны сходиться.

Когда вы учитесь, state лежит файлом terraform.tfstate рядом с кодом. Для одного человека это нормально. Но как только появляется второй разработчик — начинается беда: у каждого своя копия книги, и они расходятся.

Решение — remote state (удалённое состояние): хранить файл не локально, а в общем месте, куда смотрят все. На AWS это обычно бакет Amazon S3. Тогда книга одна на всех.

terraform {
  backend "s3" {
    bucket  = "myteam-tfstate"
    key     = "prod/network/terraform.tfstate"
    region  = "eu-central-1"
    encrypt = true
  }
}

Но общий файл рождает вторую проблему: что если два человека запустят apply одновременно? Они затрут правки друг друга и могут испортить state. Чтобы этого не случилось, нужна блокировка (locking) — пока один apply идёт, остальные ждут, как очередь в одну кабинку.

Классический способ — таблица в DynamoDB: Terraform создаёт там запись-замок на время операции.

terraform {
  backend "s3" {
    bucket         = "myteam-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "tfstate-lock"
  }
}

Начиная с Terraform 1.10 появился более простой вариант — нативная блокировка средствами самого S3, отдельная таблица не нужна; вариант через DynamoDB при этом считается устаревшим:

terraform {
  backend "s3" {
    bucket       = "myteam-tfstate"
    key          = "prod/network/terraform.tfstate"
    region       = "eu-central-1"
    use_lockfile = true
  }
}

Полезная привычка: включить версионирование бакета S3 — тогда испорченный state можно откатить к предыдущей версии.

Важная развилка инструментов. У Terraform state — ваша забота. А вот CloudFormation и CDK (надстройка над CloudFormation, где инфраструктура описывается на обычном языке программирования) состояние ведут сами внутри AWS: отдельного файла нет, заводить бакет под state не нужно, блокировка тоже на стороне AWS. Оговорка про CDK: бакет всё-таки появится, просто не под состояние — команда cdk bootstrap заводит в аккаунте служебный бакет под артефакты развёртывания (и репозиторий под образы), и без него cdk deploy не пройдёт. Подробнее об инструментах — в статьях про Terraform, CloudFormation и CDK.

Секреты: чего нельзя делать

Секрет — это любое значение, утечка которого опасна: пароль к базе, ключ API, токен. С секретами в IaC два железных правила.

Первое: не коммитить секреты в git. Файл с паролем в репозитории — это пароль, который видят все с доступом к коду, и который останется в истории навсегда, даже если вы потом его удалите.

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

Отсюда два вывода, и второй важнее первого.

Первый: секреты держат в специальном сервисе, а не в коде.

  • AWS Secrets Manager — хранилище с шифрованием и автоматической ротацией (сменой) паролей, удобно для учётных данных к базам.
  • SSM Parameter Store — параметры приложения; тип SecureString шифрует значение ключом KMS.

Второй: раз секрет в state всё равно попадёт, state защищают как сам секрет — шифруют в бакете (encrypt = true), включают версионирование и закрывают доступ так, чтобы читать его могли единицы. Управление доступом — тема статьи про IAM.

А лучше всего — вовсе не тащить значение секрета через Terraform. Пусть Terraform передаст приложению только ссылку (имя или ARN секрета), а само значение приложение прочитает уже на ходу, своими правами:

data "aws_secretsmanager_secret" "db" {
  name = "prod/db/password"
}

Здесь Terraform узнаёт, где лежит пароль, но не сам пароль, — и в state попадает безобидная ссылка. Стоит взять за правило: если можно обойтись ссылкой, обходитесь ссылкой.

Несколько окружений (dev, prod)

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

В Terraform есть два пути. Workspaces (рабочие пространства) разделяют state внутри одного бакета и одной конфигурации — это удобно для мелочей, но опасно для prod: легко перепутать активное пространство и применить изменения не туда. Для надёжной изоляции продакшена лучше отдельные директории и отдельный state на каждое окружение: у dev своя папка и свой ключ state, у prod — свои.

environments/
  dev/   -> key = "dev/terraform.tfstate"
  prod/  -> key = "prod/terraform.tfstate"

Чтобы две директории не превратились в две копии одного кода, саму конфигурацию выносят в модуль, а в envs/dev и envs/prod остаются только вызов модуля с параметрами окружения и свой backend: prod и dev применяют один и тот же код с разными значениями.

Разные файлы state — разные «бухгалтерские книги», и одно окружение не видит ресурсы другого. В мире CloudFormation/CDK тот же принцип достигается отдельными стеками (stack — именованный набор ресурсов): myapp-dev и myapp-prod существуют независимо.

IaC в конвейере доставки

Запускать apply руками со своего ноутбука — нормально, пока вы один и проект учебный. В команде это превращается в конвейер доставки (CI/CD — автоматический процесс сборки и выкатки изменений), и инфраструктура едет по тем же рельсам, что и код.

Базовый рабочий процесс простой и пошаговый:

  1. Вы открываете pull request (запрос на слияние ветки) с правкой инфраструктуры.
  2. Конвейер автоматически запускает terraform plan — это сухой прогон, который показывает, что изменится, ничего не трогая. План попадает в обсуждение pull request, и коллеги его смотрят.
  3. После одобрения и слияния (merge) конвейер запускает terraform apply — изменения применяются. В CloudFormation/CDK ту же роль играют cloudformation deploy и cdk deploy.

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

terraform plan -out=tfplan
terraform apply tfplan

apply с готовым файлом плана заодно не спрашивает подтверждения — в конвейере это как раз то, что нужно.

Так ни одно изменение продакшена не происходит «втихую»: сначала видимый план на ревью, потом применение ровно его.

Остаётся вопрос: как конвейеру войти в AWS? Соблазн — положить вечный ключ доступа (access key) в настройки CI. Это плохая идея: такой ключ долгоживущий, и если он утечёт, им можно пользоваться сколько угодно. Правильный способ — OIDC-федерация (OpenID Connect): CI на лету получает короткоживущий токен, действующий ровно на одну задачу, и AWS выдаёт временные права по доверию, а не по сохранённому паролю.

permissions:
  id-token: write
  contents: read
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::111122223333:role/ci-terraform
    aws-region: eu-central-1

Принципы самого конвейера — в разделах Принципы pipeline и Стратегии релизов.

Как ловить drift

Drift (дрейф) — это расхождение между тем, что описано в коде, и тем, что реально в облаке. Появляется он, когда кто-то правит ресурс вручную через консоль: код говорит одно, реальность — другое, и при следующем apply бывают сюрпризы.

Ловят дрейф проще всего регулярной проверкой. В Terraform это периодический terraform plan: если план не пустой, хотя вы ничего не меняли — значит, реальность ушла от кода.

terraform plan -detailed-exitcode

Флаг здесь главный: без него plan завершается успешно в любом случае, и ночная проверка «упало или нет» не заметит ничего. С флагом код возврата становится содержательным: 0 — изменений нет, всё сходится; 2 — изменения есть, то есть дрейф; 1 — сама команда сломалась. Различать 1 и 2 обязательно, иначе ошибка доступа будет выглядеть как найденный дрейф.

У CloudFormation для этого есть встроенное обнаружение дрейфа (drift detection):

aws cloudformation detect-stack-drift --stack-name myapp-prod

А CDK предоставляет команду, которая под капотом вызывает ту же проверку CloudFormation:

cdk drift

Команда появилась в CDK CLI версии 2.1017.0 — если в ответ прилетает «unknown command», значит, инструмент старее, и его надо обновить.

Хорошая привычка — гонять такую проверку по расписанию (например, ночью) и оповещать команду, если дрейф найден. Тогда ручные правки не накапливаются незаметно.

Где это применяется

Эти темы всплывают ровно в тот момент, когда инфраструктура из личного эксперимента становится командной. Remote state и блокировка — как только над проектом работает больше одного человека. Разделение окружений — как только появляется реальный продакшен, который нельзя ронять. Конвейер с планом на ревью — когда изменения инфраструктуры должны быть прозрачными и обратимыми, как обычный код.

Типичные ошибки новичков, и у каждой есть прямое лекарство из этой статьи:

  • оставить state локальным файлом и потом долго мирить расхождения;
  • вписать пароль прямо в конфигурацию и забыть, что он осел в state;
  • путать workspace и применить prod-правку в dev;
  • положить вечный ключ AWS в настройки CI.

Что учить дальше: устройство конкретных инструментов — Terraform, CloudFormation, CDK; базу облака, чтобы понимать, что именно вы описываете кодом — Основы AWS, сети, IAM; и принципы доставки в целом — ветки и релизы. Если инфраструктура едет в Kubernetes, пригодятся основы Kubernetes и деплой и конфигурация.