docs(curated): страница CRUD-стенда + ссылки на git-репозитории приложений

Новая страница docs/curated/crud/three_apps.md (раздел «Проверенные примеры» в mkdocs.yml):
- что создаётся (6 ресурсов: PG + user + db + Lucee + Flask + Node.js);
- ссылки на git: gitea.services.ngcloud.ru/terraform/{tfluceecrud,tfflaskcrud,tfnodejscrud}
  (проверено HTTP 200), в манифестах — lucee_git_path/flask_git_path/nodejs_git_path;
- запуск: два apply и почему (пароль появляется только после create_user);
- доступ к БД: vault_secrets["users"] -> { "<username>": { "password" } }, и ловушка
  state_out.users (метаданные без пароля);
- особенности: adopt_existing_on_create, уникальные домены, camelCase в postgres_conf, realm;
- диагностика: ошибку смотреть в stages операции, а не в errorLog.

Проверено локальной сборкой mkdocs: страница собирается, битых ссылок нет,
все три ссылки на репозитории присутствуют в HTML.
This commit is contained in:
Repinoid
2026-10-01 14:28:50 +03:00
parent 34abade75d
commit 00bfe7a3ad
2 changed files with 117 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
# CRUD-стенд: PostgreSQL + Lucee + Flask + Node.js
Проверенный пример: одна общая база PostgreSQL, три приложения на разных стеках и один сквозной
сценарий CRUD (создать/прочитать/обновить/удалить) в одной таблице `crud_items`.
Манифесты: `TEST_STAND/CRUD/` (test-стенд). Аналог — `DEV_STAND/CRUD/`.
Состояние на 2026-10-01, TEST-стенд: все 6 ресурсов созданы, сервисы `running`.
## Что создаётся
| Ресурс | Имя | Что это |
|---|---|---|
| `nubes_postgres` | `pg4crud2` | кластер PostgreSQL 17 |
| `nubes_postgres_user` | `user4crudpg` | пользователь БД, роль `ddl_user` |
| `nubes_postgres_database` | `db4crudpg` | база, владелец — этот пользователь |
| `nubes_lucee` | `crud-lucee` | приложение на CFML |
| `nubes_flask` | `crud-flask` | приложение на Python |
| `nubes_nodejs` | `crud-nodejs` | приложение на Node.js |
Все три приложения ходят в одну базу и одну таблицу — так видно, что CRUD работает одинаково
с любого стека.
## Код приложений (git)
| Приложение | Репозиторий |
|---|---|
| Lucee (CFML) | `https://gitea.services.ngcloud.ru/terraform/tfluceecrud` |
| Flask (Python) | `https://gitea.services.ngcloud.ru/terraform/tfflaskcrud` |
| Node.js (Express) | `https://gitea.services.ngcloud.ru/terraform/tfnodejscrud` |
Ссылки на репозитории задаются в манифестах: `lucee_git_path`, `flask_git_path`,
`nodejs_git_path` в `TEST_STAND/CRUD/locals.tf`. Платформа сама клонирует код из этих
репозиториев — вручную ничего собирать и заливать не нужно.
## Провайдер и запуск
```hcl
terraform {
required_providers {
nubes = {
source = "{{PROVIDER_SOURCE}}"
version = "{{VERSION}}"
}
}
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "{{NUBES_API_ENDPOINT}}"
}
```
```hcl
# terraform.tfvars
api_token = "***" # ЛК → Профиль → Токены → «Технический»
realm = "k8s-4-sandbox-nubes-ru" # кластер Kubernetes (из списка в ЛК)
s3_name = "naeel-s3" # имя экземпляра S3 для бэкапов
```
```bash
terraform init
terraform apply # 1-й прогон: PostgreSQL + пользователь + база
terraform apply # 2-й прогон: приложения (пароль БД уже в Vault)
```
!!! warning "Нужны два `apply` — это не ошибка"
Пароль пользователя БД платформа создаёт вместе с пользователем, а `vault_secrets`
у ресурса кластера читаются на этапе его создания — то есть **до** `create_user`.
Поэтому в первом прогоне пароля ещё нет, и приложения получают пустое значение.
Во втором прогоне пароль уже в Vault, и приложения обновляются с верным значением.
В манифестах чтение пароля обёрнуто в `try(...)`, чтобы первый прогон **не падал**
с `Invalid index ... does not identify an element in this collection value`.
Подробности: [PostgreSQL: пароль пользователя](../postgres/pg_user_db.md).
## Доступ приложений к базе
Пароль берётся из выхода `vault_secrets["users"]` — это JSON `{ "<username>": { "password": "..." } }`:
```hcl
locals {
pg_host = nubes_postgres.main_pg.state_out_flat["internalMaster"]
pg_user = nubes_postgres_user.crud_user_0.username
pg_pass = try(nonsensitive(jsondecode(nubes_postgres.main_pg.vault_secrets["users"]).user4crudpg.password), "")
}
```
⚠️ Не путать с выходом `state_out.users` — там **метаданные** (`role`, `rights`, `username`,
`mtlsAccess`) и **пароля нет**. Пароль только в `vault_secrets`.
## Особенности этого примера
- **`adopt_existing_on_create = true`** у всех ресурсов — если инстанс с таким именем уже есть
(например после `destroy`, который приостанавливает, а не удаляет), провайдер его **усыновит**,
а не упадёт с «ресурс с таким именем уже существует».
- **Домены приложений должны быть уникальными** — `lucee_domain`, `flask_domain`, `nodejs_domain`
в `locals.tf`. Иначе платформа откажет.
- **`postgres_conf`** — ключи строго как в спецификации платформы: `paramName` / `paramValue`
(camelCase). Допустимы только `log_connections` и `log_disconnections`.
- **`realm`** — рабочая ресурсная платформа. Если у неё нет ёмкости, операция падает
с сообщением «Невозможно развернуть приложение в данной ресурсной платформе» — смотрите
журнал операции, а не только текст ошибки.
## Диагностика, если `apply` упал
Текст ошибки в `errorLog` у платформы **не всегда отражает суть**. Реальная причина — в журнале
операции:
```bash
GET {api_endpoint}/instanceOperations/<UID>?fields=cfsParams,errorLog,stages
```
`stages[].stageMsg` — массив пар `[заголовок, лог]`, смотреть последнюю запись. Там же
`cfsParams` — фактический набор параметров, ушедший на платформу. Разбор реального случая:
`HISTORY/2026-10-01_test_crud_pg_create_failure.md`.
+1
View File
@@ -61,5 +61,6 @@ nav:
- Глоссарий: 30_registry/guides/glossary.md
- Проверенные примеры:
- PostgreSQL: curated/postgres/pg_user_db.md
- CRUD-стенд (PostgreSQL + Lucee + Flask + Node.js): curated/crud/three_apps.md
- Пайплайн vDC → Edge → IP → SNAT → vApp → ВМ → Штурвал: curated/pipeline/vdc_edge_ip_snat.md
- Ресурсы-модификаторы (IP организации, SNAT): curated/modifiers/org_ip_and_snat.md