Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Terraform & Terragrunt: IaC от первого ресурса до multi-env

Мастер-класс для администраторов, которые хотят освоить Infrastructure as Code с помощью OpenTofu и Terragrunt на примере Yandex Cloud.


Содержание


1. Что такое IaC и зачем это нужно

Infrastructure as Code — подход, при котором инфраструктура описывается в виде кода, хранится в git и применяется автоматически.

Ручное управление IaC
«Зайди в консоль, нажми кнопку» Текстовый файл, PR, CI/CD
Состояние в голове или wiki State-файл — единственный источник истины
Воспроизводимость: «кажется, так делали» tofu apply — одинаковый результат всегда
Аудит: кто что сделал? git log

OpenTofu использует декларативный подход: вы описываете что должно быть, а не как этого достичь. Инструмент сам строит граф зависимостей и определяет порядок операций.


2. OpenTofu и Terraform

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).


3. Подготовка окружения

# Установка 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)


4. Жизненный цикл: init → plan → apply → destroy

tofu init      # Скачать провайдеры, подготовить .terraform/
tofu plan      # Показать что изменится (ничего не создаёт)
tofu apply     # Применить изменения (спросит подтверждение)
tofu apply -auto-approve   # Без подтверждения (для CI)
tofu destroy   # Удалить все ресурсы в state

init — скачивает провайдеры в .terraform/ и создаёт .terraform.lock.hcl. Lock-файл фиксирует версии — коммитьте его в git.

plan — сравнивает желаемое состояние (код) с реальным (state). Показывает +/-/~ для создания/удаления/изменения.

apply — выполняет именно тот план, что показал plan. Если state изменился между plan и apply — пересчитывает.

destroy — сахар для apply -destroy.


5. State: что это и зачем

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
}

6. Ресурсы

Ресурс — базовая единица конфигурации. Описывает один объект инфраструктуры.

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/


7. Провайдеры

Провайдер — плагин, который реализует работу с конкретным 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/


8. Источники данных (data sources)

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


9. Переменные, locals, outputs

Input variables

Входные параметры модуля — его публичный интерфейс.

variable "env" {
  type        = string
  description = "Окружение: dev, test, prod."
  default     = "dev"

  validation {
    condition     = contains(["dev", "test", "prod"], var.env)
    error_message = "env должен быть dev, test или prod."
  }
}

Приоритет значений (от высшего к низшему):

  1. -var флаг CLI
  2. TF_VAR_<name> переменная окружения
  3. terraform.tfvars / *.auto.tfvars
  4. default в variable блоке

Типы: string, number, bool, list(...), map(...), object({...}), any.

Locals

Промежуточные вычисления внутри модуля — не принимаются снаружи.

locals {
  full_name     = "${var.env}-${var.network_name}"
  common_labels = { env = var.env, managed-by = "opentofu" }
}

Outputs

Значения, которые модуль «возвращает» вызывающему.

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/


10. Мета-аргументы

Мета-аргументы — специальные аргументы, работающие в любом ресурсе или модуле.

depends_on

Явная зависимость, когда неявной недостаточно:

resource "yandex_compute_instance" "app" {
  depends_on = [yandex_mdb_postgresql_cluster.this]
}

lifecycle

Управление поведением при обновлении и удалении:

lifecycle {
  ignore_changes = [boot_disk[0].initialize_params[0].image_id, metadata]
  create_before_destroy = true
  prevent_destroy       = true   # защита от случайного destroy
}

provider

Указать конкретный aliased провайдер для ресурса:

resource "yandex_vpc_network" "test" {
  provider = yandex.test
}

→ Примеры: opentofu/04-meta-and-language/main.tf


11. Циклы: count и for_each

count

Создаёт N экземпляров ресурса. Адресация: resource_type.name[index].

resource "yandex_compute_instance" "bastion" {
  count = var.create_bastion ? 1 : 0   # 0 = не создавать
  name  = "bastion-${count.index}"
}

Минус: если удалить элемент из середины списка — OpenTofu пересоздаёт все последующие.

for_each

Создаёт именованные экземпляры по карте или множеству. Адресация: 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


12. Условия и for-выражения

Conditional expression

nat_ip_address = each.value.public ? yandex_vpc_address.public[each.key].external_ipv4_address[0].address : null

for-выражение

Трансформация и фильтрация списков/карт:

# Только публичные серверы
{ 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/


13. Динамические блоки

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


14. Встроенные функции

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


15. Модули

Модуль — директория с .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/


16. Terragrunt

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 строит граф зависимостей и применяет юниты в правильном порядке.

Bootstrap (Yandex Cloud-специфика)

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 переданы корректно

use_lockfile = true (S3 backend)

OpenTofu 1.8+ поддерживает нативную блокировку state через файл-замок в S3-бакете — без DynamoDB таблицы. Это работает с Yandex Object Storage.

→ Примеры: opentofu/08-terragrunt/


17. Операции со state

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/


18. Import: описание существующей инфраструктуры

Когда инфра создана руками и нужно завести её под OpenTofu — import.

Императивный способ (tofu 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 {} (OpenTofu 1.5+)

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.tf

OpenTofu сам сгенерирует .tf с атрибутами реального ресурса — можно использовать как стартовый шаблон.


19. Рефакторинг: блоки moved и removed

moved — переименование без destroy

moved {
  from = yandex_vpc_network.old_name
  to   = module.network.yandex_vpc_network.this
}

OpenTofu увидит план «No changes. Resources moved» — записи в state переписываются, ресурсы в облаке не трогаются. После одного apply блок можно удалить.

Зачем: массовый рефакторинг (вынос в модуль, переименование, реорганизация) без простоя.

removed — удалить из state, но НЕ из облака (OpenTofu 1.7+)

removed {
  from = yandex_vpc_subnet.legacy
  lifecycle {
    destroy = false   # КЛЮЧЕВОЕ: иначе ресурс будет реально удалён
  }
}

Используется, когда ресурс должен «остаться в облаке», но управляться кем-то другим (другой root, другой команды). Замена для tofu state rm через декларацию в коде.

→ Пример: opentofu/05-module/moved.tf


20. Drift detection и tofu refresh

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 не проходит, нужно временно обойти один сломанный ресурс».


21. Workspaces — и почему мы их не используем

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 проще, но плохо масштабируются.


22. tofu console — REPL для функций и выражений

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.


23. Передача данных между root-модулями

Если две независимые конфигурации делят данные (например, 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 поиск по имени.


24. CI/CD интеграция

Базовый 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 (даёт ревьюеру понимание что меняется).

25. Troubleshooting

Симптом Причина Что делать
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 заново

Стратегия pinning провайдеров

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.


26. Карта примеров

Директория Что демонстрирует
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.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages