Эта боль решается двумя инструментами: источниками данных (data sources) и чтением чужого состояния через terraform_remote_state. Оба про одно и то же - перестать хардкодить и начать ссылаться на реальные ресурсы. В этом уроке разберём, как инфраструктура как код перестаёт быть монолитом и распадается на слабо связанные компоненты, которые умеют читать данные друг у друга. Тема одинаково актуальна и для terraform, и для opentofu - синтаксис идентичен.
Data source: читаем, а не создаём
Ресурс (resource) - это то, что terraform создаёт и чем владеет. Источник данных (data source) - это запрос на чтение того, что уже существует. Ты ничего не создаёшь, ничего не удаляешь, просто на этапе terraform plan провайдер идёт в API Yandex Cloud, спрашивает "а дай-ка мне вот это" и возвращает атрибуты. Дальше ты используешь их как обычные значения.
Самый частый и самый полезный пример - найти ID последнего образа Ubuntu. Образы в YC лежат в публичной папке standard-images и регулярно обновляются: вышел свежий патч ядра - появился новый image_id. Хардкодить конкретный ID - значит обречь себя на устаревшее ядро навсегда. Вместо этого мы спрашиваем образ по семейству (family), и провайдер сам отдаёт самый свежий в этом семействе.
Код: Выделить всё
data "yandex_compute_image" "ubuntu" {
family = "ubuntu-2204-lts"
}
output "image_id" {
value = data.yandex_compute_image.ubuntu.id
}
Теперь подключим подсеть, которую создал не ты. Источник yandex_vpc_subnet ищет подсеть по subnet_id или по name:
Код: Выделить всё
data "yandex_vpc_subnet" "app" {
name = "app-subnet-ru-central1-a"
}
data "yandex_compute_image" "ubuntu" {
family = "ubuntu-2204-lts"
}
resource "yandex_compute_instance" "app" {
name = "app-01"
platform_id = "standard-v3"
zone = data.yandex_vpc_subnet.app.zone
resources {
cores = 2
memory = 2
}
boot_disk {
initialize_params {
image_id = data.yandex_compute_image.ubuntu.id
size = data.yandex_compute_image.ubuntu.min_disk_size
}
}
network_interface {
subnet_id = data.yandex_vpc_subnet.app.id
nat = true
}
}
Есть ещё один служебный, но крайне удобный источник - yandex_client_config. Он отдаёт то, чем сконфигурирован сам провайдер: cloud_id, folder_id, zone и короткоживущий iam_token. Вызывается вообще без аргументов:
Код: Выделить всё
data "yandex_client_config" "client" {}
output "current_folder" {
value = data.yandex_client_config.client.folder_id
}

