From 00bfe7a3ad54409b98ff38fbd88232f61407a6af Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 1 Oct 2026 14:28:50 +0300 Subject: [PATCH] =?UTF-8?q?docs(curated):=20=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=86=D0=B0=20CRUD-=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=B0?= =?UTF-8?q?=20+=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8=20=D0=BD=D0=B0=20gi?= =?UTF-8?q?t-=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новая страница 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"] -> { "": { "password" } }, и ловушка state_out.users (метаданные без пароля); - особенности: adopt_existing_on_create, уникальные домены, camelCase в postgres_conf, realm; - диагностика: ошибку смотреть в stages операции, а не в errorLog. Проверено локальной сборкой mkdocs: страница собирается, битых ссылок нет, все три ссылки на репозитории присутствуют в HTML. --- docs/curated/crud/three_apps.md | 116 ++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 117 insertions(+) create mode 100644 docs/curated/crud/three_apps.md diff --git a/docs/curated/crud/three_apps.md b/docs/curated/crud/three_apps.md new file mode 100644 index 0000000..fb21b90 --- /dev/null +++ b/docs/curated/crud/three_apps.md @@ -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 `{ "": { "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/?fields=cfsParams,errorLog,stages +``` + +`stages[].stageMsg` — массив пар `[заголовок, лог]`, смотреть последнюю запись. Там же +`cfsParams` — фактический набор параметров, ушедший на платформу. Разбор реального случая: +`HISTORY/2026-10-01_test_crud_pg_create_failure.md`. diff --git a/mkdocs.yml b/mkdocs.yml index 18a6687..ab1b06a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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