Решение называется alias-провайдеры. Это штатный механизм Terraform (и OpenTofu тоже, тут они идентичны), который позволяет держать несколько именованных конфигураций одного и того же провайдера и явно указывать ресурсу, через какую из них работать. Именно на этом строится мультизонное отказоустойчивое развёртывание в Yandex Cloud, и именно это мы сейчас разберём по косточкам.
Зачем вообще несколько копий провайдера
Инфраструктура как код (iac) хороша тем, что вся топология лежит в одном месте. Но реальная топология редко влезает в одну зону и один каталог. Типичные сценарии, где одного провайдера мало:
- Мультизона. ru-central1-a, ru-central1-b, ru-central1-d - три физически разнесённые зоны доступности. Чтобы кластер пережил падение одной зоны, виртуалки и диски надо разложить по разным зонам. Зона задаётся в провайдере (атрибут zone) или в самом ресурсе, и иногда удобнее иметь по провайдеру на зону.
- Несколько каталогов (folder_id). Сетевую инфраструктуру держишь в одном folder, приложения - в другом. Один terraform, два каталога.
- Несколько облаков (cloud_id) или аккаунтов. Прод и тестовый контур в разных облаках, разные сервисные аккаунты - но описаны одним кодом.

Как объявить и передать alias-провайдер
Базовый блок остаётся как был - это провайдер по умолчанию. А дополнительные конфигурации получают атрибут alias:
Код: Выделить всё
# Провайдер по умолчанию - без alias
provider "yandex" {
zone = "ru-central1-a"
folder_id = var.folder_id
}
# Копия для зоны B
provider "yandex" {
alias = "zone_b"
zone = "ru-central1-b"
folder_id = var.folder_id
}
# Копия для зоны D
provider "yandex" {
alias = "zone_d"
zone = "ru-central1-d"
folder_id = var.folder_id
}
Дальше самое важное: ресурс не знает про alias сам по себе. Ресурс всегда привязывается к провайдеру по умолчанию, если ты явно не сказал иное. Чтобы отправить ресурс в другую конфигурацию, добавляешь в него мета-аргумент provider:
Код: Выделить всё
resource "yandex_compute_instance" "web_a" {
name = "web-a"
zone = "ru-central1-a"
resources {
cores = 2
memory = 2
}
boot_disk {
initialize_params {
image_id = var.image_id
}
}
network_interface {
subnet_id = yandex_vpc_subnet.subnet_a.id
nat = true
}
}
resource "yandex_compute_instance" "web_b" {
provider = yandex.zone_b
name = "web-b"
zone = "ru-central1-b"
resources {
cores = 2
memory = 2
}
boot_disk {
initialize_params {
image_id = var.image_id
}
}
network_interface {
subnet_id = yandex_vpc_subnet.subnet_b.id
nat = true
}
}
Полноценный мультизонный пример
Соберём отказоустойчивую раскладку: своя подсеть в каждой зоне и по виртуалке в каждой. Сеть одна, а подсети привязаны к зонам.
Код: Выделить всё
resource "yandex_vpc_network" "net" {
name = "app-net"
}
resource "yandex_vpc_subnet" "subnet_a" {
name = "subnet-a"
zone = "ru-central1-a"
network_id = yandex_vpc_network.net.id
v4_cidr_blocks = ["10.10.1.0/24"]
}
resource "yandex_vpc_subnet" "subnet_b" {
provider = yandex.zone_b
name = "subnet-b"
zone = "ru-central1-b"
network_id = yandex_vpc_network.net.id
v4_cidr_blocks = ["10.10.2.0/24"]
}
resource "yandex_vpc_subnet" "subnet_d" {
provider = yandex.zone_d
name = "subnet-d"
zone = "ru-central1-d"
network_id = yandex_vpc_network.net.id
v4_cidr_blocks = ["10.10.3.0/24"]
}
На практике лучше не плодить руками ресурсы web_a, web_b, web_d, а использовать for_each по списку зон. Но for_each и meta-аргумент provider вместе не дружат напрямую: provider нельзя вычислить динамически из ключа итерации. Поэтому распространённый рабочий приём - один модуль на зону, в каждый прокидываешь нужный alias-провайдер. И тут мы подходим к главной грабле.
Грабли: alias-провайдер не наследуется в модуль неявно
Запомни как мантру: дочерний модуль не видит alias-провайдеры родителя автоматически. Провайдер по умолчанию (yandex без alias) пробрасывается в модули неявно - его можно не передавать. А вот yandex.zone_b сам в модуль не попадёт. Если ресурс внутри модуля напишет provider = yandex.zone_b, а ты этот alias не передал - Terraform либо возьмёт дефолтную конфигурацию, либо упадёт с ошибкой про неопределённый провайдер.
Передача делается явно, через блок providers в вызове модуля:
Код: Выделить всё
module "app_zone_b" {
source = "./modules/app"
providers = {
yandex = yandex.zone_b
}
subnet_id = yandex_vpc_subnet.subnet_b.id
}
Ещё пара граблей, на которых легко споткнуться:
- alias обязателен у всех дополнительных копий, но запрещён у основной. Один блок без alias - это и есть default.
- Если в модуле объявлен required_providers с configuration_aliases, провайдер придётся передавать всегда, даже дефолтный. Это для модулей, которым нужно несколько копий внутри.
- Удалять провайдер, через который созданы ресурсы, нельзя, пока живы эти ресурсы. Terraform должен иметь конфигурацию, чтобы их потом уничтожить. Сначала destroy ресурсов - потом убираешь alias-блок.
- terraform state хранит привязку ресурса к провайдеру. Если переименуешь alias, придётся либо пересоздавать, либо аккуратно работать через state, иначе план покажет лишние изменения.
Руками, на своём аккаунте Yandex Cloud:
- Объяви провайдер по умолчанию (zone ru-central1-a) и две копии с alias zone_b и zone_d на остальные зоны.
- Создай одну yandex_vpc_network и три yandex_vpc_subnet - по подсети на зону, каждая через свой провайдер.
- Прогони terraform plan apply и убедись, что подсети реально оказались в разных зонах (поле zone в выводе).
- Вынеси создание подсети в модуль ./modules/subnet и вызови его трижды, прокидывая разные alias через блок providers. Сравни план - ресурсов должно быть столько же.
- Бонус: попробуй убрать блок providers из одного вызова модуля и посмотри, что скажет Terraform.
- Чем отличается объявление основного провайдера от alias-копии и можно ли задать alias основному?
- Как заставить конкретный ресурс работать через провайдер yandex.zone_b?
- Почему alias-провайдер не попадает в дочерний модуль сам по себе и как его туда передать?
- Что произойдёт, если удалить alias-блок провайдера, через который ещё существуют ресурсы?
Один блок provider "yandex" - это конфигурация по умолчанию, и она тянется в модули неявно. Дополнительные конфигурации объявляются тем же провайдером с атрибутом alias и адресуются как yandex.<alias>. Ресурс цепляется к нужной копии через мета-аргумент provider, а в модуль alias надо отдавать руками через блок providers - это и есть та самая грабля, на которой все спотыкаются. Освоив alias, ты получаешь мультизонную отказоустойчивость в ru-central1-a/b/d, разведение по каталогам и облакам - и всё это в одном terraform yandex cloud конфиге. Модули terraform плюс alias - база любой серьёзной инфраструктуры как код.