Зарегистрируйтесь, чтобы продолжить обучение

Бекенды для хранения состояния Terraform: Основы

Terraform хранит текущее состояние инфраструктуры в файле с расширением .tfstate. При выполнении операций Terraform идет в файл состояния и проверяет, какая инфраструктура уже развернута. На основе того, что есть в состоянии и что описано в проекте, Terraform понимает, что нужно сделать — создать инфраструктуру или изменить.

По умолчанию состояние Terraform хранится локально в вашей папке с проектом Terraform. Этого достаточно для небольших личных проектов, инфраструктурой которых управляем только мы. Но если мы работаем над проектом и его инфраструктурой в команде, возникают проблемы.

Например, два разработчика могут одновременно запустить изменение инфраструктуры. Или в разных ветках проекта могут храниться разные состояния инфраструктуры.

Удаленное хранение состояния Terraform изолирует состояние инфраструктуры от системы контроля версий. Также это позволяет ограничивать доступ к состоянию, если инфраструктура находится в процессе изменения кем-то еще. Как это делается — разберем в уроке.

Зачем нужно удаленное хранилище состояния

Когда мы управляем своей инфраструктурой на одной машине, схема работы с Terraform выглядит просто. Мы обновляем инфраструктуру, ее состояние изменяется.

У нас локально всегда хранится актуальный файл с состоянием:

Terraform local state

Когда над проектом работает команда, процесс усложняется. Состояние инфраструктуры нужно как-то синхронизировать и актуализировать у всех участников команды.

Нам нужно делиться с командой актуальным состоянием инфраструктуры. Это проще делать через удаленное хранилище, где все члены команды могут найти это состояние.

Если организовать этот процесс через git-репозиторий, нужно сначала обновить инфраструктуру, затем добавить коммит с новым tfstate и отправить его в удаленный репозиторий. Коллега должен получить из репозитория актуальный tfstate, внести свои изменения, и в свою очередь отправить изменения в коде Terraform и новый файл состояния в Git:

Terraform git state

Такой подход требует концентрации внимания и может привести к проблемам:

  • Человеческий фактор. Один из разработчиков может забыть обновить state из репозитория и применит неправильные изменения на инфраструктуру
  • Неконсистентность состояний. В git могут храниться разные версии состояния, и кто-нибудь применит неактуальные изменения на инфраструктуру
  • Конфликт применения изменений. Terraform будет работать у обоих с локальным файлом состояния и не будет знать о том, что кто-то уже обновляет ту же инфраструктуру

Чтобы избавиться от этих проблем, Terraform предлагает собственный механизм удаленного хранения состояния. В нем большую часть операций контроля и синхронизации Terraform выполняет автоматически.

Концепция Terraform, описывающая удаленное хранение состояния, называется remote backend.

Что такое Terraform remote backends

В терминологии Terraform backend — это решение, которое отвечает за хранение состояния. Если состояние хранится удаленно — это remote backend.

При использовании remote backend Terraform сохраняет состояние в удаленное хранилище, а локально в tfstate хранит только информацию об этом удаленном хранилище.

В качестве хранилища может выступать любое облачное объектное хранилище по типу Amazon S3: Google Cloud Storage, Azure Storage, Yandex Cloud Storage и другие подобные решения. Также Terraform может использовать для удаленного хранения состояния HTTP-сервер, базу данных PostgreSQL или облачную платформу Terraform Cloud.

В схеме с remote backend при выполнении любых операций над инфраструктурой Terraform будет обращаться к удаленному файлу состояния, блокировать его на время выполнения изменений, затем перезаписывать этот файл с учетом внесенных изменений:

Terraform remote state

C remote backend Terraform самостоятельно будет решать ту проблему синхронизации состояний, которая возникала при командной работе с Git. Любой участник процесса при работе с инфраструктурой будет получать единственный актуальный файл состояния — у него не будет возможности получить некорректный.

