15 KiB
Штурвал через IaC: анализ проблемы modify и скрытых платформенных зависимостей
Дата: 2026-09-23 Контекст: дискуссия в Telegram про запуск цепочки Штурвал полностью через Terraform.
1. Исходная задача
Клиенту нужен IaC (Infrastructure as Code): вся инфраструктура описывается одним конфигом, команда terraform apply разворачивает её целиком, plan/destroy дают полную картину. Никаких обязательных ручных шагов посередине.
Цепочка для стенда Штурвал:
vcOrg -> create
vcVdc -> create
vcNsxt -> create
------------------------------
vcOrg -> modify (аллоцировать внешние IP в организацию)
vcNsxt -> modify (включить SNAT, указать внешний IP из vcOrg)
------------------------------
k8sShturval -> create
Ключевой конфликт: create у Terraform работает штатно, а операции modify в текущем провайдере никак не выражаются — Terraform не умеет «создать ресурс, а через несколько шагов поменять в нём же параметр».
2. Почему modify не выражается в текущем провайдере
2.1. Генератор строит схему только из create
Провайдер генерируется из YAML-спеков (generated/{stand}/resources_yaml/*.yaml). Схема ресурса (какие поля можно писать в .tf) строится только из операции create. Параметры, которые есть только в modify, в схему не попадают.
Подтверждено по файлам:
generated/dev/resources_yaml/19_vc_org.yaml:create(id 136) → толькоresourceRealm(418),organizationType(556),orgSuffix(1125);vIPConfigure(выделение внешних IP) есть только вmodify(id 207), с sub-полямиname(39) иcount(40).
generated/dev/resources_yaml/22_vc_nsxt.yaml:create(id 10) →vdcUid,needEnableAVI(340),virtualServicesCount(341),qosProfile(825),routedNetConfiguration(1110) и др.;ipSpaceName(372) есть только вmodify(id 111).
Вывод: vIPConfigure (vc_org) и ipSpaceName (vc_nsxt) живут только в modify, в схеме tf-ресурсов их нет. Поэтому «прописать параметр в tf и сделать apply» падает ещё на plan (атрибут не известен провайдеру).
2.2. Эти параметры — не «настройки», а отложенные действия
vIPConfigure=[{name,count}]— транзакция «выделить N внешних IP» на ресурсной платформе. Повторный вызов накапливает (аккумулирует квоту), а не задаёт состояние. Это не декларативное значение.ipSpaceName— включение SNAT на конкретный ipSpace, который возникает только после того, как на орге выделены IP.- Эти операции требуют порядка (org.modify → затем nsxt.modify) и зависят от живого состояния инстанса, а не от дефолтов формы.
2.3. Схема в state ≠ реальное состояние
Вписывать недостающие параметры «насильно» в tfstate нельзя и бесполезно:
- Terraform валидирует атрибуты по схеме провайдера, а не по state — неизвестный атрибут будет отброшен/вызовет ошибку.
- Записывать в state «SNAT включён», когда этого нет на площадке, — значит получить ложный
plan(чистый) при сломанной инфраструктуре. - Ручная правка tfstate/
state pushломает целостность (серийник, конфликты на следующем apply).
Работает только косвенно: terraform_data/null_resource + local-exec → в state попадает факт «операция выполнена» (маркер с triggers), но не состояние SNAT/IP. Порядок задаётся через depends_on, но дрейф по самим параметрам plan не видит.
3. Каноничное решение: отдельный ресурс (resource association)
Это принятая в Terraform практика — «resource association / separate resource». Классические примеры:
aws_security_group+aws_security_group_ruleaws_vpc+aws_route_table_associationgoogle_project+google_project_iam_member
Базовый ресурс создаётся отдельно, а донастройка/привязка — отдельным ресурсом с depends_on. Граф сам выстраивает порядок, destroy разворачивает его корректно.
3.1. Прецедент из Cloud Director (VCD)
В репозитории лежит сторонний шаблон — /home/naeel/TF/tf_provider/!/ (network.tf.tmpl, vmware_org.tf, vdc.tf), показывающий, как та же цепочка делается провайдером VMware Cloud Director:
resource "vcd_nsxt_alb_settings"— включение ALB,count = var.alb_enable ? 1 : 0,depends_on = [vcd_nsxt_edgegateway...];resource "vcd_nsxt_alb_edgegateway_service_engine_group"— выделение SE,reserved_virtual_services = var.alb_segroup_count;resource "vcd_network_routed_v2"— routed-сеть,edge_gateway_id,dns1/dns2/static_ip_pool;resource "vcd_ip_space_custom_quota"— квота IP на оргу,depends_on = [edge].
Приём «включить/выключить» = count. Обратная операция (выключить ALB / снять квоту) получается удалением ресурса — inverse логика не нужна.
Маппинг на наши сервисы:
| Nubes API | Канон VCD |
|---|---|
needEnableAVI |
vcd_nsxt_alb_settings (+ count) |
virtualServicesCount |
reserved_virtual_services в SE-группе |
routedNetConfiguration (mainDns/secondDns/ipAddrPool) |
vcd_network_routed_v2 (dns1/dns2/static_ip_pool) |
vIPConfigure (IP на оргу) |
vcd_ip_space_custom_quota (на оргу) |
3.2. Чем наш случай сложнее канона
В классическом паттерне ребёнок — отдельный объект API со своим CRUD (правило, association, attachment). Его можно создать/прочитать/удалить.
У нас отдельного объекта нет — есть операция modify над родителем. Поэтому требуются:
Read— не свой объект, а чтение состояния родителя;Delete— не удаление, а обратный modify (inverse);Create/Update— вызов той же операции с параметрами;- идемпотентность (не дёргать
run, если live уже целевое) и live-сверку.
Именно поэтому «просто завести поля из modify в схему» не работает — нужна полноценная механика, а не одна правка.
4. Более глубокая проблема: скрытые платформенные зависимости
Это главное из всей дискуссии (реплики Виталия Зайцева, 18:30–18:34).
4.1. ipSpaceName нельзя ввести вручную — он выводится
имя ipSpace → зависит от providerGateway
providerGateway → зависит от providerVdc
providerVdc → никто не знает изначально
Пользователь не может заполнить ipSpaceName, потому что это значение выводится из внутренней топологии (providerVdc → providerGateway → ipSpace), а не из того, что он видел в ЛК. Это не «поле, которое забыли отдать через API», а вычисляемое от скрытых зависимостей значение.
4.2. Текущий костыль платформы
«При создании орги/vdc/edge, если организация ничего не знает про недостающие параметры — они подкладываются». То есть одноразовая подстановка при создании пустой орги, чтобы избавить пользователя от «мучительных приседаний» в ЛК.
4.3. Ограничение модели: один T0
«Другая проблема — что будет, если в облаке появится больше 1 T0». Пока принято допущение на уровне кода: в организации всё одно подключение. Решение осознанно отложено («пара лет спокойствия»), но для IaC это риск: текущее решение завязано на «в орге всегда один провайдер-шлюз».
4.4. Ожидание изменений спеков
«Мне надо увидеть, как спеки поменяются, чтобы понять, что исправлять… Надеюсь, появится сначала в sandbox.nubes.ru, а не в ngcloud». То есть платформа меняется, форма ресурсов зависит от новых спеков, и строить модификатор сейчас = работать по устаревшим спекам.
5. Итог: где правда
-
Ручной ЛК и скрипт не подходят — клиент требует IaC (Георгий прав). Это не «костыль против красоты», это невыполнение требования.
-
Отдельный ресурс под модификацию — необходимое, но не достаточное условие. Он закрывает «как expressить modify», но не закрывает «откуда юзер возьмёт значения».
-
Главная блокировка — не Terraform, а платформа.
ipSpaceName(и подобные) выводятся изproviderVdc → providerGateway → ipSpace, которые юзер не знает. Пока платформа не отдаёт эти значения в спеках (или провайдер не резолвит их data-источником), честный IaC не собрать — ни модификаторами, ни скриптом, ни руками. -
«Ломается агностичность» — верно, но это не порок, а цена. Ресурсы-модификаторы доменные и «ручные», как в VCD. Без них IaC невозможен, прецедент — перед глазами (
!/network.tf.tmpl). -
Состояние дел: платформа в движении (ждут новые спеки). Правильная последовательность — дождаться, что придёт в спеках (snandbox), а затем решать форму ресурса; не строить по старым спекам.
6. Возможные пути (по убыванию «честности» перед IaC)
| Вариант | Что делает | Вердикт |
|---|---|---|
| A. Полноценные ресурсы-модификаторы + data-источники | отдельный tf-ресурс на modify + data-source, резолвящий ipSpace. Полный IaC. |
правильно, но только после новых спеков |
B. Data-source через http/external + jsondecode |
DevOps сам дёргает API и подставляет динамические списки, без правки провайдера | рабочая «дожималка», не полный IaC |
C. terraform_data/null_resource + local-exec |
модификации скриптом, факт в state, порядок через depends_on |
полумера, состояние SNAT/IP вне state |
| D. Прессеты/дефолтное окружение | готовый набор компонентов, экспорт через провайдер | снижает боль на старте, IaC не заменяет |
| E. Ручной ЛК / скрипт вне tf | модификации руками | не подходит (требование клиента) |
7. Моё мнение
Коротко: для настоящего IaC нужны обе вещи одновременно — отдельный ресурс под modify и механизм получения динамических значений (ipSpace и пр.). Пока платформа не отдаёт второе через API/спеки, все «быстрые» способы (скрипт, руками, пресеты) закрывают только симптом, а не требование клиента.
Рекомендация: не городить модификатор сейчас по устаревшим спекам. Дождаться изменений спеков (сначала sandbox), параллельно — обсчитать два blockers: (1) как провайдер будет резолвить providerVdc → providerGateway → ipSpace без ручного ввода; (2) допущение «один T0». После этого проектировать форму ресурсов.
Что точно не делать: вписывать параметры «насильно» в tfstate; дёргать modify через скрипт на каждый apply без live-сверки (накопит vIPConfigure); ждать, что «прописал поле в tf → apply» заработает без правки провайдера.