Files
tf_provider/docs/curated/crud/three_apps.md
T
Repinoid ab9f7d2962 docs(curated): страница CRUD-стенда приведена к схеме pg/ + apps/
Страница curated/crud/three_apps.md описывала старую схему: всё в одной папке,
два apply и пароль из vault_secrets кластера через try(). Это уже не так.
Переписана по TEST_STAND/CRUD/README.md:
- два каталога = два state, apply/destroy в каждом меняет только своё;
- три шага: pg/ -> terraform output -json > ../apps/creds.json -> apps/;
- пароль берётся из выхода подресурса nubes_postgres_user (не из vault_secrets);
- раздел «Структура манифестов» и новый раздел «Справка: выходные параметры pg/»
  (состав выходов, вид JSON {sensitive,type,value}, соответствие env-переменным
  Flask/Node.js/Lucee);
- имена приведены к текущим: lucee-crud / flask-crud / nodejs-crud, пути в
  apps/locals.tf, добавлен keep_on_destroy;
- снято непроверенное «состояние на 2026-10-01, все 6 ресурсов running» и строка
  про DEV_STAND/CRUD как аналог — там осталась старая плоская схема, сказано прямо.
Проверено: 191 строка, 16 открывающих/закрывающих блоков кода (чётно).
2026-10-02 08:02:28 +03:00