Еще Terraform remote backend нативно поддерживает механизмы блокировки состояния. Если начать изменять какую-либо инфраструктуру, ее состояние не будет доступно никому другому, пока все изменения не будут применены. Это позволит исключить любые конфликты с параллельным применением изменений на одной и той же инфраструктуре.

Перейдем к практике и попробуем организовать хранение состояния удаленно. В качестве remote backend используем хранилище типа S3.

Как настроить хранение состояния в S3

Настроим хранение состояния в облаке YandexCloud. Будем использовать консольную утилиту облака YandexCLI. Она позволит создавать объекты в облаке через терминал.

Для достижения цели нам понадобятся:

  • Хранилище файлов типа S3, в которое Terraform будет сохранять состояние инфраструктуры
  • Бессерверная БД типа DynamoDB, в которой Terraform будет фиксировать блокировки
  • Сервисный аккаунт облака, через который Terraform сможет управлять содержимым S3 и DynamoDB

Эти объекты в облаке нам предстоит создать. Технически мы можем использовать Terraform и для управления этими объектами тоже. Но к моменту выполнения terraform init в проекте они уже должны существовать. То есть объекты для хранения remote backend должны быть созданы в другом проекте Terraform с локальным состоянием.

Для упрощения процесса создадим нужные для remote backend объекты вручную. Они в дальнейшем почти никогда не меняются.

Подготавливаем облако для хранения состояния

Создадим в облаке S3-хранилище yc-hexlet-state объемом 10МБ. Этого хватит для хранения состояния надолго:

yc storage bucket create --name yc-hexlet-state --max-size 10000000

Дальше нам нужно сделать табличку в облачной базе данных, где Terraform будет фиксировать блокировки состояния:

yc ydb database create terraform-state-lock --serverless

done (7s)
id: etnpkn3gs4s56qk9g7kf
folder_id: ...
created_at: ...
name: terraform-state-lock
...
document_api_endpoint: https://docapi.serverless.yandexcloud.net/ru-central1/b1gjrod3dvqni46u3paj/etnpkn3gs4s56qk9g7kf

Сохраним document_api_endpoint, он нам понадобится при конфигурации Terraform.

Таблицы в YDB YandexCli создавать не умеет, поэтому таблицу добавим в веб-интерфейсе облака. Найдем нашу базу в разделе «Managed Service for YDB»:

Yandex Managed YDB

Нам нужен раздел «Навигация». Там найдем опцию «Создать таблицу» и создадим документную таблицу lock с колонкой LockID типа String, которая будет являться ключом партиционирования:

Yandex Managed YDB

Далее через YandexCLI создадим сервисный аккаунт hexlet-remote, который будет сохранять состояние Terraform в облачное хранилище:

yc iam service-account create --name hexlet-remote --description "SA to manage terraform state"

id: ajejk11p9ls1vvhc12mb
folder_id: ...
created_at: ...
name: hexlet-remote
description: SA to manage terraform state

В ответе получим id нового сервисного аккаунта. Он понадобится, чтобы разрешить сервисному аккаунту взаимодействовать с хранилищем.

Проверим имя каталога YandexCloud, в котором работаем:

yc resource-manager folder list
+----------------------+---------+--------+--------+
|          ID          |  NAME   | LABELS | STATUS |
+----------------------+---------+--------+--------+
| ...                  | default |        | ACTIVE |
+----------------------+---------+--------+--------+

В примере выше используется каталог default.

Выдадим роли storage.editor и ydb.editor нашему сервисному аккаунту в этом каталоге:

yc resource-manager folder add-access-binding default --role storage.editor --subject serviceAccount:ajejk11p9ls1vvhc12mb

done (1s)
effective_deltas:
  - action: ADD
    access_binding:
      role_id: storage.editor
      subject:
        id: ajejk11p9ls1vvhc12mb
        type: serviceAccount

yc resource-manager folder add-access-binding default --role ydb.editor --subject serviceAccount:ajejk11p9ls1vvhc12mb

done (1s)
effective_deltas:
  - action: ADD
    access_binding:
      role_id: ydb.editor
      subject:
        id: ajejk11p9ls1vvhc12mb
        type: serviceAccount

