Files
tf_provider/TEST_STAND/CRUD/README.md
T
Repinoid 17350eed89 docs(crud): README и страница сведены в один документ + исправлен бред по коду
Разбор замечаний ревью (проверено по манифестам):
1) рассинхрон: теперь одинаковый набор и порядок разделов в обоих файлах
   (Быстрый старт, Что создаётся, Как это устроено, Что пользователь задаёт сам,
   Код приложений (git), Провайдер, Повседневные операции, Файлы, Особенности
   этого примера, Справка). На странице переименован раздел, перенесены Код
   приложений и Особенности, добавлены Повседневные операции и Файлы; в README
   добавлены Что создаётся, Провайдер и Особенности;
2) ошибка в таблице env: pg_db_name -> Lucee показывал testds_connectionString и
   DATABASE_URL как отдельные значения. По apps/lucee.tf это составные строки
   подключения, а PGDATABASE у Lucee нет; таблица исправлена + пояснение про
   testds_* (JDBC-параметры);
3) pg_host: пояснено, что это внутренний хост для приложений, а сами приложения
   открываются по внешним доменам;
4) пути репозиториев в таблицах приведены к виду из apps/locals.tf — с .git;
5) «уникально в пределах стенда» -> «внутри своего сервиса»;
6) postgres_conf и прочие особенности теперь и в README.
Проверено: пофайловое сравнение разделов (различия только в --- и
{{плейсхолдерах}}), живая страница 166060 байт, cmp с локальной сборкой совпал.
2026-10-02 08:58:38 +03:00

15 KiB
Raw Blame History

CRUD Test Stand

CRUD = Create / Read / Update / Delete — создать, прочитать, изменить, удалить.

Три приложения (Lucee, Flask, Node.js) работают с одной общей таблицей crud_items в PostgreSQL: в любом из них можно добавлять записи, просматривать их, редактировать и удалять.

Итог после запуска: добавьте запись в одном приложении — остальные два её увидят.


Быстрый старт

# 1. получить манифесты (нужен установленный Terraform — проверьте `terraform version`)
git clone https://gitea.services.ngcloud.ru/terraform/tf_provider.git
cd tf_provider/TEST_STAND/CRUD

# 2. заполнить переменные (что именно — в разделе «Что пользователь задаёт сам»)
cd pg     && cp terraform.tfvars.example terraform.tfvars  # api_token, realm, s3_name
cd ../apps && cp terraform.tfvars.example terraform.tfvars  # api_token, realm — те же

# 3. база данных (кластер создаётся несколько минут)
cd ../pg && terraform init && terraform apply

# 4. передать креды БД приложениям
terraform output -json > ../apps/creds.json

# 5. три приложения
cd ../apps && terraform init && terraform apply

После этого приложения открываются в браузере (имена заданы в apps/locals.tf):

Приложение Адрес
Lucee https://lucee-crud.luceek8s.dev.nubes.ru
Flask https://flask-crud.pythonk8s.dev.nubes.ru
Node.js https://nodejs-crud.nodejsk8s.dev.nubes.ru

Добавьте запись в одном приложении — она появится в двух других (таблица crud_items общая).

⚠️ В apps/creds.json пароль лежит открытым текстом: файл в apps/.gitignore — не коммитить и не пересылать.

Провайдер берётся из реестра: источник и версия заданы в pg/main.tf и apps/main.tf.

Подробности дальше: что задавать руками и как всё устроено.


Что создаётся

Ресурс Имя (по умолчанию) Что это
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 работает одинаково с любого стека.


Как это устроено

Два каталога — два отдельных файла состояния Terraform:

CRUD/
├── pg/     база данных: кластер PostgreSQL + пользователь + база
└── apps/   приложения: Lucee + Flask + Node.js
  • terraform apply и terraform destroy в apps/ меняют только приложения (создают, изменяют, удаляют) — база в pg/ не затрагивается;
  • terraform apply и terraform destroy в pg/ меняют только базу — приложения в apps/ не затрагиваются;
  • приложения можно пересоздавать сколько угодно, база при этом не меняется;
  • связь между каталогами — файл apps/creds.json (шаг 4 быстрого старта): он делается из terraform output в pg/, поэтому после смены хоста или пароля его нужно обновить и повторить apply в apps/.

Что пользователь задаёт сам

Обязательные значения (без них apply не пройдёт)

Переменная Где Что это
api_token pg/terraform.tfvars и apps/terraform.tfvars токен Nubes: ЛК → Профиль → Токены → создать «Технический»
realm там же, одно и то же значение ресурсная платформа (кластер Kubernetes), например k8s-4-sandbox-nubes-ru; в apps/ обязан совпасть с pg/, иначе приложения не увидят кластер с базой
s3_name pg/terraform.tfvars имя (или UUID) экземпляра S3 для бэкапов: ЛК → S3

Файлы создаются из примеров и нужны в обеих папках:

cd pg && cp terraform.tfvars.example terraform.tfvars
cd ../apps && cp terraform.tfvars.example terraform.tfvars

