- Определена архитектура 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 ресурс)
21 KiB
Отчёт: поведение 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 (первая попытка) | ~52–75 сек |
| pg_test_user (повторные попытки) | ~52–83 сек |
| test_extra_user1 (чистый) | ~52–90+ сек |
| test_extra_user1 (с зависшим состоянием) | ~3 мин 10 сек → Error |
Создание через несколько попыток занимает в среднем ~60–90 секунд.
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 |
✅ | ~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
-
Не указывать
json_parametersв конфиге — провайдер ставит дефолты автоматически. -
Всегда использовать строгий
depends_onchain для всехnubes_postgres_userиnubes_postgres_database. Параллелизм ломает API. -
Роль пользователей — только
ddl_user.app_userне работает. -
Запускать apply через
nohupилиtmux, не через прямую SSH-команду. Полный apply занимает 8–15 минут и SSH таймаутится. -
Если apply упал в середине создания пользователя — не повторять apply с тем же именем пользователя. Изменить
usernameв конфиге на новое значение. -
Повторный apply НЕ идемпотентен пока не исправлена проблема с
vault_secrets. Каждый apply пересоздаёт пользователей и базы. Это баг провайдера. -
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 окружения поведение может отличаться.