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

9.7 KiB
Raw Blame History

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/.

Провайдер

terraform {
  required_providers {
    nubes = {
      source  = "{{PROVIDER_SOURCE}}"
      version = "{{VERSION}}"
    }
  }
}

provider "nubes" {
  api_token    = var.api_token
  api_endpoint = "{{NUBES_API_ENDPOINT}}"
}

Запуск — три шага

1. База данных (pg/)

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/

# всё ещё в папке pg/
terraform output -json > ../apps/creds.json

Одна команда выгружает хост, порт, имя пользователя, имя БД и пароль в apps/creds.json. Приложения читают этот файл (apps/locals.tf).

!!! warning "В creds.json пароль лежит открытым текстом" Файл в apps/.gitignore — не коммитить и не пересылать.

3. Приложения (apps/)

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": ... }:

{
  "pg_host":     { "sensitive": false, "type": "string", "value": "<хост master>" },
  "pg_password": { "sensitive": true,  "type": "string", "value": "<пароль>" }
}

Поэтому в apps/locals.tf значение берётся через .value:

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 у платформы не всегда отражает суть. Реальная причина — в журнале операции:

GET {api_endpoint}/instanceOperations/<UID>?fields=cfsParams,errorLog,stages

stages[].stageMsg — массив пар [заголовок, лог], смотреть последнюю запись. Там же cfsParams — фактический набор параметров, ушедший на платформу. Разбор реального случая: HISTORY/60_stands/2026-10-01_test_crud_pg_create_failure.md.