В --subject указываем id сервисного аккаунта, который создали ранее.

Мы разрешили сервисному аккаунту hexlet-remote просматривать и изменять S3-хранилище в нашем каталоге, а также работать с базой в Managed YDB. Теперь настроим Terraform так, чтобы он подключался к хранилищу S3 и клал туда состояние.

Создадим у сервисного аккаунта ключи доступа к S3 хранилищу:

yc iam access-key create --service-account-name hexlet-remote --description "terraform s3 backend access key"

access_key:
  id: ...
  service_account_id: ...
  created_at: ...
  description: ...
  key_id: YCABX6vQXtCjoKu_oB7QabuZO
secret: YCOL4xZ1tdpduS46z_YTlvDzYUwv8xBK_UuRq18m

Сохраним значения key_id и secret. Они понадобятся для подключения Terraform к удаленному хранилищу.

Мы создали в облаке всё необходимое для удаленного хранения состояния. Теперь настроим Terraform так, чтобы он отправлял состояние в удаленное хранилище.

Описываем remote backend в проекте Terraform

Создадим в проекте файл backend.tf и вставим туда блок backend, описывающий хранение состояния:

terraform {
  backend "s3" {
    endpoints = {
      s3 =       "https://storage.yandexcloud.net"
      dynamodb = "https://docapi.serverless.yandexcloud.net/ru-central1/b1gjrod3dvqni46u3paj/etnpkn3gs4s56qk9g7kf"
    }
    region                      = "ru-central1"
    bucket                      = "yc-hexlet-state"
    key                         = "hexlet-remote-state"
    access_key                  = "YCABX6vQXtCjoKu_oB7QabuZO"
    secret_key                  = "YCOL4xZ1tdpduS46z_YTlvDzYUwv8xBK_UuRq18m"
    dynamodb_table              = "lock"
    skip_region_validation      = true
    skip_credentials_validation = true
    skip_requesting_account_id  = true
  }
}

У каждого типа backend в Terraform свой набор параметров. Мы используем backend "s3" и указываем для него:

  • endpoints — список эндпоинтов. Здесь мы указываем сетевой адрес хранилища S3. В случае YandexCloud — это всегда storage.yandexcloud.net. У других облачных провайдеров будет другой. А так же указываем document_api_endpoint созданной нами Managed YDB
  • region — свойство, характерное для S3 конкретного облачного провайдера. У YandexCloud это всегда ru-central1
  • bucket — имя хранилища, которое мы создали
  • key — директория в хранилище, куда Terraform будет класть файл состояния
  • access_key и secret_key — соответственно, key_id и secret созданного нами ключа доступа
  • dynamodb_table — имя таблицы, которую мы создали в Managed YDB через интерфейс облака
  • Параметры skip служат для упрощения подключения к S3, в учебных целях

Инициируем инфраструктуру, выполнив terraform init.

Если изменить настройки backend в существующем проекте, необходимо будет вызвать модифицированную команду: terraform init -migrate-state. В этом случае Terraform постарается перенести локальный файл с состоянием в удаленное хранилище.