terraform_remote_state: один пишет, другой читает
Data source хорош, когда ресурс существует "где-то в облаке" и ты просто ищешь его по имени. Но часто бывает иначе: сеть тоже описана в terraform, просто в другом проекте, со своим terraform state. Команда сети владеет VPC, ты владеешь приложением. Как тебе достать ID подсети из их состояния, не лазая в консоль?
Тут выходит на сцену terraform_remote_state - источник данных, который читает не API облака, а чужой файл состояния напрямую. Механика простая и состоит из двух половинок.
Половинка первая - тот, кто публикует. Проект сети объявляет нужные значения через output. Всё, что попало в output, записывается в terraform state и становится доступным наружу:
Код: Выделить всё
# проект network/outputs.tf
output "subnet_app_id" {
value = yandex_vpc_subnet.app.id
}
output "network_id" {
value = yandex_vpc_network.main.id
}
Код: Выделить всё
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "tf-states-prod"
key = "network/terraform.tfstate"
endpoints = { s3 = "https://storage.yandexcloud.net" }
region = "ru-central1"
skip_region_validation = true
skip_credentials_validation = true
}
}
resource "yandex_compute_instance" "app" {
name = "app-01"
# ...
network_interface {
subnet_id = data.terraform_remote_state.network.outputs.subnet_app_id
nat = true
}
}
Так рождается слабая связанность (loose coupling). Проект сети ничего не знает о приложении. Проект приложения знает о сети ровно одно - имя бакета и набор её outputs. Меняешь внутренности сети как угодно, лишь бы контракт outputs оставался прежним - и приложение даже не заметит. Это та же идея, что интерфейсы в коде, только для инфраструктуры.
Что выбрать: remote_state или прямой data source
Часто одну и ту же задачу можно решить обоими способами, и новички путаются. Возьмём наш subnet_id.
Через terraform_remote_state ты читаешь его из чужого состояния по контракту outputs. Через прямой data source yandex_vpc_subnet ты читаешь подсеть из API облака по имени - вообще не зная и не интересуясь, кто и каким terraform её создал.
Когда что брать? Если у компонентов общий владелец и общий процесс - связка через remote_state чище: явный контракт, видно зависимости, никакой магии с именами. Если же ресурс создан кем угодно (другой командой, кликами в консоли, вообще не terraform) - прямой data source надёжнее, он не требует доступа к чужому state и не ломается, если соседи переименовали output. Грубое правило: remote_state - для своих модулей terraform внутри одной экосистемы, data source - для всего внешнего. Кстати, модули terraform тут ни при чём - это про разбиение кода внутри одного state, а мы говорим про связь между разными state.
Типичные грабли
- Думают, что data source создаёт ресурс. Нет. Удалишь блок data - в облаке ничего не пропадёт. Это чтение, а не владение.
- Виртуалки пересоздаются на ровном месте. Образ Ubuntu обновился, image_id изменился, и terraform хочет пересоздать VM, чтобы поменять диск. Если это не нужно - добавь в ресурс . Тогда уже поднятые машины останутся на старом образе, а новые будут стартовать со свежего.
Код: Выделить всё
lifecycle { ignore_changes = [boot_disk[0].initialize_params[0].image_id] } - Пытаются прочитать значение, которое не объявлено как output. terraform_remote_state видит только outputs. Нет output - нет значения, хоть оно сто раз в чужом state лежит.
- Циклическая зависимость. Проект A читает remote_state B, а B читает remote_state A. terraform на это справедливо ругается. Разрывай цикл, направляй зависимости в одну сторону.
- Хардкод вместо data source. Скопировать ID из консоли быстрее на 10 секунд сегодня и дороже на час отладки через месяц. Не делай так.
Повтори руками, без этого в голове не уляжется:
- Напиши data "yandex_compute_image" по family "ubuntu-2204-lts" и выведи через output его id и min_disk_size. Запусти terraform plan, посмотри, какой реальный image_id подставился.
- Добавь data "yandex_vpc_subnet" по имени существующей подсети и подними yandex_compute_instance, взяв subnet_id и zone из этого источника. Проверь, что ни одного ID руками ты не вписал.
- Сделай второй проект: в нём через output опубликуй любое значение, примени, а потом из первого проекта прочитай его через terraform_remote_state с бэкендом s3 на Object Storage.
- Удали один из data-блоков и запусти plan. Убедись, что в облаке ничего не удаляется - меняется только то, что на этот источник ссылалось.
- Чем data source отличается от resource и что произойдёт с облаком, если удалить блок data?
- Почему поиск образа по family предпочтительнее, чем захардкоженный image_id, и какой атрибут отдаёт самый свежий образ семейства?
- Какие именно значения видит terraform_remote_state в чужом состоянии и что нужно сделать в проекте-источнике, чтобы значение стало доступным?
- В каком случае ты выберешь прямой data source yandex_vpc_subnet, а не чтение через terraform_remote_state?
Запомни три вещи. Первое: data source - это чтение существующего, а не создание; через него мы достаём из API Yandex Cloud актуальный image_id последнего Ubuntu, нужную подсеть и параметры клиента, не зашивая идентификаторы в код. Второе: terraform_remote_state читает чужой terraform state и видит только то, что сосед явно отдал через output - это контракт между компонентами. Третье: всё это про слабую связанность - инфраструктура как код перестаёт быть одним комом и превращается в набор компонентов, которые аккуратно ссылаются друг на друга, а не копируют ID из консоли. Перестань хардкодить - и половина ночных инцидентов отвалится сама.