doc: pg-terraform behavior report + ERR-PG-06/07

- doc/pg-terraform-behavior.md: подробный отчёт по поведению Nubes PostgreSQL
  с Terraform (vault_secrets, роли, тайминги, depends_on chain, SSH timeout)
  Добавлены: §2.6 vault ограничение (только 1 пользователь на инстанс), §9 IAM 408
- doc/errors/log.md: ERR-PG-01..ERR-PG-07 (все ошибки сессии тестирования)
  ERR-PG-06: vault backend позволяет только 1 vault_secrets на инстанс
  ERR-PG-07: HTTP 408 от auth-api-test.ngcloud.ru (транзитная)
- doc/progress.md: финальный статус сессии — lifecycle тесты заблокированы
  vault-ограничением тест-окружения, требуется исправление на стороне Nubes
This commit is contained in:
Repinoid
2026-04-01 19:21:37 +04:00
parent 211563a87c
commit ebbba66146
3 changed files with 700 additions and 1 deletions
+359
View File
@@ -0,0 +1,359 @@
# Отчёт: поведение 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`:
если создавать БД параллельно с пользователем — БД создастся до того как
пользователь появится, и получим ошибку.
---
## 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-го пользователя (любого) | ❌ | Vault: Секрет не создан (~3–4 мин) | Ограничение тест-окружения |
| Удаление `nubes_postgres_user` | ✅ | ~56–81 сек | — |
| Принятие существующего user (`adopt`) | ✅ | не работает при "зависшей" операции | Новое имя |
| Создание `nubes_postgres_database` | ✅ | ~47–66 сек | — |
| Удаление `nubes_postgres_database` | ✅ | ~46–92 сек | — |
| Повторный apply (idempotent) | ❌ частично | `vault_secrets` → destroy+recreate user/db | Открытая проблема |
| 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 окружения поведение может отличаться.