Новая страница 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.
6.3 KiB
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. Платформа сама клонирует код из этих
репозиториев — вручную ничего собирать и заливать не нужно.
Провайдер и запуск
terraform {
required_providers {
nubes = {
source = "{{PROVIDER_SOURCE}}"
version = "{{VERSION}}"
}
}
}
provider "nubes" {
api_token = var.api_token
api_endpoint = "{{NUBES_API_ENDPOINT}}"
}
# terraform.tfvars
api_token = "***" # ЛК → Профиль → Токены → «Технический»
realm = "k8s-4-sandbox-nubes-ru" # кластер Kubernetes (из списка в ЛК)
s3_name = "naeel-s3" # имя экземпляра S3 для бэкапов
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": "..." } }:
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 у платформы не всегда отражает суть. Реальная причина — в журнале
операции:
GET {api_endpoint}/instanceOperations/<UID>?fields=cfsParams,errorLog,stages
stages[].stageMsg — массив пар [заголовок, лог], смотреть последнюю запись. Там же
cfsParams — фактический набор параметров, ушедший на платформу. Разбор реального случая:
HISTORY/2026-10-01_test_crud_pg_create_failure.md.