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 с локальной сборкой совпал.
This commit is contained in:
Repinoid
2026-10-02 08:58:38 +03:00
parent 39af8bfcbf
commit 17350eed89
2 changed files with 146 additions and 54 deletions
+76 -14
View File
@@ -48,6 +48,22 @@ cd ../apps && terraform init && terraform apply
--- ---
## Что создаётся
| Ресурс | Имя (по умолчанию) | Что это |
|---|---|---|
| `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: Два каталога — два отдельных файла состояния Terraform:
@@ -86,9 +102,9 @@ cd ../apps && cp terraform.tfvars.example terraform.tfvars
| Имя | Где задаётся | Правило | | Имя | Где задаётся | Правило |
|---|---|---| |---|---|---|
| `pg_resource_name` (кластер) | `pg/terraform.tfvars` | уникально **в пределах стенда** | | `pg_resource_name` (кластер) | `pg/terraform.tfvars` | уникально **внутри сервиса `postgres`** — другого инстанса с таким именем быть не должно |
| `pg_username`, `pg_db_name` | `pg/terraform.tfvars` | уникальны в пределах кластера; служебные имена (`admin`, `postgres`, `standby`) платформа не примет | | `pg_username`, `pg_db_name` | `pg/terraform.tfvars` | уникальны в пределах кластера; служебные имена (`admin`, `postgres`, `standby`) платформа не примет |
| `lucee_resource_name`, `flask_resource_name`, `nodejs_resource_name` | `apps/locals.tf` | уникальны **в пределах стенда** | | `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-имена), поэтому и задаваемое имя должно быть уникальным | | `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`, а провайдер ищет Почему это важно: все ресурсы создаются с `adopt_existing_on_create = true`, а провайдер ищет
@@ -108,15 +124,39 @@ cd ../apps && cp terraform.tfvars.example terraform.tfvars
## Код приложений (git) ## Код приложений (git)
Платформа сама клонирует код из репозиториев — собирать и заливать вручную не нужно:
| Приложение | Репозиторий | | Приложение | Репозиторий |
|---|---| |---|---|
| Lucee (CFML) | [`terraform/tfluceecrud`](https://gitea.services.ngcloud.ru/terraform/tfluceecrud) | | Lucee (CFML) | `https://gitea.services.ngcloud.ru/terraform/tfluceecrud.git` |
| Flask (Python) | [`terraform/tfflaskcrud`](https://gitea.services.ngcloud.ru/terraform/tfflaskcrud) | | Flask (Python) | `https://gitea.services.ngcloud.ru/terraform/tfflaskcrud.git` |
| Node.js (Express) | [`terraform/tfnodejscrud`](https://gitea.services.ngcloud.ru/terraform/tfnodejscrud) | | Node.js (Express) | `https://gitea.services.ngcloud.ru/terraform/tfnodejscrud.git` |
Пути задаются в `apps/locals.tf`: `lucee_git_path`, `flask_git_path`, `nodejs_git_path`. Пути задаются в `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`:
```hcl
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"
}
```
--- ---
@@ -150,13 +190,30 @@ cd ../apps && cp terraform.tfvars.example terraform.tfvars
--- ---
## Особенности этого примера
- **`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/`
В `pg/outputs.tf` объявлено шесть выходов: В `pg/outputs.tf` объявлено шесть выходов:
| Выход | Значение | Откуда | | Выход | Значение | Откуда |
|---|---|---| |---|---|---|
| `pg_host` | внутренний хост master | `state_out_flat["internalMaster"]` кластера | | `pg_host` | внутренний хост master — приложения ходят к базе по внутренней сети кластера, а сами приложения открываются по внешним доменам (см. «Быстрый старт») | `state_out_flat["internalMaster"]` кластера |
| `pg_port` | `5432` | константа в `outputs.tf` | | `pg_port` | `5432` | константа в `outputs.tf` |
| `pg_username` | имя пользователя БД | выход подресурса `nubes_postgres_user` | | `pg_username` | имя пользователя БД | выход подресурса `nubes_postgres_user` |
| `pg_db_name` | имя базы | выход `nubes_postgres_database` | | `pg_db_name` | имя базы | выход `nubes_postgres_database` |
@@ -184,9 +241,14 @@ pg_pass = local.creds.pg_password.value
| Выход `pg/` | Flask | Node.js | Lucee | | Выход `pg/` | Flask | Node.js | Lucee |
|---|---|---|---| |---|---|---|---|
| `pg_host` | `PGHOST` | `PGHOST` | `PGHOST`, `testds_connectionString` | | `pg_host` | `PGHOST` | `PGHOST` | `PGHOST` |
| `pg_port` | `PGPORT` | `PGPORT` | `PGPORT`, `testds_connectionString` | | `pg_port` | `PGPORT` | `PGPORT` | `PGPORT` |
| `pg_username` | `PGUSER` | `PGUSER` | `PGUSER`, `testds_username` | | `pg_username` | `PGUSER` | `PGUSER` | `PGUSER` |
| `pg_password` | `PGPASSWORD` | `PGPASSWORD` | `PGPASSWORD`, `testds_password` | | `pg_password` | `PGPASSWORD` | `PGPASSWORD` | `PGPASSWORD` |
| `pg_db_name` | `PGDATABASE` | `PGDATABASE` | `testds_connectionString`, `DATABASE_URL` | | `pg_db_name` | `PGDATABASE` | `PGDATABASE` | — (входит в строки подключения ниже) |
| `pg_ssl_mode` | `PGSSLMODE` | `PGSSLMODE` | `PGSSLMODE` | | `pg_ssl_mode` | `PGSSLMODE` | `PGSSLMODE` | `PGSSLMODE` |
Кроме этого Lucee получает `testds_connectionString` и `DATABASE_URL` — это **строки подключения**,
собранные из хоста, порта, пользователя, пароля и имени базы (`apps/lucee.tf`), а не отдельные
значения выходов. Остальные `testds_*` (`class`, `bundleName`, `bundleVersion`, `username`,
`password`, `connectionLimit`, `liveTimeout`, `validate`) — параметры JDBC-драйвера.
+70 -40
View File
@@ -58,22 +58,7 @@ cd ../apps && terraform init && terraform apply
Все три приложения ходят в одну базу и одну таблицу — так видно, что CRUD работает одинаково Все три приложения ходят в одну базу и одну таблицу — так видно, что 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` |
Пути к репозиториям задаются в `apps/locals.tf` (`lucee_git_path`,
`flask_git_path`, `nodejs_git_path`). Платформа сама клонирует код — собирать и заливать
вручную не нужно.
Каждое приложение получает в `json_env` переменную `SERVICE_NAME` (`lucee` / `flask` / `nodejs`) —
это значение колонки `created_by`, чтобы было видно, кто добавил строку.
## Структура манифестов
Два каталога — два отдельных файла состояния Terraform: Два каталога — два отдельных файла состояния Terraform:
@@ -83,11 +68,10 @@ CRUD/
└── apps/ приложения: Lucee + Flask + Node.js └── apps/ приложения: Lucee + Flask + Node.js
``` ```
- `terraform apply` и `terraform destroy` в `apps/` меняют **только приложения** — база в `pg/` не затрагивается; - `terraform apply` и `terraform destroy` в `apps/` меняют **только приложения** (создают, изменяют, удаляют) — база в `pg/` не затрагивается;
- `terraform apply` и `terraform destroy` в `pg/` меняют только базу — приложения в `apps/` не затрагиваются; - `terraform apply` и `terraform destroy` в `pg/` меняют только базу — приложения в `apps/` не затрагиваются;
- приложения можно пересоздавать сколько угодно, база при этом не меняется; - приложения можно пересоздавать сколько угодно, база при этом не меняется;
- связь между каталогами — файл `apps/creds.json`: он делается из `terraform output` в `pg/`, - связь между каталогами — файл `apps/creds.json` (шаг 4 быстрого старта): он делается из `terraform output` в `pg/`, поэтому после смены хоста или пароля его нужно обновить и повторить `apply` в `apps/`.
поэтому после смены хоста или пароля его нужно обновить и повторить `apply` в `apps/`.
## Что пользователь задаёт сам ## Что пользователь задаёт сам
@@ -110,9 +94,9 @@ cd ../apps && cp terraform.tfvars.example terraform.tfvars
| Имя | Где задаётся | Правило | | Имя | Где задаётся | Правило |
|---|---|---| |---|---|---|
| `pg_resource_name` (кластер) | `pg/terraform.tfvars` | уникально **в пределах стенда** | | `pg_resource_name` (кластер) | `pg/terraform.tfvars` | уникально **внутри сервиса `postgres`** — другого инстанса с таким именем быть не должно |
| `pg_username`, `pg_db_name` | `pg/terraform.tfvars` | уникальны в пределах кластера; служебные имена (`admin`, `postgres`, `standby`) платформа не примет | | `pg_username`, `pg_db_name` | `pg/terraform.tfvars` | уникальны в пределах кластера; служебные имена (`admin`, `postgres`, `standby`) платформа не примет |
| `lucee_resource_name`, `flask_resource_name`, `nodejs_resource_name` | `apps/locals.tf` | уникальны **в пределах стенда** | | `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-имена), поэтому и задаваемое имя должно быть уникальным | | `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`, а провайдер ищет Почему это важно: все ресурсы создаются с `adopt_existing_on_create = true`, а провайдер ищет
@@ -128,6 +112,20 @@ cd ../apps && cp terraform.tfvars.example terraform.tfvars
`pg_role="ddl_user"`, `pg_db_name="db4crudpg"`. Размеры приложений (`*_cpu`, `*_memory`, `pg_role="ddl_user"`, `pg_db_name="db4crudpg"`. Размеры приложений (`*_cpu`, `*_memory`,
`*_replicas`) — в `apps/locals.tf`. `*_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`, чтобы было видно, кто добавил строку.
## Провайдер ## Провайдер
```hcl ```hcl
@@ -146,13 +144,54 @@ provider "nubes" {
} }
``` ```
## Повседневные операции
| Задача | Команда |
|---|---|
| Передеплоить приложения | `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/`
В `pg/outputs.tf` объявлено шесть выходов: В `pg/outputs.tf` объявлено шесть выходов:
| Выход | Значение | Откуда | | Выход | Значение | Откуда |
|---|---|---| |---|---|---|
| `pg_host` | внутренний хост master | `state_out_flat["internalMaster"]` кластера | | `pg_host` | внутренний хост master — приложения ходят к базе по внутренней сети кластера, а сами приложения открываются по внешним доменам (см. «Быстрый старт») | `state_out_flat["internalMaster"]` кластера |
| `pg_port` | `5432` | константа в `outputs.tf` | | `pg_port` | `5432` | константа в `outputs.tf` |
| `pg_username` | имя пользователя БД | выход подресурса `nubes_postgres_user` | | `pg_username` | имя пользователя БД | выход подресурса `nubes_postgres_user` |
| `pg_db_name` | имя базы | выход `nubes_postgres_database` | | `pg_db_name` | имя базы | выход `nubes_postgres_database` |
@@ -181,24 +220,15 @@ pg_pass = local.creds.pg_password.value
| Выход `pg/` | Flask | Node.js | Lucee | | Выход `pg/` | Flask | Node.js | Lucee |
|---|---|---|---| |---|---|---|---|
| `pg_host` | `PGHOST` | `PGHOST` | `PGHOST`, `testds_connectionString` | | `pg_host` | `PGHOST` | `PGHOST` | `PGHOST` |
| `pg_port` | `PGPORT` | `PGPORT` | `PGPORT`, `testds_connectionString` | | `pg_port` | `PGPORT` | `PGPORT` | `PGPORT` |
| `pg_username` | `PGUSER` | `PGUSER` | `PGUSER`, `testds_username` | | `pg_username` | `PGUSER` | `PGUSER` | `PGUSER` |
| `pg_password` | `PGPASSWORD` | `PGPASSWORD` | `PGPASSWORD`, `testds_password` | | `pg_password` | `PGPASSWORD` | `PGPASSWORD` | `PGPASSWORD` |
| `pg_db_name` | `PGDATABASE` | `PGDATABASE` | `testds_connectionString`, `DATABASE_URL` | | `pg_db_name` | `PGDATABASE` | `PGDATABASE` | — (входит в строки подключения ниже) |
| `pg_ssl_mode` | `PGSSLMODE` | `PGSSLMODE` | `PGSSLMODE` | | `pg_ssl_mode` | `PGSSLMODE` | `PGSSLMODE` | `PGSSLMODE` |
## Особенности этого примера Кроме этого Lucee получает `testds_connectionString` и `DATABASE_URL` — это **строки подключения**,
собранные из хоста, порта, пользователя, пароля и имени базы (`apps/lucee.tf`), а не отдельные
значения выходов. Остальные `testds_*` (`class`, `bundleName`, `bundleVersion`, `username`,
`password`, `connectionLimit`, `liveTimeout`, `validate`) — параметры JDBC-драйвера.
- **`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`** — рабочая ресурсная платформа. Если у неё нет ёмкости, операция падает
с сообщением «Невозможно развернуть приложение в данной ресурсной платформе».