192 lines
9.7 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, три приложения на разных стеках
(Lucee/CFML, Python, Node.js) и одна таблица `crud_items`. Запись, добавленная в любом
из трёх приложений, видна в двух других.
Манифесты: `TEST_STAND/CRUD/` (test-стенд) — два каталога: `pg/` (база) и `apps/` (приложения).
`DEV_STAND/CRUD/` — тот же пример, но по старой схеме: всё в одной папке.
## Что создаётся
| Ресурс | Имя (по умолчанию) | Что это |
|---|---|---|
| `nubes_postgres` | `pg4crud2` | кластер PostgreSQL 17 |
| `nubes_postgres_user` | `user4crudpg` | пользователь БД, роль `ddl_user` |
| `nubes_postgres_database` | `db4crudpg` | база, владелец — этот пользователь |
| `nubes_lucee` | `lucee-crud` | приложение на Lucee 5.4 |
| `nubes_flask` | `flask-crud` | приложение на Python 3.12 |
| `nubes_nodejs` | `nodejs-crud` | приложение на Node.js 22 |
Все три приложения ходят в одну базу и одну таблицу — так видно, что 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` |
Пути к репозиториям задаются в `TEST_STAND/CRUD/apps/locals.tf` (`lucee_git_path`,
`flask_git_path`, `nodejs_git_path`). Платформа сама клонирует код — собирать и заливать
вручную не нужно.
Каждое приложение получает в `json_env` переменную `SERVICE_NAME` (`lucee` / `flask` / `nodejs`) —
это значение колонки `created_by`, чтобы было видно, кто добавил строку.
## Структура манифестов
Два каталога — два отдельных файла состояния Terraform:
```
CRUD/
├── pg/ база данных: кластер PostgreSQL + пользователь + база
└── apps/ приложения: Lucee + Flask + Node.js
```
- `terraform apply` и `terraform destroy` в `apps/` меняют **только приложения** — база в `pg/` не затрагивается;
- `terraform apply` и `terraform destroy` в `pg/` меняют только базу — приложения в `apps/` не затрагиваются;
- приложения можно пересоздавать сколько угодно, база при этом не меняется;
- связь между каталогами — файл `apps/creds.json`: он делается из `terraform output` в `pg/`,
поэтому после смены хоста или пароля его нужно обновить и повторить `apply` в `apps/`.
## Провайдер
```hcl
terraform {
required_providers {
nubes = {
source = "{{PROVIDER_SOURCE}}"
version = "{{VERSION}}"
}
}
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "{{NUBES_API_ENDPOINT}}"
}
```
## Запуск — три шага
### 1. База данных (`pg/`)
```bash
cd pg
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
```
Создадутся кластер (несколько минут), пользователь БД и база.
| Переменная (`pg/terraform.tfvars`) | Где взять |
|---|---|
| `api_token` | ЛК → Профиль → Токены → «Технический» |
| `realm` | ЛК → Кластеры (например `k8s-4-sandbox-nubes-ru`) |
| `s3_name` | ЛК → S3 → имя экземпляра (для бэкапов) |
### 2. Передать креды БД в `apps/`
```bash
# всё ещё в папке pg/
terraform output -json > ../apps/creds.json
```
Одна команда выгружает хост, порт, имя пользователя, имя БД и пароль в `apps/creds.json`.
Приложения читают этот файл (`apps/locals.tf`).
!!! warning "В `creds.json` пароль лежит открытым текстом"
Файл в `apps/.gitignore` — не коммитить и не пересылать.
### 3. Приложения (`apps/`)
```bash
cd ../apps
cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply
```
Создадутся три приложения, подключённые к общей БД.
| Что заполнить/проверить | Файл | Зачем |
|---|---|---|
| `api_token` | `apps/terraform.tfvars` | тот же, что в `pg/` |
| `realm` | `apps/terraform.tfvars` | должен совпадать с БД |
| `lucee_domain`, `flask_domain`, `nodejs_domain` | `apps/locals.tf` | имена доменов — должны быть **уникальны** в облаке |
## Справка: выходные параметры `pg/`
В `pg/outputs.tf` объявлено шесть выходов:
| Выход | Значение | Откуда |
|---|---|---|
| `pg_host` | внутренний хост master | `state_out_flat["internalMaster"]` кластера |
| `pg_port` | `5432` | константа в `outputs.tf` |
| `pg_username` | имя пользователя БД | выход подресурса `nubes_postgres_user` |
| `pg_db_name` | имя базы | выход `nubes_postgres_database` |
| `pg_password` | пароль, `sensitive = true` | выход подресурса `nubes_postgres_user`: платформа генерирует пароль сама при создании пользователя, провайдер читает его из Vault и отдаёт выходом подресурса |
| `pg_ssl_mode` | `require` | константа в `outputs.tf` |
`terraform output -json` кладёт в файл не голые значения, а объекты вида
`{ "sensitive": ..., "type": ..., "value": ... }`:
```json
{
"pg_host": { "sensitive": false, "type": "string", "value": "<хост master>" },
"pg_password": { "sensitive": true, "type": "string", "value": "<пароль>" }
}
```
Поэтому в `apps/locals.tf` значение берётся через `.value`:
```hcl
creds = jsondecode(file("${path.module}/creds.json"))
pg_host = local.creds.pg_host.value
pg_pass = local.creds.pg_password.value
```
Дальше `apps/*.tf` передают эти значения приложениям как переменные окружения:
| Выход `pg/` | Flask | Node.js | Lucee |
|---|---|---|---|
| `pg_host` | `PGHOST` | `PGHOST` | `PGHOST`, `testds_connectionString` |
| `pg_port` | `PGPORT` | `PGPORT` | `PGPORT`, `testds_connectionString` |
| `pg_username` | `PGUSER` | `PGUSER` | `PGUSER`, `testds_username` |
| `pg_password` | `PGPASSWORD` | `PGPASSWORD` | `PGPASSWORD`, `testds_password` |
| `pg_db_name` | `PGDATABASE` | `PGDATABASE` | `testds_connectionString`, `DATABASE_URL` |
| `pg_ssl_mode` | `PGSSLMODE` | `PGSSLMODE` | `PGSSLMODE` |
## Особенности этого примера
- **`adopt_existing_on_create = true`** у всех ресурсов — если инстанс с таким именем уже есть
(например после `destroy`, который приостанавливает, а не удаляет), провайдер его **усыновит**,
а не упадёт с «ресурс с таким именем уже существует».
- **`keep_on_destroy = true`** у пользователя и базы: при `destroy` в `pg/` кластер уходит в `Suspend`,
а пользователь и база остаются. Иначе база исчезла бы вместе с кластером. Повторный `apply`
возвращает их в state (усыновление).
- **Домены приложений должны быть уникальными** — `lucee_domain`, `flask_domain`, `nodejs_domain`
в `apps/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`.