Files
tf_provider/docs/curated/crud/three_apps.md
T
Repinoid 2aa2946700 docs(history): обновлены перекрёстные ссылки на файлы HISTORY после переноса
Перенос в тематические папки сломал бы все ссылки, поэтому обновлены пути:
- HISTORY/OPUS/ -> HISTORY/90_llm/OPUS/, HISTORY/SONNET/ -> HISTORY/90_llm/SONNET/;
- 15 целевых файлов из корня получили свой тематический префикс
  (HISTORY/<файл>.md -> HISTORY/<папка>/<файл>.md) — в 33 файлах репозитория.

Затронуто вне HISTORY: README.md, VERSIONS.md, HOW_TO/DEVOPS_BUILD_PIPELINE.md,
NOTES/README.md, NOTES/10_plans/, NOTES/20_prompts/, NOTES/30_analysis/,
NOTES/40_chat_summaries/, docs/curated/{crud,postgres}, docs/help/dev-reference/,
docs/ops/TESTING.md.

Проверки после правки:
- ссылок вида HISTORY/<дата> без тематической папки не осталось;
- все пути HISTORY/*.md из markdown-ссылок существуют (кроме трёх упоминаний,
  которые не были файлами и до переноса: HISTORY/90_llm/OPUS/3006_1.md,
  3006_0.md — планировавшиеся имена в старых транскриптах,
  и HISTORY/HOWTO-UPLOAD.md — ссылка на старый внешний репозиторий tf_registry);
- ссылки по «голому» имени внутри 90_llm/OPUS/ остались корректными (соседние файлы).
2026-10-02 07:36:09 +03:00

117 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/60_stands/2026-10-01_test_crud_pg_create_failure.md`.