Мастер-класс для администраторов, которые хотят освоить Infrastructure as Code с помощью OpenTofu и Terragrunt на примере Yandex Cloud.
- Terraform & Terragrunt: IaC от первого ресурса до multi-env
- Содержание
- 1. Что такое IaC и зачем это нужно
- 2. OpenTofu и Terraform
- 3. Подготовка окружения
- 4. Жизненный цикл: init → plan → apply → destroy
- 5. State: что это и зачем
- 6. Ресурсы
- 7. Провайдеры
- 8. Источники данных (data sources)
- 9. Переменные, locals, outputs
- 10. Мета-аргументы
- 11. Циклы: count и for_each
- 12. Условия и for-выражения
- 13. Динамические блоки
- 14. Встроенные функции
- 15. Модули
- 16. Terragrunt
- 17. Операции со state
- 18. Import: описание существующей инфраструктуры
- 19. Рефакторинг: блоки
movedиremoved - 20. Drift detection и
tofu refresh - 21. Workspaces — и почему мы их не используем
- 22.
tofu console— REPL для функций и выражений - 23. Передача данных между root-модулями
- 24. CI/CD интеграция
- 25. Troubleshooting
- 26. Карта примеров
Infrastructure as Code — подход, при котором инфраструктура описывается в виде кода, хранится в git и применяется автоматически.
| Ручное управление | IaC |
|---|---|
| «Зайди в консоль, нажми кнопку» | Текстовый файл, PR, CI/CD |
| Состояние в голове или wiki | State-файл — единственный источник истины |
| Воспроизводимость: «кажется, так делали» | tofu apply — одинаковый результат всегда |
| Аудит: кто что сделал? | git log |
OpenTofu использует декларативный подход: вы описываете что должно быть, а не как этого достичь. Инструмент сам строит граф зависимостей и определяет порядок операций.
OpenTofu — форк Terraform под лицензией MPL 2.0, поддерживается Linux Foundation. На 100% совместим с существующими конфигурациями Terraform.
Terraform 1.5 (MPL) → HashiCorp закрывает исходники (BSL) → fork → OpenTofu
Для нас практических различий нет:
- Те же
.tfфайлы, тот же HCL-синтаксис - Те же провайдеры из реестра
tofuвместоterraformв командах
Зеркало провайдеров для России: https://terraform-mirror.yandexcloud.net/ (настраивается в ~/.terraformrc).
# Установка OpenTofu (macOS)
brew install opentofu
# Установка Terragrunt
brew install terragrunt
# Установка и настройка yc CLI
brew install --cask yandex-cloud-cli
yc init
# Получение IAM-токена и экспорт в переменную
export YC_TOKEN=$(yc iam create-token)
# Узнать cloud_id и folder_id
yc config get cloud-id
yc config get folder-idНастройка зеркала провайдеров ~/.terraformrc:
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*", "registry.opentofu.org/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*", "registry.opentofu.org/*/*"]
}
}Токены и пароли в
.tfvars: никогда. Только через переменные окружения:export TF_VAR_yc_token=$(yc iam create-token)
tofu init # Скачать провайдеры, подготовить .terraform/
tofu plan # Показать что изменится (ничего не создаёт)
tofu apply # Применить изменения (спросит подтверждение)
tofu apply -auto-approve # Без подтверждения (для CI)
tofu destroy # Удалить все ресурсы в stateinit — скачивает провайдеры в .terraform/ и создаёт .terraform.lock.hcl. Lock-файл фиксирует версии — коммитьте его в git.
plan — сравнивает желаемое состояние (код) с реальным (state). Показывает +/-/~ для создания/удаления/изменения.
apply — выполняет именно тот план, что показал plan. Если state изменился между plan и apply — пересчитывает.
destroy — сахар для apply -destroy.
State (.tfstate) — JSON-снимок реального состояния инфраструктуры. OpenTofu хранит в нём ID всех созданных ресурсов.
Без state OpenTofu не знает, существуют ли ресурсы уже или их нужно создать. При следующем plan он сравнивает код с state, а не с облаком напрямую.
Backends — где хранить state:
local(по умолчанию) — файл рядом с кодом. Удобно для обучения.s3— объектное хранилище (YC Object Storage, AWS S3). Для команды.
Блокировка state с OpenTofu 1.8+:
backend "s3" {
# ...
use_lockfile = true # S3-native lock, без DynamoDB
}Ресурс — базовая единица конфигурации. Описывает один объект инфраструктуры.
resource "<ТИП>" "<ИМЯ>" {
аргумент1 = значение1
аргумент2 = значение2
}ТИП— задаётся провайдером:yandex_vpc_network,yandex_compute_instanceИМЯ— локальный идентификатор внутри конфига (не имя в облаке)- Ссылка на ресурс:
yandex_vpc_network.this.id— OpenTofu автоматически строит зависимость
resource "yandex_vpc_network" "this" {
name = "my-network"
}
resource "yandex_vpc_subnet" "this" {
network_id = yandex_vpc_network.this.id # ← ссылка = зависимость
v4_cidr_blocks = ["10.0.0.0/24"]
zone = "ru-central1-a"
}→ Пример: opentofu/01-first-resource/
Провайдер — плагин, который реализует работу с конкретным API. Объявляется в блоке required_providers:
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = "~> 0.127"
}
}
}
provider "yandex" {
cloud_id = var.yc_cloud_id
folder_id = var.yc_folder_id
zone = "ru-central1-a"
}Два способа аутентификации в YC:
1. Явный токен (прозрачно для студентов):
provider "yandex" {
token = var.yc_token # из TF_VAR_yc_token
...
}2. Через yc init (чище, без токена в переменных):
provider "yandex" {
# используем переменную YC_TOKEN
cloud_id = var.yc_cloud_id
folder_id = var.yc_folder_id
}Provider aliases — несколько конфигураций одного провайдера:
provider "yandex" {
folder_id = var.folder_id_dev # default
}
provider "yandex" {
alias = "test"
folder_id = var.folder_id_test # second instance
}
module "test_env" {
source = "./modules/vm-stand"
providers = { yandex = yandex.test } # передаём aliased провайдер
}→ Примеры: opentofu/01-first-resource/ · opentofu/06-managed-pg-two-providers/ · opentofu/07-multi-folder-aliases/
Data source — read-only запрос к API. Ничего не создаёт, только читает данные.
data "<ТИП>" "<ИМЯ>" {
...условие поиска...
}Типичные применения:
# Получить последний образ Ubuntu 22.04 — не нужно хардкодить ID
data "yandex_compute_image" "ubuntu" {
family = "ubuntu-2204-lts"
}
# Читает folder_id, cloud_id, zone из yc CLI; iam_token — для проброса в другие провайдеры
data "yandex_client_config" "client" {}
# Использование
resource "yandex_compute_instance" "vm" {
boot_disk {
initialize_params {
image_id = data.yandex_compute_image.ubuntu.id # ← ref к data source
}
}
}Зачем iam_token из yandex_client_config?
Самому провайдеру yandex этот токен передавать не надо: data source возвращает значения уже сконфигурированного провайдера
iam_token нужен, когда токен надо переадресовать в другой провайдер, который не умеет читать конфиг yc сам. Классический пример — kubernetes/helm поверх Managed K8s:
data "yandex_client_config" "client" {}
provider "kubernetes" {
host = yandex_kubernetes_cluster.this.master[0].external_v4_endpoint
cluster_ca_certificate = yandex_kubernetes_cluster.this.master[0].cluster_ca_certificate
token = data.yandex_client_config.client.iam_token # ← передаем токен
}kubernetes — отдельный провайдер, он зависит от yandex, а не от себя.
→ Пример: opentofu/03-data-and-vm/data.tf
Входные параметры модуля — его публичный интерфейс.
variable "env" {
type = string
description = "Окружение: dev, test, prod."
default = "dev"
validation {
condition = contains(["dev", "test", "prod"], var.env)
error_message = "env должен быть dev, test или prod."
}
}Приоритет значений (от высшего к низшему):
-varфлаг CLITF_VAR_<name>переменная окруженияterraform.tfvars/*.auto.tfvarsdefaultвvariableблоке
Типы: string, number, bool, list(...), map(...), object({...}), any.
Промежуточные вычисления внутри модуля — не принимаются снаружи.
locals {
full_name = "${var.env}-${var.network_name}"
common_labels = { env = var.env, managed-by = "opentofu" }
}Значения, которые модуль «возвращает» вызывающему.
output "network_id" {
description = "ID сети."
value = yandex_vpc_network.this.id
}
output "connection_string" {
value = "..."
sensitive = true # не выводится в лог apply
}После tofu apply: tofu output network_id.
→ Примеры: opentofu/02-variables-outputs/
Мета-аргументы — специальные аргументы, работающие в любом ресурсе или модуле.
Явная зависимость, когда неявной недостаточно:
resource "yandex_compute_instance" "app" {
depends_on = [yandex_mdb_postgresql_cluster.this]
}Управление поведением при обновлении и удалении:
lifecycle {
ignore_changes = [boot_disk[0].initialize_params[0].image_id, metadata]
create_before_destroy = true
prevent_destroy = true # защита от случайного destroy
}Указать конкретный aliased провайдер для ресурса:
resource "yandex_vpc_network" "test" {
provider = yandex.test
}→ Примеры: opentofu/04-meta-and-language/main.tf
Создаёт N экземпляров ресурса. Адресация: resource_type.name[index].
resource "yandex_compute_instance" "bastion" {
count = var.create_bastion ? 1 : 0 # 0 = не создавать
name = "bastion-${count.index}"
}Минус: если удалить элемент из середины списка — OpenTofu пересоздаёт все последующие.
Создаёт именованные экземпляры по карте или множеству. Адресация: resource_type.name["key"].
resource "yandex_compute_instance" "servers" {
for_each = local.servers # map(object({...}))
name = each.key
# each.value — объект с полями
}Преимущество: удаление элемента по ключу не затрагивает остальные.
→ Примеры: opentofu/04-meta-and-language/main.tf
nat_ip_address = each.value.public ? yandex_vpc_address.public[each.key].external_ipv4_address[0].address : nullТрансформация и фильтрация списков/карт:
# Только публичные серверы
{ for k, v in local.servers : k => v if v.public }
# Список имён серверов
[ for name, _ in local.servers : name ]
# Карта name -> internal IP
{ for name, vm in yandex_compute_instance.servers : name => vm.network_interface[0].ip_address }→ Примеры: opentofu/04-meta-and-language/
dynamic позволяет генерировать повторяющиеся вложенные блоки из списка или карты:
resource "yandex_vpc_security_group" "main" {
dynamic "ingress" {
for_each = var.allowed_ports # list(number)
content {
protocol = "TCP"
port = ingress.value
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
}Без dynamic пришлось бы писать отдельный ingress {} блок для каждого порта вручную.
→ Примеры: opentofu/04-meta-and-language/network.tf
OpenTofu имеет богатую стандартную библиотеку. Наиболее полезные:
| Функция | Что делает | Пример |
|---|---|---|
cidrsubnet(cidr, bits, idx) |
Нарезает подсеть | cidrsubnet("10.0.0.0/16", 8, 1) → "10.0.1.0/24" |
lookup(map, key, default) |
Читает из map с default | lookup(each.value, "role", "generic") |
try(expr, default) |
Возвращает default при ошибке | try(each.value.public, false) |
coalesce(vals...) |
Первое не-null/не-пустое | coalesce(var.name, "default") |
merge(map1, map2) |
Объединяет maps | merge(local.labels, { role = "web" }) |
templatefile(path, vars) |
Рендерит файл-шаблон | templatefile("init.tpl", { hostname = each.key }) |
file(path) |
Читает файл в строку | file("~/.ssh/id_rsa.pub") |
jsonencode(val) |
Сериализует в JSON | jsonencode(local.labels) |
toset(list) |
Список → множество | toset(var.zones) |
flatten(list of lists) |
Разворачивает вложенные списки | flatten([["a","b"], ["c"]]) |
Функции работают в любом выражении HCL и вычисляются во время plan.
→ Примеры: opentofu/04-meta-and-language/locals.tf, main.tf
Модуль — директория с .tf файлами. Позволяет объединить связанные ресурсы и переиспользовать их.
Root module — директория, откуда запускается tofu apply.
Child module — модуль, вызванный через блок module {}.
module "stand" {
source = "../modules/vm-stand" # локальный путь
# Входные переменные модуля
network_name = "dev-stand"
ssh_public_key = file("~/.ssh/id_rsa.pub")
servers = {
"web" = { cores = 2, memory = 2, public = true }
}
}
# Чтение outputs модуля
output "web_ip" {
value = module.stand.instance_ips["web"].external
}Источники для source:
source = "../modules/vm-stand" # локальный
source = "git::https://github.com/org/repo.git//modules/vpc?ref=v1.0" # git
source = "yandex-cloud/kubernetes/yandex" # registryПосле изменения source или добавления нового модуля — tofu init.
→ Примеры: opentofu/modules/vm-stand/ · opentofu/05-module/
Terragrunt — обёртка над OpenTofu/Terraform, решающая проблему DRY в multi-env конфигурациях.
Без Terragrunt для dev/prod нужно дублировать provider.tf, backend.tf, повторять одинаковые inputs. Terragrunt позволяет вынести общее в корневой terragrunt.hcl и наследовать его.
envs/
├── env.hcl ← cloud_id, folder_id (в .gitignore)
└── yandex/
├── root.hcl ← root: generate provider + backend, общие locals
├── folders/
│ └── terragrunt.hcl ← создаёт каталоги dev и prod в YC
├── dev/
│ └── terragrunt.hcl ← include root, dependency на folders, source = module
└── prod/
└── terragrunt.hcl
Почему root называется
root.hcl, а неterragrunt.hcl?
terragrunt run-allсканирует подпапки и считает каждыйterragrunt.hclотдельным деплоем. Если root названterragrunt.hcl, сканер пытается обработать его как модуль и ломает резолв зависимостей. Стандартный приём с v0.50+ — переименовать вroot.hcl, а в дочерних конфигах подключать черезfind_in_parent_folders("root.hcl").
generate — генерирует .tf файл в рабочей директории при каждом запуске:
generate "backend" {
path = "backend_gen.tf"
if_exists = "overwrite"
contents = <<-EOF
terraform {
backend "s3" {
bucket = "mc-tfstate"
key = "${path_relative_to_include()}/terraform.tfstate"
use_lockfile = true
...
}
}
EOF
}include — подключить родительский конфиг:
include "root" {
path = find_in_parent_folders("root.hcl")
expose = true # позволяет читать locals родителя
}read_terragrunt_config — читать статические значения из соседнего файла (паттерн env.hcl):
locals {
env = read_terragrunt_config(find_in_parent_folders("env.hcl"))
yc_cloud_id = local.env.locals.yc_cloud_id
yc_folder_id = local.env.locals.yc_folder_id
}inputs — переменные, передаваемые в OpenTofu-модуль:
inputs = {
network_name = "dev-stand"
servers = include.root.locals.servers # из root locals
}dependency — зависимость между юнитами Terragrunt. Решает проблему «нужно сначала создать каталог, потом из его ID конфигурировать провайдер» (то, что в шаге 07 требовало двухфазного apply):
dependency "folders" {
config_path = "../folders"
# mock_outputs позволяют terragrunt init/plan работать до первого apply зависимости.
mock_outputs = {
folder_dev_id = "mock-dev-folder-id"
}
mock_outputs_allowed_terraform_commands = ["init", "validate", "plan"]
}
inputs = {
yc_folder_id = dependency.folders.outputs.folder_dev_id
}При run-all Terragrunt строит граф зависимостей и применяет юниты в правильном порядке.
S3 backend требует сервисный аккаунт со static access key. Эти ресурсы создаются разово отдельной директорией bootstrap/ через обычный tofu apply (state локально). На выходе — access_key/secret_key для экспорта в AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY и создание бакета mc-tfstate.
terragrunt plan # из директории окружения
terragrunt apply
terragrunt run-all plan # из родительской директории — все окружения сразу
terragrunt run-all apply
terragrunt run-all destroy
terragrunt validate-inputs # проверка что все inputs переданы корректноOpenTofu 1.8+ поддерживает нативную блокировку state через файл-замок в S3-бакете — без DynamoDB таблицы. Это работает с Yandex Object Storage.
→ Примеры: opentofu/08-terragrunt/
State — не только то, что читает OpenTofu, но и то, что вы как админ будете чинить руками. Минимальный набор команд:
tofu state list # все адреса ресурсов в state
tofu state show <addr> # подробности конкретного ресурса
tofu state mv <from> <to> # переименовать адрес в state (без destroy+create)
tofu state rm <addr> # удалить из state (НЕ из облака)
tofu state pull > state.json # выгрузить весь state в файл (read-only)
tofu state push state.json # ОПАСНО: залить файл обратноКогда что:
state list— первое, что делаете, если «не понимаю, что задеплоено».state show— узнать ID или атрибут, не лазая в облако.state rm— вывести ресурс из управления OpenTofu (например, при разделении монолита на части).state mv— переименовать без пересоздания. С OpenTofu 1.5+ предпочтительнее блокmoved— он попадает в git.
Lock state: при apply создаётся блокировка (.tflock в S3-бакете, если use_lockfile = true, или DynamoDB запись в классическом случае). Если apply упал и lock остался — снимать через tofu force-unlock <LOCK_ID>.
→ Тренажёр: opentofu/04b-state-ops/
Когда инфра создана руками и нужно завести её под OpenTofu — import.
# 1. Сначала пишем в коде пустой ресурс с правильными аргументами
resource "yandex_vpc_subnet" "legacy" {
# name, network_id, zone, cidr — должны совпадать с реальным
}
# 2. Импортируем
tofu import yandex_vpc_subnet.legacy <id-из-облака>
# 3. tofu plan — должен показать "No changes" (если код совпал с реальностью)import {
to = yandex_vpc_subnet.legacy
id = "e9b0000000000subnet-id"
}
resource "yandex_vpc_subnet" "legacy" {
# ...
}После tofu apply блок можно удалить из кода. Преимущества:
- проходит через PR-ревью,
- остаётся в git-истории,
- работает в
planкак отдельная операция «будет импортирован».
tofu plan -generate-config-out=generated.tfOpenTofu сам сгенерирует .tf с атрибутами реального ресурса — можно использовать как стартовый шаблон.
moved {
from = yandex_vpc_network.old_name
to = module.network.yandex_vpc_network.this
}OpenTofu увидит план «No changes. Resources moved» — записи в state переписываются, ресурсы в облаке не трогаются. После одного apply блок можно удалить.
Зачем: массовый рефакторинг (вынос в модуль, переименование, реорганизация) без простоя.
removed {
from = yandex_vpc_subnet.legacy
lifecycle {
destroy = false # КЛЮЧЕВОЕ: иначе ресурс будет реально удалён
}
}Используется, когда ресурс должен «остаться в облаке», но управляться кем-то другим (другой root, другой команды). Замена для tofu state rm через декларацию в коде.
→ Пример: opentofu/05-module/moved.tf
Drift — расхождение между state и реальностью. Бывает, когда:
- кто-то поменял ресурс через консоль/CLI,
- автоматика сменила параметры (например, autoscaling group изменил count),
- провайдер сменил поведение в новой версии.
Как ловить:
tofu plan # сравнивает код (желаемое) с state. Drift между state и облаком не показывает.
tofu plan -refresh-only # обновляет state из облака. Без этого drift вне state.
tofu apply -refresh-only # подтверждает обновление state.tofu refresh (deprecated в пользу -refresh-only) делает то же самое: тянет актуальные атрибуты из облака в state.
Антипаттерн -target:
tofu apply -target=yandex_vpc_subnet.a # применить только этоДелает state частично-свежим/частично-stale. Допустимо только в recovery-сценарии: «глобальный apply не проходит, нужно временно обойти один сломанный ресурс».
Terraform/OpenTofu умеет встроенно держать несколько state в одном root через workspaces:
tofu workspace new dev
tofu workspace new prod
tofu workspace select dev
tofu applyМинусы:
- один и тот же код для всех workspace — нельзя версионировать параметры окружения отдельно,
- легко применить prod-команду на dev-workspace (выбор глобальный),
- структура
terraform.workspaceчерез ифы плохо читается, - сложно дать разные роли/доступы на разные state.
В этом курсе мы используем Terragrunt + папки на окружение — каждое окружение это отдельная директория, отдельный state, отдельные права. Workspaces проще, но плохо масштабируются.
tofu console
> cidrsubnet("10.0.0.0/16", 8, 5)
"10.0.5.0/24"
> { for k, v in { a = 1, b = 2, c = 3 } : k => v * 10 if v > 1 }
{
"b" = 20
"c" = 30
}
> length(["a", "b", "c"])
3Внутри console доступны переменные, locals, outputs текущего root. Бесценный инструмент для отладки сложных выражений до apply.
Если две независимые конфигурации делят данные (например, root-A создаёт VPC, root-B создаёт VM в этой VPC):
# В root-B
data "terraform_remote_state" "network" {
backend = "s3"
config = {
endpoints = {
s3 = "https://storage.yandexcloud.net"
}
bucket = "mc-tfstate"
key = "network/terraform.tfstate"
region = "ru-central1"
# ...
}
}
resource "yandex_compute_instance" "vm" {
network_interface {
subnet_id = data.terraform_remote_state.network.outputs.subnet_id
}
}Минусы: tight coupling между root'ами, разделение бэкенда. Альтернатива — выносить общую часть в модуль (если хранится в одном репо) или использовать data source провайдера (data "yandex_vpc_subnet" { name = ... }) — read-only поиск по имени.
Базовый pipeline для OpenTofu:
# .github/workflows/tofu-ci.yml
jobs:
fmt-validate:
steps:
- run: tofu fmt -check -recursive opentofu/
- run: tofu validate # на каждом шаге
plan-on-pr:
if: github.event_name == 'pull_request'
steps:
- run: tofu plan -no-color > plan.txt
- uses: actions/github-script@v7
# ...положить plan.txt в комментарий к PR
apply-on-merge:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment: production # manual approval через protection rules
steps:
- run: tofu apply -auto-approveПолный пример: .github/workflows/tofu-ci.yml
Главные правила:
- Никогда не хранить креды в коде — только в Secrets/Vault и пробрасывать через env.
apply— только из защищённой ветки и с manual approval.fmt -checkиvalidate— на каждом PR (быстро, без сети).plan— на PR (даёт ревьюеру понимание что меняется).
| Симптом | Причина | Что делать |
|---|---|---|
Could not resolve provider yandex-cloud/yandex |
Нет доступа к публичному registry из России | Настроить ~/.terraformrc с network_mirror = https://terraform-mirror.yandexcloud.net/ |
one of 'token' or 'service_account_key_file' should be specified |
YC_TOKEN env var не выставлен ИЛИ провайдер настроен без token |
export YC_TOKEN=$(yc iam create-token) или вернуть token = var.yc_token в provider |
Error: Cycle: ... |
Циклическая зависимость между ресурсами | Найти, кто на кого ссылается. Часто чинится разделением: например, owner базы ↔ permission юзера — убрать permission на эту же базу |
Failed to acquire state lock |
Прерванный предыдущий apply или другой человек запустил параллельно | Если уверены, что апплая нет — tofu force-unlock <LOCK_ID> |
Provider produced inconsistent final plan |
Баг в провайдере или взаимодействие с ignore_changes |
Обновить провайдер, проверить lifecycle.ignore_changes |
Error: ... will be lost после переименования ресурса |
Не добавили moved {} блок |
Добавить moved или tofu state mv |
connection refused в cyrilgdn/postgresql после успешного создания кластера |
PG ещё не готов принимать SQL (1–3 мин после apply) | Повторить tofu apply, или добавить time_sleep с depends_on на кластере |
Terragrunt EnvVarNotFoundError |
get_env("X") без default, переменная не выставлена |
Добавить default: get_env("X", "") или export X=... |
tofu plan показывает изменения после консольной правки |
Drift между state и облаком | tofu apply -refresh-only чтобы засинхронить state, потом обычный apply |
Backend configuration changed после правки terragrunt.hcl |
Terragrunt перегенерил backend, OpenTofu не понимает | tofu init -migrate-state ИЛИ удалить .terragrunt-cache/ и init заново |
version = "0.127.0" # точная версия. Безопасно, но требует ручных обновлений
version = "~> 0.127" # >= 0.127, < 0.128. Минорные патчи автоматом
version = "~> 0.127.0" # >= 0.127.0, < 0.128.0. То же что выше, более читаемо
version = ">= 0.127" # любая выше. Опасно — провайдер может ввести breaking changesДля прода рекомендуется ~> 0.127 + .terraform.lock.hcl в git. Lock-файл фиксирует точную версию + checksum, ~> определяет границы при tofu init -upgrade.
| Директория | Что демонстрирует |
|---|---|
opentofu/01-first-resource/ |
Provider, первый resource, init/plan/apply/destroy |
opentofu/02-variables-outputs/ |
variable с validation, locals, output, tfvars |
opentofu/03-data-and-vm/ |
Data sources: yandex_compute_image, yandex_client_config |
opentofu/04-meta-and-language/ |
count, for_each, dynamic, условия, функции, lifecycle |
opentofu/04b-state-ops/ |
Тренажёр state-команд: list/show/mv/rm, import, drift, moved |
opentofu/05-module/ |
Вызов локального модуля, module.<name>.<output>, moved {} |
opentofu/06-managed-pg-two-providers/ |
Managed PostgreSQL + cyrilgdn/postgresql + random_password |
opentofu/07-multi-folder-aliases/ |
Provider aliases — один root, два каталога YC |
opentofu/08-terragrunt/ |
Terragrunt DRY, generate, include, run-all, use_lockfile |
opentofu/modules/vm-stand/ |
Переиспользуемый модуль VM-стенда |
opentofu/modules/yc-env-folders/ |
Модуль создания каталогов YC по списку имён (for_each, map output) |
.github/workflows/tofu-ci.yml |
CI: fmt + validate + plan + apply с manual approval |
Подробные инструкции по запуску каждого примера и тайминги для лектора — в demo.md.