lenec ru

← все посты

Terraform state без боли: backends, locking и миграция со старой схемы

19K

Terraform state — это та папка, куда смотрят со страхом и редко. Лежит где-то в S3, кто-то его настраивал три года назад, никто не помнит, как делать backup, и каждый apply начинается с молитвы. Я через это прошёл несколько раз, в том числе мигрируя со старого local state в S3+DynamoDB и потом в Terraform Cloud. Ниже — что и как настраивать в 2026, чтобы спать спокойно.

Версия — terraform 1.9. Опенть, OpenTofu 1.8 ведёт себя идентично, все команды и backends те же.

Что такое state и почему без него никак

State — это JSON-файл, в котором terraform хранит маппинг между ресурсами в коде и их реальными ID в облаке. Когда ты пишешь:

resource "aws_s3_bucket" "data" {
  bucket = "my-app-data"
}

...terraform создаёт bucket в AWS и записывает в state: «aws_s3_bucket.data → arn:aws:s3:::my-app-data». В следующий terraform plan он смотрит state, идёт в AWS, читает реальное состояние bucket и сравнивает с кодом. Без state он не знает, какие ресурсы он создавал.

State содержит секреты — пароли БД, ключи, любые sensitive-output. Это первая причина не хранить его в git.

Backends: где state лежит

local — только для локальной игры

По умолчанию terraform пишет state в terraform.tfstate в текущей папке. Никаких блокировок, никакой шары. Если два инженера в команде запустят apply одновременно — последний выиграет, остальные потеряют изменения.

Local — это для одиночных экспериментов и tutorials. В команде сразу настраиваешь remote backend.

S3 + DynamoDB — классика для AWS

Самый распространённый remote backend. State хранится в S3, locking — в DynamoDB.

terraform {
  backend "s3" {
    bucket         = "company-tf-state"
    key            = "prod/network/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

В новых версиях terraform появился флаг use_lockfile = true и нативная поддержка S3-native locking без DynamoDB (через S3 object locks). Если стартуешь с нуля в 2026 — стоит присмотреться, минус одна сущность. Я пока консервативен и держу DynamoDB, потому что в проде уже работает.

Bucket для state нужно настраивать так:

resource "aws_s3_bucket" "tf_state" {
  bucket = "company-tf-state"
}

resource "aws_s3_bucket_versioning" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_public_access_block" "tf_state" {
  bucket                  = aws_s3_bucket.tf_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Версионирование — обязательно. Если state случайно повредится, можно восстановить предыдущую версию.

GCS, Azure Blob — для своих облаков

Аналог S3 в Google Cloud и Azure. Конфиги те же, синтаксис чуть другой:

terraform {
  backend "gcs" {
    bucket = "company-tf-state"
    prefix = "prod/network"
  }
}

GCS поддерживает object versioning и встроенный locking без DynamoDB-аналога.

Terraform Cloud / Terraform Enterprise

Hashicorp-managed backend плюс много обвязки: workspace UI, audit log, RBAC, run triggers. Если у тебя 30+ инженеров и нужен governance — это хороший выбор. Но это уже подписка и $$, и lock-in.

OpenTofu state encryption

OpenTofu добавил встроенное шифрование state на стороне клиента. Это решает проблему «у меня в state пароль БД, и кто-то с readonly access к S3 его прочитает». Если на тебя смотрит compliance — посмотри в эту сторону.

Locking: чтобы apply не убивал apply

Locking — это механизм, который не даёт двум terraform-сессиям менять state одновременно. Без него гонки гарантированы.

В S3 backend lock реализуется через DynamoDB-таблицу с одной строкой на ключ state-файла. Один apply берёт lock, другой ждёт.

resource "aws_dynamodb_table" "tf_locks" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Если apply упал в середине (terraform убит SIGKILL, инженер закрыл терминал) — lock остаётся в таблице. Снять руками:

terraform force-unlock <lock-id>

ID берётся из сообщения об ошибке при попытке нового apply. Делать force-unlock без понимания, кто держит lock — опасно: можно потерять чужие изменения.

Структура state-файлов

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

infra/
  network/      # VPC, subnets, route tables
    backend.tf  # key = "prod/network/terraform.tfstate"
  cluster/      # EKS
    backend.tf  # key = "prod/cluster/terraform.tfstate"
  rds/          # БД
    backend.tf  # key = "prod/rds/terraform.tfstate"
  apps/         # отдельные приложения, может быть свой state на app
    api/
    worker/

Между state-файлами нужны зависимости — это делается через terraform_remote_state:

data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "company-tf-state"
    key    = "prod/network/terraform.tfstate"
    region = "eu-central-1"
  }
}

resource "aws_eks_cluster" "main" {
  name     = "prod"
  role_arn = aws_iam_role.eks.arn
  vpc_config {
    subnet_ids = data.terraform_remote_state.network.outputs.private_subnet_ids
  }
}

Альтернатива — data sources, которые читают AWS напрямую (например, aws_vpc по тегу). Это менее coupled, но и менее explicit.

Миграция со старой схемы

Часто приходишь в проект, где state лежит в одном гигантском файле, или вообще в local. Что делать:

Шаг 1: бэкап

terraform state pull > backup-$(date +%Y%m%d-%H%M).tfstate

Это руками. Делай каждый раз перед любой миграцией. Серьёзно. Я однажды потерял неделю работы команды, потому что не сделал бэкап перед split.

Шаг 2: настрой новый backend

Допустим, у тебя был local state, переходишь на S3. Сначала создаёшь bucket и DynamoDB-таблицу (отдельным small-проектом, который потом сам в себя в state добавишь, или вручную через AWS CLI).

Потом в основном проекте добавляешь backend-блок:

terraform {
  backend "s3" {
    bucket         = "company-tf-state"
    key            = "prod/main/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

И запускаешь:

terraform init -migrate-state

terraform спросит «копировать local state в S3?» — отвечаешь yes. После этого terraform.tfstate в локальной папке можно удалить (а лучше переименовать на пару дней, пока не убедишься, что всё работает).

Шаг 3: split одного огромного state на несколько

Это самая болезненная процедура. Если у тебя в одном state-файле 2000 ресурсов — split их по логическим областям.

# из старого state переносим ресурсы в новый
terraform state mv -state-out=../network/terraform.tfstate \
  aws_vpc.main aws_vpc.main

terraform state mv -state-out=../network/terraform.tfstate \
  'aws_subnet.private[0]' 'aws_subnet.private[0]'

После каждой партии переноса делай terraform plan в обоих местах — должно показывать «No changes». Если показывает diff — что-то перенесено не туда, откатывайся из бэкапа.

Реалистично: для проекта с 500 ресурсами split занимает день-два, не пробуй сделать вечером перед уходом домой.

Что не делать никогда

  • Не править state.json руками. Только через terraform state ... команды.
  • Не коммитить .tfstate в git. Там секреты.
  • Не запускать apply из двух мест без lock. Сломаешь state.
  • Не использовать один state на десять окружений. dev, staging, prod — отдельные state-файлы или отдельные workspace.

Что запомнить

State — это сердце terraform, относись к нему как к БД с продакшен-данными. Backend в S3 (или GCS) с шифрованием и версионированием. Locking через DynamoDB или нативный S3 lock. Один state на одну логически изолированную область. Бэкап перед любой миграцией. Никогда не правь руками.

Куда копать: официальная документация по state, и про OpenTofu — у них хорошие гайды по encryption и миграции с terraform. Если планируешь модульную инфраструктуру с кучей stacks — присмотрись к Terragrunt, он автоматизирует backend-конфиги и зависимости между state-файлами.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.