Files
sless/doc/pg-terraform-behavior.md
T
Repinoid 6dc2dc69ba IoT MVP: архитектура, план реализации, лог рассуждений
- Определена архитектура managed IoT service
- Согласовано решение: RabbitMQ (MVP), потом Kafka; EMQX + CRD контроллер
- Создан подробный план для Sonnet (doc/iot-mvp-plan.md)
- Добавлено правило в copilot-instructions: лог мышления в doc/thinking/ по датам
- Полный ход рассуждений в doc/thinking/2026-04-04.md

Ключевые решения:
- IoT код в iot/ (легко вынести потом)
- CRD IoTDevice + контроллер (как все остальное в sless)
- EMQX HTTP Auth Backend для динамической аутентификации устройств
- Архитектура broker-agnostic (легко переключить на Kafka)
- Terraform: расширяем текущий provider (sless_iot_device ресурс)
2026-04-04 08:34:14 +03:00

21 KiB
Raw Blame History

Отчёт: поведение 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/


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 (первая попытка) ~5275 сек
pg_test_user (повторные попытки) ~5283 сек
test_extra_user1 (чистый) ~5290+ сек
test_extra_user1 (с зависшим состоянием) ~3 мин 10 сек → Error

Создание через несколько попыток занимает в среднем ~6090 секунд.

2.3 Роль app_user — НЕ РАБОТАЕТ

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 на несколько фаз:

# Фаза 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


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:

# Через 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 ~6090 сек
Создание nubes_postgres_user с app_user Секрет не создан (~3 мин) Только ddl_user
Параллельное создание нескольких users Race condition в Vault depends_on chain
Создание 2–3-го пользователя (любого) ~6090 сек каждый depends_on chain обязателен
Удаление nubes_postgres_user ~5681 сек
Принятие существующего user (adopt) не работает при "зависшей" операции Новое имя
Создание nubes_postgres_database ⚠️ Может блокироваться ERR-PG-08 См. раздел 3.5
Удаление nubes_postgres_database ~4692 сек
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) ~010 мин
2 nubes_postgres_user pg_test_user ~6083 сек
3 nubes_postgres_database pg_test_db ~4766 сек
4 nubes_postgres_user test_extra_user1 ~6090 сек
5 nubes_postgres_user test_extra_user2 ~6090 сек
6 nubes_postgres_database test_extra_db1 ~4766 сек
7 nubes_postgres_database test_extra_db2 ~4766 сек
Итого (только новые ресурсы) ~815 минут

При повторном apply с vault_secrets drift (+destroy+recreate user/db): | Дополнительно | Destroy DB | ~4792 сек | | | Destroy User | ~5681 сек | | | Re-create всего | +~815 мин |


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 окружения поведение может отличаться.