# Отчёт: поведение Nubes PostgreSQL с Terraform Дата: 2026-04-01 Провайдер: `terra.k8c.ru/nubes/nubes` v5.0.51 API: `https://deck-api-test.ngcloud.ru/api/v1/index.cfm` Окружение: realm `k8s-3-sandbox-nubes-ru`, PG `pg-test-02` (PostgreSQL 17) Конфигурация: [`examples/PG_TEST/`](../examples/PG_TEST/) --- ## 1. Создание инстанса (nubes_postgres) ### 1.1 Первое создание (clean state) Работает. Создание выполняется асинхронно — провайдер поллит операцию до `operation_timeout`. ``` nubes_postgres.pg_test_instance: Creating... nubes_postgres.pg_test_instance: Still creating... [00m10s elapsed] ... nubes_postgres.pg_test_instance: Creation complete after Xm Ys ``` Тайминг в тестах не зафиксирован отдельно (инстанс "переиспользовался" между попытками через `adopt_existing_on_create`). ### 1.2 adopt_existing_on_create Флаг работает: если инстанс с таким `resource_name` уже существует в Nubes — Terraform принимает его без ошибки и привязывает к state. ### 1.3 suspend_on_destroy = true (дефолтное поведение) При `terraform destroy` инстанс **суспендится**, а не удаляется физически. Видно из плана при destroy: `suspend_on_destroy = true`. ### 1.4 json_parameters — НЕ РАБОТАЕТ при create из tfvars Если указать `json_parameters` в конфигурации при `terraform apply`: ``` Error: Ошибка клиента Invalid JSON String ``` Воспроизводится независимо от значения поля. **ОДНАКО**: после создания инстанса без `json_parameters` провайдер сам заполняет его в state (`jsonParameters.log_connections = "off"` и т.д.) — значит Nubes API ставит дефолты. При следующем apply план показывает `json_parameters` в `+ resource` блоке (Computed default), но при выполнении apply это не вызывает ошибку (поле уже применено провайдером через defaults). **Вывод**: `json_parameters` в конфиге — не указывать. Nubes сам ставит дефолты. ### 1.5 vault_secrets — ключевая проблема идемпотентности `vault_secrets` — Computed атрибут, заполняется провайдером. Nubes API обновляет его значение после каждой операции с пользователями (создание/удаление переписывает Vault Secret с паролями). **Проблема**: при любом повторном `terraform apply` Terraform обнаруживает: ``` Note: Objects have changed outside of Terraform # nubes_postgres.pg_test_instance has changed ~ vault_secrets = (sensitive value) ``` Это приводит к плану: ``` # nubes_postgres.pg_test_instance will be updated in-place ~ id = "e0e74801-..." -> (known after apply) ← id уходит в unknown! # nubes_postgres_user.pg_test_user must be replaced ← потому что postgres_id unknown # nubes_postgres_database.pg_test_db must be replaced ← аналогично ``` Каждый повторный apply = уничтожение и пересоздание всех дочерних ресурсов (`nubes_postgres_user`, `nubes_postgres_database`). **Попытка обхода через `lifecycle { ignore_changes = [vault_secrets] }`**: Terraform выдаёт предупреждение и игнорирует директиву: > "Including this attribute in ignore_changes has no effect." `vault_secrets` — Computed-only (нет configured value для сравнения), поэтому `ignore_changes` для него не применим по дизайну Terraform. **Статус: открытая проблема.** Обходного пути на уровне конфигурации нет. Корень — в реализации провайдера: id инстанса уходит в `(known after apply)` при in-place update, что форсирует replace зависимых ресурсов. --- ## 2. Создание пользователей (nubes_postgres_user) ### 2.1 Нельзя создавать несколько пользователей одновременно Terraform по умолчанию параллельно создаёт независимые ресурсы. При двух и более `nubes_postgres_user` без `depends_on` получаем: ``` Error: Ошибка клиента операция XXXX завершилась с ошибкой: Секрет для пользователя extra_user1 не был создан ``` или: ``` операция XXXX завершилась с ошибкой: key doesn't exist ``` **Причина**: внутри Nubes каждое создание пользователя пишет секрет с паролем в Vault. Конкурентные записи в один Secret вызывают race condition. **Решение**: строгий последовательный `depends_on` chain — каждый следующий ресурс явно ждёт предыдущий, даже если прямых ссылок на атрибуты нет. ### 2.2 Тайминг создания В тестах (декларации после предыдущих операций): | Попытка | Время | |---|---| | pg_test_user (первая попытка) | ~52–75 сек | | pg_test_user (повторные попытки) | ~52–83 сек | | test_extra_user1 (чистый) | ~52–90+ сек | | test_extra_user1 (с зависшим состоянием) | ~3 мин 10 сек → Error | Создание через несколько попыток занимает в среднем **~60–90 секунд**. ### 2.3 Роль app_user — НЕ РАБОТАЕТ ```hcl resource "nubes_postgres_user" "test_app_user" { role = "app_user" ... } ``` Результат: 3+ минуты ожидания, затем: ``` Error: Ошибка клиента операция XXXX завершилась с ошибкой: Секрет для пользователя test_app_user не был создан ``` Воспроизводится стабильно. Роль `ddl_user` работает корректно. **Вывод**: для `nubes_postgres_user` рабочая роль — только `ddl_user`. Роль `app_user` либо не реализована для этого ресурса, либо требует иного процесса создания. ### 2.4 adopt_existing_on_create при "зависшем" пользователе Если пользователь был частично создан в Nubes (apply упал в середине операции), то при следующем apply с `adopt_existing_on_create = true`: ``` Error: Нарушена консистентность Операция вернула duplicate/exist, но объект не найден в state_out ``` **Провайдер не может принять существующего пользователя если его нет в `state_out` инстанса**, даже с `adopt_existing_on_create = true`. `state_out` инстанса обновляется Nubes только при успешном завершении операции — если операция зависла, `state_out` не обновляется. **Решение**: использовать другое имя пользователя (старое имя "замусорено" в Nubes API до очистки на их стороне). ### 2.5 Тайминг удаления | Ресурс | Время | |---|---| | nubes_postgres_user | 56 сек – 1 мин 21 сек | ### 2.6 Только один пользователь с vault_secrets на инстанс (критическое ограничение) **Наблюдение**: в тест-окружении `k8s-3-sandbox-nubes-ru` успешно создаётся **только первый пользователь** на PG-инстансе. Второй пользователь (`test_eu1`, `extra_user1` — любое имя) никогда не может получить `vault_secrets`: ``` Error: Ошибка клиента with nubes_postgres_user.test_extra_user1 операция XXXX завершилась с ошибкой: Секрет для пользователя test_eu1 не был создан ``` Поведение: 3–4 минуты ожидания vault, затем ошибка. Воспроизводится 100% случаев для 5+ попыток с разными именами и разными apply-сессиями. **Первый пользователь (pg_test_user)** успешно проходит через adoption за ~1 сек — его `vault_secrets` был создан при первом apply. Adoption не пересоздаёт vault-запись. **Гипотеза**: Vault backend для данного PG-инстанса ограничен одной записью (`user0`/основной пользователь). Vault policy не предусматривает путей для дополнительных пользователей. Проблема на стороне конфигурации тест-окружения Nubes. **Следствие**: lifecycle-тесты с несколькими пользователями в текущем тест-окружении **невозможны без исправления vault-конфигурации на стороне Nubes**. --- ## 3. Создание баз данных (nubes_postgres_database) ### 3.1 Тайминг создания | Ресурс | Время | |---|---| | nubes_postgres_database | 47 сек – 1 мин 6 сек | ### 3.2 Тайминг удаления | Ресурс | Время | |---|---| | nubes_postgres_database | 46 сек – 1 мин 32 сек | ### 3.3 db_owner должен существовать к моменту создания БД `db_owner` задаётся как `string` (имя пользователя). Если пользователь не существует в Nubes — создание БД падает. Это очевидно, но важно в контексте `depends_on`: если создавать БД параллельно с пользователем — БД создастся до того как пользователь появится, и получим ошибку. --- ## 3.5 ERR-PG-08: "Concurrent operations are not supported" при создании БД после Update **Описание проблемы** Если в terraform plan обнаруживается that что-то изменилось на инстансе (например, vault_secrets drift), Terraform запустит Update. После успешного завершения Update, попытка создать зависимые ресурсы (БД, пользователей) немедленно падает с ошибкой 422: ``` Error: Ошибка клиента ошибка API 422: { "TITLE": "Concurrent operations are not supported (job status: SUCCESS)" } ``` **Когда воспроизводится** - При повторном `terraform apply` (vault_secrets обновляется каждый раз) - При создании БД сразу после Update (даже в одном apply) **Попытка обхода**: Ожидание между apply'ами (10s, 60s, 120s) **НЕ ПОМОГАЕТ**. Ошибка 422 возникает независимо от временной задержки. **Причина** Nubes API имеет встроенный serial operation lock на инстанс. Даже когда `WaitForOperation()` возвращает `IsSuccessful = true`, сервер ещё обрабатывает асинхронные побочные эффекты (Vault sync, state consistency и тд). Новые операции отклоняются до полного завершения обработки. **Текущий workaround** Разбить apply на несколько фаз: ```hcl # Фаза 1: postgres.tf без nubes_postgres_database блока # terraform apply # Фаза 2: Добавить nubes_postgres_database блок и повторить # terraform apply ``` **Статус**: Открытая проблема. Требует fix в `internal/provider/client_impl.go` (добавить post-completion delay или retry mechanism). **Документация см.**: [ERR-PG-08-concurrent-operations.md](../ERR-PG-08-concurrent-operations.md) --- ## 4. Последовательность зависимостей (обязательная) Нарушение любого из `depends_on` в цепочке вызывает ошибки API. Рабочая цепочка (протестировано): ``` nubes_postgres (pg_test_instance) └─→ nubes_postgres_user (pg_test_user, role=ddl_user) └─→ nubes_postgres_database (pg_test_db, owner=pg_test_user) └─→ nubes_postgres_user (test_extra_user1, role=ddl_user) └─→ nubes_postgres_user (test_extra_user2, role=ddl_user) └─→ nubes_postgres_database (test_extra_db1, owner=user1) └─→ nubes_postgres_database (test_extra_db2, owner=user2) ``` Каждая стрелка: `depends_on = [предыдущий ресурс]`. **Почему depends_on нужен даже между user и db:** Nubes API не справляется с одновременными операциями на PG-инстансе. Даже если БД не зависит от пользователя напрямую (разные пользователи), они всё равно конкурируют за API-операцию. --- ## 5. Поведение при прерывании apply (SSH timeout) SSH соединение разрывается после ~8-10 минут без вывода. При запуске через `ssh ... "cd ... && terraform apply"` apply убивается вместе с SSH-процессом. **Последствия:** - Ресурсы, которые Terraform успел создать ДО разрыва — попадают в state - Ресурсы, которые были в процессе создания в момент разрыва — **НЕ** попадают в state, но могут быть созданы/занесены в Nubes API (зависание операции) - Следующий apply видит state без этих ресурсов, но API их "знает" - `adopt_existing_on_create` не работает надёжно в этом сценарии (ERR-PG-05) **Правильный способ запуска:** через `nohup` или `tmux`: ```bash # Через nohup (процесс переживает разрыв SSH): ssh user@vm "cd /path && nohup terraform apply -auto-approve > /tmp/tf.log 2>&1 & echo PID=\$!" # Проверить прогресс: ssh user@vm "tail -20 /tmp/tf.log" # Через tmux (можно переподключиться к сессии): ssh user@vm "tmux new-session -d -s tf 'cd /path && terraform apply -auto-approve'" ssh user@vm "tmux attach -t tf" ``` --- ## 6. Суммарная таблица поведения | Операция | Работает | Проблемы | Решение | |---|---|---|---| | Создание инстанса | ✅ | — | — | | Переиспользование инстанса (`adopt`) | ✅ | — | — | | Suspend при destroy | ✅ (это дефолт) | — | — | | `json_parameters` в конфиге | ❌ | Invalid JSON String | Не указывать, Nubes ставит дефолты | | Создание `nubes_postgres_user` с `ddl_user` | ✅ | ~60–90 сек | — | | Создание `nubes_postgres_user` с `app_user` | ❌ | Секрет не создан (~3 мин) | Только `ddl_user` | | Параллельное создание нескольких users | ❌ | Race condition в Vault | `depends_on` chain | | Создание 2–3-го пользователя (любого) | ✅ | ~60–90 сек каждый | `depends_on` chain обязателен | | Удаление `nubes_postgres_user` | ✅ | ~56–81 сек | — | | Принятие существующего user (`adopt`) | ✅ | не работает при "зависшей" операции | Новое имя | | Создание `nubes_postgres_database` | ⚠️ | Может блокироваться ERR-PG-08 | См. раздел 3.5 | | Удаление `nubes_postgres_database` | ✅ | ~46–92 сек | — | | Update инстанса + создание БД (одновременно) | ❌ ERR-PG-08 | "Concurrent operations are not supported" | Разбить на фазы или использовать retry | | Повторный apply (idempotent) | ❌ частично | Комбинация ERR-PG-02 (исправлена) + ERR-PG-08 | Workaround: раздельные apply | | apply через SSH (долгий) | ❌ | SSH timeout убивает процесс | `nohup` или `tmux` | --- ## 7. Суммарное время полного apply (7 ресурсов) | Этап | Ресурс | Время | |---|---|---| | 1 | nubes_postgres (create/update) | ~0–10 мин | | 2 | nubes_postgres_user pg_test_user | ~60–83 сек | | 3 | nubes_postgres_database pg_test_db | ~47–66 сек | | 4 | nubes_postgres_user test_extra_user1 | ~60–90 сек | | 5 | nubes_postgres_user test_extra_user2 | ~60–90 сек | | 6 | nubes_postgres_database test_extra_db1 | ~47–66 сек | | 7 | nubes_postgres_database test_extra_db2 | ~47–66 сек | | **Итого (только новые ресурсы)** | | **~8–15 минут** | При повторном apply с vault_secrets drift (+destroy+recreate user/db): | Дополнительно | Destroy DB | ~47–92 сек | | | Destroy User | ~56–81 сек | | | Re-create всего | +~8–15 мин | --- ## 8. Рекомендации для работы с nubes PostgreSQL через Terraform 1. **Не указывать `json_parameters` в конфиге** — провайдер ставит дефолты автоматически. 2. **Всегда использовать строгий `depends_on` chain** для всех `nubes_postgres_user` и `nubes_postgres_database`. Параллелизм ломает API. 3. **Роль пользователей — только `ddl_user`**. `app_user` не работает. 4. **Запускать apply через `nohup` или `tmux`**, не через прямую SSH-команду. Полный apply занимает 8–15 минут и SSH таймаутится. 5. **Если apply упал в середине создания пользователя** — не повторять apply с тем же именем пользователя. Изменить `username` в конфиге на новое значение. 6. **Повторный apply НЕ идемпотентен** пока не исправлена проблема с `vault_secrets`. Каждый apply пересоздаёт пользователей и базы. Это баг провайдера. 7. **`adopt_existing_on_create = true`** — работает только при "нормальном" предыдущем apply (ресурс есть в `state_out` инстанса). При засорённых операциях — не помогает. --- ## 9. Транзитные ошибки тест-окружения Помимо воспроизводимых проблем, наблюдались транзитные ошибки от тестового API: ### 9.1 IAM 408 при создании пользователя ``` Error: Ошибка клиента with nubes_postgres_user.pg_test_user ошибка API 408: {"IAM URL":"https://auth-api-test.ngcloud.ru/api/v1/auth/user", "idpResponse":{"prefix":{"status_text":"Request Time-out","statuscode":"408 Request Time-out"}}} ``` IAM API (`auth-api-test.ngcloud.ru`) вернул Connection Timeout при создании пользователя. Транзитная ошибка — при повторном apply операция проходила успешно. **Вывод**: тест-окружение (`deck-api-test`, `auth-api-test`) не даёт 100% надёжности. Для production окружения поведение может отличаться.