Terraform 1.0 и remote state: переводим все окружения на S3 + DynamoDB locking
HashiCorp объявила Terraform 1.0 долгосрочно стабильным. Переводим все окружения, систематизируем remote state в S3 и разбираем что ломается при переходе с 0.15.
HashiCorp объявляет Terraform 1.0 долгосрочно стабильным релизом с публичными гарантиями обратной совместимости внутри major-версии
HashiCorp объявила Terraform 1.0 долгосрочно стабильным релизом с формальными гарантиями совместимости внутри major-версии. Мы разбирали что это значит ещё в мае, когда вышел GA. Сегодня HashiCorp акцентирует именно долгосрочный горизонт - что подталкивает нас наконец разобраться с remote state на всех клиентских окружениях системно, а не по мере касания.
Зачем сейчас
До сих пор у нас была ситуация «работает - не трогай». Несколько проектов держали state локально или в S3 без блокировки. Часть использовала DynamoDB locking, но конфигурация у всех немного разная - кто как настраивал, тот так и живёт. Пока Terraform был 0.x, этот беспорядок как-то терпелся: всё равно при каждом минорном обновлении надо было лезть в конфиги. С объявлением 1.0 как стабильного горизонта логика поменялась: раз уж мы переводим всё на одну версию и планируем не трогать это долго - надо привести в порядок и инфраструктуру state.
Что мы систематизируем
Стандартная схема для всех окружений: S3 в качестве backend, DynamoDB для locking, отдельный bucket и таблица на проект.
Конфигурация backend у нас теперь выглядит так:
terraform {
required_version = "~> 1.0"
backend "s3" {
bucket = "acme-terraform-state"
key = "prod/main.tfstate"
region = "eu-central-1"
dynamodb_table = "acme-terraform-locks"
encrypt = true
}
}
Таблица DynamoDB создаётся один раз на проект с одним атрибутом - LockID типа String, billing mode PAY_PER_REQUEST. Ничего лишнего.
Почему locking обязателен, а не опционален. Несколько месяцев назад один из проектов словил ситуацию: два человека параллельно запустили terraform apply в CI - один через merge в main, второй вручную. State без блокировки - оба применились частично, потом пришлось разбирать что произошло вручную. DynamoDB locking создаёт запись на время операции и блокирует параллельный запуск. Второй apply получает ошибку «state is locked» и падает чисто, без повреждения.
Шифрование state. encrypt = true включает SSE-S3 на объекте. В state попадают outputs, иногда - sensitive значения несмотря на все предосторожности. Шифровать надо всегда.
Upgrade guide с 0.15: что реально ломается
Переход с 0.14 на 1.0 технически тривиален, с 0.15 - тоже, но есть несколько мест которые стоит проверить прежде чем запускать apply.
Sensitive outputs в state. В 0.15 ввели обязательное помечание sensitive outputs - и это изменило как значения хранятся в state. При переходе с 0.14 на 1.0 через 0.15 state мигрирует автоматически, но если у вас были outputs без sensitive = true которые содержат пароли или ключи - самое время пройтись и пометить. Terraform plan после апгрейда покажет изменения в outputs - не пугаться, это нормально.
-target стал строже. В 1.0 использование -target в production вызывает предупреждение в plan, а ряд паттернов - ошибку. Если в CI-скриптах был -target для «применить только это» - надо смотреть конкретно.
Синтаксис terraform state команд. Некоторые подкоманды изменили сигнатуру в цепочке 0.13-0.15. Если есть скрипты которые делают terraform state mv или terraform state rm - проверьте что аргументы соответствуют 1.0.
Provider required_providers. Если проект ещё не перешёл на блок required_providers (остался на provider "aws" {} без explicit source) - 1.0 потребует явного указания. Это ломается при terraform init с понятной ошибкой, не при apply.
Практически: мы делаем terraform init с новой версией, смотрим на предупреждения, потом terraform plan и читаем diff глазами. Именно читаем - автоматически доверять «нет изменений» нельзя, провайдеры могут подтянуть новые версии.
Структура для нескольких окружений
На проектах с несколькими окружениями (dev/staging/prod) state разделяется через key в backend конфиге. Мы храним в одном bucket, разделяем prefix-ами:
prod/vpc/main.tfstate
prod/app/main.tfstate
staging/vpc/main.tfstate
staging/app/main.tfstate
Bucket один, IAM-политики ограничивают доступ по prefix. CI для staging не имеет доступа к prod/ prefix - это работает как дополнительный барьер от случайного apply не в то окружение.
DynamoDB таблица одна на проект - ключ блокировки включает путь к state, так что конфликтов нет.
Где сейчас
Из десятка проектов в managed-сопровождении на 1.0 уже переведено большинство. Остались два - один на 0.14 с локальным state (придётся мигрировать state в S3, это отдельная операция с terraform state push), второй на 0.15 с remote state в другом облаке.
Неожиданностей при апгрейде не было ни на одном проекте. Предупреждения от lint были - в основном про deprecated синтаксис в провайдерах, но это на стороне провайдеров, не самого Terraform.
Главный результат который уже чувствуется: когда все проекты на одной версии и с одинаковой схемой state, гораздо проще объяснять новому человеку в команде как это устроено. Раньше каждый проект надо было смотреть отдельно - у одного backend в S3 без locking, у другого Terraform Cloud, у третьего вообще state в git (да, такое тоже было). Унификация - это не красота ради красоты, это операционная экономия.