# Как работает провайдер Nubes: поведение и отличия от канонического Terraform > Страница для тех, кто уже работал с Terraform и хочет понять, чего ожидать от провайдера Nubes, > и для DevOps, которым важно знать, что реально произойдёт в облаке при `plan`, `apply` и `destroy`. > Здесь описан наблюдаемый контракт (что уже проверено на живых стендах) и ограничения платформы. ## 1. Суть в одном абзаце Провайдер Nubes — это не плагин к гипервизору, а **обёртка над API личного кабинета**: каждый Terraform-ресурс соответствует *инстансу услуги* в облаке, а `create` / `update` / `delete` транслируются в **асинхронные операции** платформы (`create`, `modify`, `suspend`, `resume`, `delete`). Отсюда и все отличия от привычного Terraform: - операции **долгие** — провайдер ждёт их завершения (поллинг); - меняется **уже созданный объект**, а не создаётся заново; - ошибки приходят **внутри тела ответа**, а не HTTP-кодом; - ресурс ищется **по имени**, а не только по `id`; - удаление может быть **«мягким»** — объект остаётся в облаке. ## 2. Какие ресурсы за что отвечают | Услуга облака | Ресурс Terraform | Что делает | |---|---|---| | Организация в Cloud Director (19) | `nubes_vc_org_ip_allocation` | **модификатор**: меняет квоту внешних IP в организации (саму организацию создают в ЛК, Terraform её не трогает) | | Виртуальный датацентр (21) | `nubes_vc_vdc` | создаёт vDC с ресурсами (CPU/RAM/Storage) | | Сетевой шлюз периметра (22) | `nubes_vc_nsxt` | создаёт Edge (routed-сеть, ALB/AVI) | | Сетевой шлюз периметра (22) | `nubes_vc_nsxt_snat` | **модификатор**: включает/переключает SNAT (`ipSpaceName`) на существующем Edge | | Kubernetes кластер Штурвал (150) | `nubes_k8s_sthutrval_cluster` | создаёт кластер (control plane + группы воркеров) | Общее правило. Ресурс, у которого есть свой «объект в облаке», **создаёт** этот объект. А то, что платформа меняет операцией `modify` (квота IP, включение SNAT, настройки шлюза), собрано в отдельные ресурсы- **модификаторы**: они не создают ничего нового, а правят уже существующий объект. ## 3. Отличия от «учебного» Terraform | Ожидание по канону | Как в Nubes | Причина | |---|---|---| | `create` = один вызов API | Создание инстанса — **последовательность**: `POST /instances` → `POST /instanceOperations` → параметры (`instanceOperationCfsParams`) → `run` → поллинг до `dtFinish` | Так устроен API платформы | | `id` — произвольная строка | `id` = UUID инстанса в облаке; провайдер читает его из ответа платформы | Состояние живёт в облаке, не в Terraform | | `update` = замена при несовместимых параметрах | `update` = операция **`modify`** над тем же инстансом; часть параметров — **create-only** (менять нельзя → ошибка на этапе plan) | Платформа не пересоздаёт объекты «из коробки» | | есть `data sources` для поиска существующего | Поиск/ссылки — через **ref-параметры**: можно указать UUID **или имя** инстанса | Экономит data sources, но требует дисциплины в значениях | | `import` — явная команда | Плюс к `import` есть **авто-усыновление** `adopt_existing_on_create = true`: ресурс сам находит инстанс **по имени** | В проде объекты живут неделями, и пересоздавать их нельзя | | `plan` показывает, что ресурс уже существует | **Нет**: проверка «инстанс с таким именем уже есть / adopt» выполняется в `Create`, то есть на `apply` | Иначе ломается `destroy` и работа с tainted-ресурсами | | `delete` удаляет | `delete` может быть `delete` / `suspend` / `state_only` — см. §5 | У платформы не всё удаляется, а часть объектов удалять нельзя, пока жив потребитель | | ошибка приходит HTTP-кодом | Ошибка операции — **в теле**: `isSuccessful=false` + `errorLog`, при успешном HTTP-коде | API платформы отвечает 200/201 почти всегда | | операции быстрые | Операции асинхронные и долгие (Штурвал — десятки минут) → параметр `operation_timeout`; одновременные операции на одном инстансе не поддерживаются | Наследство платформы (внутри — CFS-оркестратор) | | план обязан совпадать с config | Тоже, но: **ref-параметры в плане остаются как написаны** (`name` не подменяется на `uuid`), а уже перед вызовом API провайдер сам разберётся, имя это или UUID | Иначе Terraform упадёт с «Provider produced invalid plan» | | параметры сравниваются как строки | JSON-параметры сравниваются **канонично**: порядок ключей и пробелы не важны, скаляры приводятся к строке, UUID — без учёта регистра | API возвращает JSON в своём виде, иначе будет «вечный diff» | | идентичность — по `id` | Дополнительно: инстанс ищется по `resource_name` (displayName) → **переименование = новый ресурс** | Имя задаётся при создании и не меняется | ### Что из этого следует на практике - `terraform plan` **не может** проверить, что «такой объект уже есть»: он это покажет как `will be created`, а разбираться будет `apply`. - `terraform apply` на существующей инфраструктуре — это нормальный сценарий (adopt), если включён `adopt_existing_on_create`; без флага вы получите явную ошибку-конфликт, а не дубль ресурса. - Долгие операции требуют терпения: смотрите `operation_timeout`, не запускайте вторую операцию по тому же объекту. ## 4. Создание, чтение, изменение, удаление **Создание.** Провайдер формирует параметры операции, отправляет её и ждёт завершения. Параметры, зависящие от других ресурсов (например `vdc_uid`, `nsxt_uid`), передаются как ref-значения: UUID или имя. **Чтение (refresh).** Значения `state_params` / `state_out` приходят из облака: так в state попадают адреса (`kubernetesApiAddress`, `ingressAddress`), имена, текущие параметры. **Изменение.** Если параметры поменялись, вызывается `modify` (для модификаторов — обратный `modify` при удалении). Параметры, помеченные как create-only, изменить нельзя — провайдер скажет об этом до обращения к API. **Удаление.** См. следующий раздел — это самое неочевидное место. ## 5. Удаление: `delete`, `suspend` и «оставить как есть» У платформы три разных исхода, и провайдер умеет все три. Управляется флагами: | Флаг | Где применим | Поведение при `destroy` | Предупреждение в выводе | |---|---|---|---| | `suspend_on_destroy = true` | кластер Штурвала, vDC (по умолчанию `true`) | объект **приостанавливается**, не удаляется | «Ресурс заморожен, а не удалён» | | `keep_on_destroy = true` | Edge, SNAT, квота IP (по умолчанию `false`) | объект **не трогается в облаке**, только убирается из state | «Оставлен как есть» (для SNAT — «SNAT не выключался», для квоты IP — «Аллокация IP не снималась») | | оба `false` | любой | обычное удаление | — | Если выставлены оба флага, победит `keep_on_destroy` (объект просто не тронут). По умолчанию провайдер удаляет по-настоящему (`keep_on_destroy = false`), а «заморозку» или «оставить как есть» включают явно в конфигурации стенда. ### Почему Edge остаётся `running` Edge **физически не умеет `suspend`**: в списке доступных операций шлюза есть только `delete`, `modify`, `reconcile`. Усыпить его платформа не даёт, поэтому единственный корректный способ «не удалять шлюз» — не трогать его: `keep_on_destroy = true`. Побочный эффект — шлюз продолжает работать и тарифицироваться, и через него продолжают публиковаться внешние адреса (API кластера и ingress). Именно поэтому удаление Edge при живом кластере Штурвала (или других зависимых объектах: vApp, VM) считается недопустимым. ### Почему SNAT остаётся включённым, а квота IP не обнуляется - SNAT-модификатор с `keep_on_destroy = true` **не отправляет** обратный `modify` с `ipSpaceName = "no-needed"`, то есть SNAT для виртуальных машин остаётся в прежнем состоянии. - Квота внешних IP: уменьшать `count` **ниже фактически занятых адресов платформа не разрешает** (ошибка вида «Кол-во занятых Ip … Невозможно выставить параметр count ниже этого параметра»). Адреса держит кластер Штурвала (API + ingress), и `suspend` кластера их **не освобождает**. Поэтому при «заморозке» квота не изменяется вовсе, а реальное освобождение адресов возможно только после удаления кластера — отдельным шагом. ### Почему кластер и vDC уходят в `suspend` Для них `suspend` поддерживается платформой, и это самый близкий к «выключить» вариант: ВМ останавливаются, объекты не удаляются, адреса и конфигурация сохраняются. Обратный ход делает `apply`: | Что | При `destroy` (заморозка) | При следующем `apply` | |---|---|---| | Кластер Штурвала | `suspend` | adopt по имени + `resume` | | vDC | `suspend` | adopt по имени + `resume` | | Edge | не трогается (`running`) | adopt (инстанс уже работает) | | SNAT | не трогается (включён) | повторный `modify` теми же значениями (фактически no-op) | | Квота IP | не трогается | `modify` с тем же `count` (no-op) | Итого цикл «`destroy` → `apply`» на стенде выглядит как «усыпить → разбудить», а не «снести → поднять заново». Полностью удалить такой стенд можно только явным отказом от заморозки (`keep_on_destroy = false` / `suspend_on_destroy = false`) и в правильном порядке (см. §6). ## 6. Конкретный пайплайн стенда: vDC → Edge → внешние IP → SNAT → Штурвал Порядок создания и зависимости: 1. Организация — **вручную в ЛК** (Terraform её не создаёт). 2. `nubes_vc_vdc` — виртуальный датацентр. 3. `nubes_vc_nsxt` — Edge. Обязательно включать балансировщик (параметр `need_enable_avi = true`) и задать не меньше 3 виртуальных сервисов (`virtual_services_count >= 3`) — это нужно кластеру Штурвала. 4. `nubes_vc_org_ip_allocation` — внешние адреса в организации (минимум 3 по инструкции услуги Штурвала). 5. `nubes_vc_nsxt_snat` — SNAT на Edge (по `depends_on` после аллокации адресов). 6. `nubes_k8s_sthutrval_cluster` — кластер Штурвала (по `depends_on` после SNAT: нодам нужен выход в интернет). При удалении Terraform идёт в обратном порядке. Ограничения платформы, которые встречаются на этом пути: - **1 кластер Штурвала = 1 vDC** (действующее ограничение услуги). - vDC удаляется только **через 14 дней после `suspend`**; при живых Edge/vApp/VM/кластере — через поддержку. - Edge не удаляется при живых зависимых объектах (по инструкции услуги). - Квота IP не опускается ниже занятых адресов (см. §5). Как проверить, что конфигурация корректна: `terraform plan` после `apply` должен говорить `No changes`. Если появляется стабильный diff — сверяйте его с `terraform state show` и с тем, что реально показывается в личном кабинете. ## 7. Регистр UUID и канонизация значений UUID в облаке не имеет «правильного» регистра: один и тот же идентификатор может прийти как `2c37fed1-…` и как `2C37FED1-…`. Поэтому провайдер сравнивает UUID **без учёта регистра** — в том числе внутри JSON-параметров (например, `startupConfiguration` кластера Штурвала). Подробный разбор проблемы и всех мест, где она может проявиться, — в `60_strategy/terraform_case_sensitivity_fix.md` (внутренний документ). Для JSON-параметров сравнение также игнорирует порядок ключей и пробелы, а скаляры приводит к строковому виду — это нужно, чтобы `plan` не показывал «изменение» там, где API вернул то же значение в другом формате. ## 8. Чек-лист DevOps - Пин версии провайдера привязан к стенду: **prod `1.*`, dev `2.*`, test `3.*`**; после смены версии — `terraform init -upgrade`. - Перед `apply` и после — смотрите `plan`; ожидаемый финальный результат — `No changes`. - **Читайте предупреждения `destroy`**: «заморожен», «оставлен как есть», «SNAT не выключался» — это не косметика, а отчёт о том, что объекты продолжают жить и тарифицироваться. - Не удаляйте объекты стенда руками в ЛК между `destroy` и `apply`: авто-усыновление ищет их по имени, и на удалённом объекте упрётся в состояние `not created`. - Долгие операции: задавайте `operation_timeout` (для Штурвала — `60m`), не запускайте параллельные операции по одному объекту. - Проверяя состояние через API ЛК, помните про обязательные заголовки (браузерный `User-Agent` и `Referer`) — иначе отдаёт `403`. ## 9. FAQ **Почему `plan` пишет `will be created`, если объект уже есть?** Потому что проверка существования и усыновление выполняются на `apply` (в `Create`). В state ресурса нет → план честно планирует создание. Решение — `adopt_existing_on_create = true`. **Почему `destroy` сказал `5 destroyed`, а в облаке всё живо?** Сработала «заморозка»: кластер и vDC приостановлены, Edge, SNAT и квота IP не изменялись. Terraform «удалил» ресурсы только из своего состояния. Смотрите предупреждения в выводе. **Почему IP не освободились?** Квота не может стать меньше числа занятых адресов, а их держит кластер. Пока кластер существует (даже в `suspend`), платформа не даст уменьшить `count`. **Почему Edge нельзя приостановить?** У платформы для шлюза нет операции `suspend` — только `delete`, `modify`, `reconcile`. **Почему после `apply` кластер ожил сам?** Сработало усыновление: ресурс нашёл инстанс по имени (`adopt_existing_on_create`), увидел статус `suspended` и выполнил `resume`. **State пуст, а объекты в облаке есть. Что делать?** Это ожидаемый результат «заморозки». `terraform apply` в том же каталоге усыновит объекты обратно. **Кто-то удалил объект в ЛК. Что будет?** Усыновление не найдёт его и провайдер завершится ошибкой с указанием статуса (`not created`) — нужно либо создать объект заново, либо разбираться с конфигурацией. **А можно всё-таки удалить стенд полностью?** Да, но осознанно: выставить `keep_on_destroy = false` и `suspend_on_destroy = false`, затем удалять в порядке кластер → квота IP → SNAT → Edge → vDC, при необходимости — через поддержку (для vDC действует правило 14 дней). ## 10. Куда смотреть дальше - [Как развернуть vDC, Edge, внешние IP, SNAT и кластер Штурвал](https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/curated/pipeline/vdc_edge_ip_snat/) — пошаговая инструкция по всей цепочке вашего стенда. - `curated/modifiers/org_ip_and_snat.md` — ресурсы-модификаторы. - `30_registry/guides/terraform-structure.md` — структура манифестов. - Внутренние документы (не публикуются): `60_strategy/provider_philosophy.md`, `60_strategy/modifier_resources_ideology_and_specification.md`, `60_strategy/adopt_ref_validation.md`, `60_strategy/terraform_case_sensitivity_fix.md`.