- 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
360 lines
18 KiB
Markdown
360 lines
18 KiB
Markdown
# Отчёт: поведение 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 окружения поведение может отличаться.
|
||
|