Имена: придумать самому, и они обязаны быть уникальными

Имя Где задаётся Правило
pg_resource_name (кластер) pg/terraform.tfvars уникально внутри сервиса postgres — другого инстанса с таким именем быть не должно
pg_username, pg_db_name pg/terraform.tfvars уникальны в пределах кластера; служебные имена (admin, postgres, standby) платформа не примет
lucee_resource_name, flask_resource_name, nodejs_resource_name apps/locals.tf уникальны внутри своего сервиса (lucee, flask, nodejs — у каждого свой набор имён)
lucee_domain, flask_domain, nodejs_domain apps/locals.tf здесь задаётся не полный домен, а имя — суффикс платформа добавляет сама: lucee-crud → lucee-crud.luceek8s.dev.nubes.ru, flask-crud → flask-crud.pythonk8s.dev.nubes.ru, nodejs-crud → nodejs-crud.nodejsk8s.dev.nubes.ru. Полные имена уникальны (это обычные DNS-имена), поэтому и задаваемое имя должно быть уникальным

Почему это важно: все ресурсы создаются с adopt_existing_on_create = true, а провайдер ищет инстанс по имени внутри своего сервиса. Если такое имя уже занято, он не станет создавать новый ресурс, а усыновит существующий — то есть при занятом имени можно подцепить чужой или старый инстанс. Если усыновление отключено, apply упадёт с «инстанс с resource_name … уже существует».

Можно не задавать (есть значения по умолчанию)

pg/main.tf: pg_cpu=500, pg_memory=512, pg_replicas=1, pg_disk=10, pg_version="17", pg_retain=14, pg_schedule="0 0 * * *", pg_timeout="11m", pg_username="user4crudpg", pg_role="ddl_user", pg_db_name="db4crudpg". Размеры приложений (*_cpu, *_memory, *_replicas) — в apps/locals.tf.


Код приложений (git)

Приложение Репозиторий
Lucee (CFML) https://gitea.services.ngcloud.ru/terraform/tfluceecrud.git
Flask (Python) https://gitea.services.ngcloud.ru/terraform/tfflaskcrud.git
Node.js (Express) https://gitea.services.ngcloud.ru/terraform/tfnodejscrud.git

Пути задаются в apps/locals.tf (lucee_git_path, flask_git_path, nodejs_git_path) — платформа сама клонирует код, собирать и заливать вручную не нужно.

Каждое приложение получает в json_env переменную SERVICE_NAME (lucee / flask / nodejs) — это значение колонки created_by, чтобы было видно, кто добавил строку.


Провайдер

Так он объявлен в pg/main.tf и apps/main.tf:

terraform {
  required_providers {
    nubes = {
      source  = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes"
      version = "3.0.0"
    }
  }
}

provider "nubes" {
  api_token    = var.api_token
  api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
}

Повседневные операции

Задача Команда
Передеплоить приложения cd apps && terraform apply
Удалить приложения cd apps && terraform destroy — БД не трогается
Изменить БД cd pg && terraform apply
Удалить БД cd pg && terraform destroy — кластер уйдёт в Suspend, а не удалится

При destroy БД кластер переводится в Suspend, а пользователь и база остаются (keep_on_destroy = true). Полное удаление: снять keep_on_destroy в pg/postgres_user_db.tf и apply.


Файлы

Файл Что делает
pg/main.tf провайдер, переменные кластера/пользователя/базы
pg/postgres.tf кластер nubes_postgres.main_pg
pg/postgres_user_db.tf пользователь crud_user_0 + база pg_db
pg/outputs.tf хост, порт, юзер, база, пароль — для выгрузки кредов
apps/main.tf провайдер, переменные приложений
apps/locals.tf чтение creds.json, домены, версии, размеры
apps/lucee.tf, apps/flask.tf, apps/nodejs.tf три приложения
pg/terraform.tfvars.example, apps/terraform.tfvars.example шаблоны для копирования в terraform.tfvars
apps/creds.json создаётся шагом 4 быстрого старта, не в git

Особенности этого примера

  • 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 — рабочая ресурсная платформа. Если у неё нет ёмкости, операция падает с сообщением «Невозможно развернуть приложение в данной ресурсной платформе».

Справка: выходные параметры 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
pg_port PGPORT PGPORT PGPORT
pg_username PGUSER PGUSER PGUSER
pg_password PGPASSWORD PGPASSWORD PGPASSWORD
pg_db_name PGDATABASE PGDATABASE — (входит в строки подключения ниже)
pg_ssl_mode PGSSLMODE PGSSLMODE PGSSLMODE

Кроме этого Lucee получает testds_connectionString и DATABASE_URL — это строки подключения, собранные из хоста, порта, пользователя, пароля и имени базы (apps/lucee.tf), а не отдельные значения выходов. Остальные testds_* (class, bundleName, bundleVersion, username, password, connectionLimit, liveTimeout, validate) — параметры JDBC-драйвера.