В итоге мы должны увидеть, что Terraform смог настроить работу с бэкендом s3:

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Теперь проверим, как работает наша блокировка. Откроем две вкладки терминала на проекте с Terraform. Выполним в первой вкладке terraform apply:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # yandex_vpc_network.net will be created
  + resource "yandex_vpc_network" "net" {
      ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value:

Видим стандартный вывод Terraform, информирующий о готовности изменить инфраструктуру.

Теперь зайдем во вторую вкладку и попробуем выполнить terraform apply в этом же проекте:

Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│
│ Error message: ConditionalCheckFailedException: Condition not satisfied
│ Lock Info:
│   ID:        ac5bf646-b858-c819-c847-95f260d3c613
│   Path:      yc-hexlet-state/hexlet-remote-state
│   Operation: OperationTypeApply
│   Who:       hexlet@user
│   Version:   1.3.7
│   Created:   2023-07-06 11:38:14.794552864 +0000 UTC
│   Info:
│

Здесь мы видим блокировку. В тот момент, когда мы выполнили terraform apply в первый раз, Terraform создал запись о блокировке. Пока первая сессия работы с Terraform не завершится, остальные попытки менять инфраструктуру будут блокироваться.

Так мы с помощью удаленного хранения состояния в S3-хранилище и блокировки состояния в YDB добились того, что:

  • Состояние инфраструктуры всегда будет одинаковым и актуальным у всей команды. Локально в .tfstate проекта будет храниться только адрес удаленного бэкенда
  • Не возникнут конфликты одновременного обновления инфраструктуры двумя или более членами команды

Мы настроили удаленное хранение состояния. Осталось позаботиться о безопасности и о том, чтобы ключи для нашего хранилища не утекли в сеть.

Как работать с секретами backend

В работе с секретами backend стоит придерживаться тех же правил, что при работе с другими секретами Terraform. В примере выше мы прописали все настройки backend прямым текстом. Это может сделать наши облачные S3 и Managed YDB уязвимыми.

Доработаем хранение секретов backend, чтобы обезопасить нашу инфраструктуру.

В разделе backend мы не можем обращаться к обычным переменным Terraform. Переменные как сущность относятся к самому tfstate. А на момент обработки состояния Terraform еще не знает о переменных, которые существуют в инфраструктуре. Так секретами backend понадобится управлять отдельно.

Terraform позволяет подключать секреты состояния при выполнении terraform init по одному, либо в файле с помощью параметра -backend-config.

Вернемся к нашему backend.tf и уберем из него данные, которые можно считать чувствительными:

terraform {
  backend "s3" {
    endpoint                    = "storage.yandexcloud.net"
    region                      = "ru-central1"
    key                         = "hexlet-remote-state"
    skip_region_validation      = true
    skip_credentials_validation = true
    skip_requesting_account_id  = true
  }
}

Мы убрали значения bucket, access_key, secret_key и параметры dynamodb_* для подключения к базе. Без них в открытом виде нашим данным об инфраструктуре ничего не угрожает.

Создадим отдельный файл secret.backend.tfvars и вставим в него то, что вынесли из backend.tf:

bucket                      = "yc-hexlet-state"
access_key                  = "YCABX6vQXtCjoKu_oB7QabuZO"
secret_key                  = "YCOL4xZ1tdpduS46z_YTlvDzYUwv8xBK_UuRq18m"
dynamodb_endpoint           = "https://docapi.serverless.yandexcloud.net/ru-central1/b1gjrod3dvqni46u3paj/etnpkn3gs4s56qk9g7kf"
dynamodb_table              = "lock"

Имя secret.backend.tfvars будет соответствовать маске secret.* в .gitignore нашего проекта Terraform, и файл с этими секретами не попадет в сеть.

Подключим файл с секретами backend при инициации инфраструктуры:

terraform init -backend-config=secret.backend.tfvars

Инициация должна пройти успешно. С точки зрения Terraform ничего не изменилось. Он подгружает все параметры, описанные в -backend-config, внутрь блока backend.

Для передачи файла с секретами коллегам можно применять облачные менеджеры ключей, такие как AWS Secret Manager или Yandex Lockbox.

Выводы

Удаленное хранение состояния предполагает, что Terraform сохраняет файл с состоянием инфраструктуры terraform.tfstate в некоторое удаленное хранилище. Локально он хранит только адрес удаленного хранилища и параметры подключения к нему.

Удаленное хранение решает проблемы организации доступа к актуальному состоянию Terraform, когда инфраструктура управляется более чем с одной машины. Это в основном касается командной работы, но может быть удобно и при работе с личными проектами. Это позволяет использовать единый подход на проектах любого масштаба.


Самостоятельная работа

  1. Зарегистрируйтесь в на Terraform Cloud

  2. Измените хранение стейта Terraform на удаленное в облаке Terraform Cloud


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»