From 15397298de74de11c3ce42b21fffa9e449efc9b1 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 3 Oct 2026 09:27:36 +0300 Subject: [PATCH] =?UTF-8?q?Maria=5FCRUD:=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20(MariaDB=20+=20?= =?UTF-8?q?Lucee=20+=20Flask=20+=20Node.js=20+=20HTTP-=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BA=D0=B0)=20=D1=80=D0=B0=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D1=89=D1=91=D0=BD=20=D0=B2=20=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Взят пользовательский набор отслеживаемых git файлов стенда TEST_STAND/Maria_CRUD репозитория tf_provider: README.md, main.tf, resources.tf, terraform.tfvars.example. Секреты и состояние не переносились: terraform.tfvars, terraform.tfstate* остались на месте, корневой .gitignore закрывает *.tfvars. Правки при переносе: - README.md — клонирование теперь git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git && cd tf_examples/Maria_CRUD; в блоке «Провайдер» подставлены конкретные значения (source .../nubes-test/nubes, version 3.1.0, endpoint TEST-стенда) вместо плейсхолдеров {{...}} — как в примере CRUD; - resources.tf — комментарий ссылался на несуществующий путь TEST_STAND/CRUD/pg/outputs.tf (CRUD переведён на плоскую схему), заменён на «см. пример CRUD в этом же репозитории». Не делалось: apply из этой копии не запускался (работоспособность проверена на стенде TEST_STAND/Maria_CRUD), корневой README репозитория примеров не менялся — он относит все папки, кроме fullpipe_chain, к черновикам, и для CRUD его тоже не меняли. --- Maria_CRUD/README.md | 218 ++++++++++++++++++++++++++++ Maria_CRUD/main.tf | 32 ++++ Maria_CRUD/terraform.tfvars.example | 9 ++ 3 files changed, 259 insertions(+) create mode 100644 Maria_CRUD/README.md create mode 100644 Maria_CRUD/main.tf create mode 100644 Maria_CRUD/terraform.tfvars.example diff --git a/Maria_CRUD/README.md b/Maria_CRUD/README.md new file mode 100644 index 0000000..ef34bb2 --- /dev/null +++ b/Maria_CRUD/README.md @@ -0,0 +1,218 @@ +# CRUD-стенд: MariaDB + Lucee + Flask + Node.js + HTTP-проверка + +Проверенный пример: одна общая база MariaDB, три приложения на разных стеках (Lucee/CFML, +Python, Node.js) и одна таблица `crud_items`. Запись, добавленная в любом из трёх приложений, +видна в двух других. Рядом — отдельный контейнер HTTP-проверки, который подтверждает, что +приложение видит базу. + +Манифесты: репозиторий примеров `tf_examples`, папка `Maria_CRUD` — **один каталог, один файл состояния**. +Креды базы передаются приложениям внутри того же `apply` (подробнее — раздел +«Особенности этого примера», пункт про `vault_secrets`), поэтому отдельный `creds.json` и +второй каталог не нужны. + +## Быстрый старт + +```bash +# 1. получить манифесты (нужен установленный Terraform — проверьте `terraform version`) +git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git +cd tf_examples/Maria_CRUD + +# 2. заполнить переменные (что именно — в разделе «Что пользователь задаёт сам») +cp terraform.tfvars.example terraform.tfvars # api_token, realm + +# 3. создать всё: кластер MariaDB + база + пользователь + три приложения + HTTP-проверка +terraform init +terraform apply +``` + +Кластер создаётся несколько минут; приложения поднимаются после него. + +> ⛔ Перед `apply` образ HTTP-проверки должен быть **опубликован** в реестре +> (`gitea.services.ngcloud.ru/terraform/mariadb-check:0.1`) — иначе создание `nubes_http` упадёт. + +После этого сервисы открываются в браузере: + +| Сервис | Адрес | +|---|---| +| Lucee | | +| Flask | | +| Node.js | | +| HTTP-проверка | | + +Добавьте запись в одном приложении — она появится в двух других (таблица `crud_items` общая). +HTTP-проверка отвечает `{"service":"maria-http","status":"ready","database":"connected"}`, +когда подключение к базе живо. + +## Что создаётся + +| Ресурс | Имя (по умолчанию) | Что это | +|---|---|---| +| `nubes_mariadb` | `maria-crud-db` | кластер MariaDB 9.4.0 | +| `nubes_mariadb_database` | — | база `maria` | +| `nubes_mariadb_user` | — | пользователь `cruduser` с полными правами (`ALL PRIVILEGES`) на `maria` | +| `nubes_lucee` | `maria-crud-lucee` | приложение на Lucee 5.4 | +| `nubes_flask` | `maria-crud-flask` | приложение на Python 3.12 | +| `nubes_nodejs` | `maria-crud-nodejs` | приложение на Node.js 23 | +| `nubes_http` | `maria-crud-http` | контейнер проверки (образ `mariadb-check:0.1`) | + +Все три приложения ходят в одну базу и одну таблицу — так видно, что CRUD работает одинаково +с любого стека. Файл состояния один, поэтому `apply`/`destroy` действуют на весь стенд сразу. + +## Что пользователь задаёт сам + +### Обязательные значения (без них `apply` не пройдёт) + +| Переменная | Где | Что это | +|---|---|---| +| `api_token` | `terraform.tfvars` | токен Nubes: ЛК → Профиль → Токены → создать «Технический» | +| `realm` | там же | ресурсная платформа (кластер Kubernetes), например `k8s-4-sandbox-nubes-ru`; **должна существовать в ЛК**, иначе операция падает с «Ресурсной платформы … нету в Личном кабинете. Не удалось получить kubeconfig» | + +```bash +cp terraform.tfvars.example terraform.tfvars +``` + +### Значения в самом конфиге, которые надо перепроверить + +| Что | Где задаётся | Правило | +|---|---|---| +| `resource_name` всех ресурсов | `resources.tf` | уникально **внутри своего сервиса**: провайдер ищет инстанс по имени, и при `adopt_existing_on_create = true` займёт существующий, а не создаст новый | +| `domain` приложений | `resources.tf` (`access_configuration` / `domain`) | задаётся **не полный домен, а имя**: суффикс платформа добавляет сама (`maria-crud-flask` → `maria-crud-flask.pythonk8s.dev.nubes.ru`). Домены должны быть уникальными | +| `s3_uid` | `backup_configuration` | имя **или** UUID экземпляра S3 (в примере `naeel-s3`). Имя резолвится провайдером в UUID; если такого экземпляра на стенде нет — платформа упадёт с `invalid input syntax for type uuid` | +| `registry_path` | `nubes_http` | **полный** путь до образа: `<реестр>//<образ>:<тег>`. Короткий путь (без хоста реестра) платформа не найдёт | +| `cruduser` / `crudpass` | `nubes_mariadb_user` | логин и пароль БД задаются здесь; логин — см. «Особенности» (без `_`) | + +### Можно не задавать (есть значения в манифесте) + +`cluster_configuration` кластера: `cpu=500` мили-ядер (1000 = 1 vCPU), `memory=1024` МБ, `replicas=1`, +`disk=1` ГБ; +`backup_configuration`: `retain=24` (часы), `schedule="0 0 * * *"`; `master_ip_space="no-needed"` +(внешний доступ не нужен), `master_access_list=["10.0.0.0/8"]`; версия MariaDB `9.4.0`. +Размеры приложений: Lucee `300` мК / `512` МБ, Flask и Node.js `300` мК / `256` МБ, +HTTP-проверка `100` мК / `256` МБ, `1` экземпляр. + +## Код приложений + +| Приложение | Источник | +|---|---| +| Lucee (CFML) | `https://gitea.services.ngcloud.ru/terraform/maria_lucee.git` | +| Flask (Python) | `https://gitea.services.ngcloud.ru/terraform/maria_flask.git` | +| Node.js (Express) | `https://gitea.services.ngcloud.ru/terraform/maria_nodejs.git` | +| HTTP-проверка | образ `gitea.services.ngcloud.ru/terraform/mariadb-check:0.1` | + +Пути задаются в `resources.tf` (`app_configuration.git_path` / `registry_path`) — платформа сама +клонирует код и собирает приложения, вручную собирать и заливать не нужно (кроме образа +HTTP-проверки: он уже должен быть в реестре). + +Каждое приложение получает в `json_env` переменную `SERVICE_NAME` (`lucee` / `flask` / `nodejs`) — +это значение колонки `created_by`, чтобы было видно, кто добавил строку. + +## Провайдер + +Так он объявлен в `main.tf`: + +```hcl +terraform { + required_providers { + nubes = { + source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + version = "3.1.0" + } + } +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" +} +``` + +## Как проверить, что всё работает + +```bash +# 1. HTTP-проверка: процесс жив и база доступна +curl -s https://maria-crud-http.containerk8s.dev.nubes.ru/healthz # {"status":"ok"} +curl -s https://maria-crud-http.containerk8s.dev.nubes.ru/readyz # {"service":"maria-http","status":"ready","database":"connected"} +``` + +2. Открыть любое приложение в браузере (адреса — в разделе «Быстрый старт»), добавить запись +и убедиться, что она появилась в двух других приложениях и в колонке «КЕМ» указано, какое +приложение её добавило. + +## Повседневные операции + +| Задача | Команда | +|---|---| +| Изменить/передеплоить стенд | `terraform apply` | +| Остановить стенд | `terraform destroy` — кластер переводится в состояние `Suspend` (под останавливается, ресурсы не потребляются, данные на диске сохраняются); приложения выключаются | + +> При `destroy` кластер **не удаляется**: срабатывает `suspend` (у кластера `keep_on_destroy` не задан, +> действует `suspend_on_destroy = true`) — кластер переходит в состояние `Suspend` (остановлен, +> данные не удаляются). Пользователь и база остаются внутри кластера — +> у них `keep_on_destroy = true`, поэтому `delete_user` / `delete_database` не вызываются. +> Повторный `apply` возвращает кластер из состояния `Suspend` (усыновление + `resume`), содержимое сохраняется. + +### Если операция завершилась ошибкой + +Платформа работает асинхронно, поэтому и `apply`, и `destroy` изредка могут падать по причинам, +не связанным с конфигурацией: таймаут до кластера Kubernetes (`Kubernetes cluster unreachable`, +`TLS handshake timeout`), сбой аутентификации платформы (IAM) и др. В таких случаях просто +повторите команду — обычно она проходит. + +Проверить, что записано в состоянии Terraform и что реально создано: + +```bash +terraform state list # какие ресурсы Terraform считает созданными +terraform plan # что Terraform собирается изменить (без реальных изменений) +``` + +Состояние автоматически перезаписывается только при `apply`; `plan` показывает разницу «на бумаге», +ничего не меняя. + +### Если нужно удалить пользователя или базу + +`keep_on_destroy = true` означает «при destroy не удалять», поэтому удаление блока из файла +само по себе объект **не** удалит (провайдер возьмёт значение из состояния). Порядок такой: + +1. у нужного подресурса поставить `keep_on_destroy = false`, выполнить `terraform apply` + (в облаке ничего не меняется — это только флаг жизненного цикла); +2. убрать блок ресурса из `resources.tf`, выполнить `terraform apply` → сработает + `delete_user` / `delete_database`. + +Альтернатива одним действием — удалить объект вручную в ЛК (операция `delete_user`). + +## Файлы + +| Файл | Что делает | +|---|---| +| `main.tf` | провайдер и переменные (`api_token`, `realm`) | +| `resources.tf` | все ресурсы стенда: кластер, база, пользователь, три приложения, HTTP-проверка | +| `terraform.tfvars.example` | шаблон для копирования в `terraform.tfvars` (сам `terraform.tfvars` — в `.gitignore`) | + +## Особенности этого примера (важные грабли) + +- **Логин БД — без подчёркиваний.** Формально regex платформы `_` допускает, но имя k8s-объекта + `Grant` собирается из логина, а `_` ломает RFC 1123 (`metadata.name: Invalid value`). Дефис тоже + нельзя — его не пропускает сам regex. Поэтому `cruduser`, а не `crud_user`. +- **Креды берутся из подресурса `nubes_mariadb_user`, а не из `vault_secrets` кластера.** Внутри + одного `apply` `vault_secrets` кластера ещё пуст, и ссылка на него падает с `Invalid index`. +- **`retain` — в часах, а не в днях**, и платформа требует минимум `24`. При суточном расписании + меньшие значения оставляют базу без валидной копии бэкапа. +- **Версия MariaDB** — только `8.4.0` или `9.4.0` (версию можно только повышать). +- **`db_name` пользователя** — не строка, а JSON-массив существующих баз: + `jsonencode([{ dbname = "maria" }])` = полный доступ к базе `maria`. База должна существовать + заранее, поэтому у пользователя есть `depends_on` на `nubes_mariadb_database`. +- **`registry_path` — полный путь** до образа (`<реестр>//<образ>:<тег>`); короткий + путь уходит в API как есть и платформой не находится. +- **`realm`** должна существовать в ЛК (см. «Обязательные значения»); при этом список + `/resourceRealms/available` может показывать её как доступную, хотя объекта платформы нет. +- **4-байтовые символы (эмодзи) не сохраняются.** Таблицу `crud_items` создаёт то приложение, + которое стартовало первым; у Lucee и Flask в `CREATE TABLE` не задан `DEFAULT CHARSET` + (utf8mb4 задан только у Node.js), поэтому колонка `value` может получиться в utf8mb3 и вставка + эмодзи падает с `Incorrect string value`. Обычный набор (латиница, кириллица, спецсимволы) + работает. +- **`adopt_existing_on_create = true`** у всех ресурсов: если инстанс с таким именем уже есть + (например после `destroy`, который приостанавливает, а не удаляет), провайдер его **усыновит**, + а не упадёт с «ресурс с таким именем уже существует». +- **SQL-инъекции закрыты**: во всех приложениях значения идут параметрами (`cfqueryparam`, + `%s`, `?`), а единственная подстановка в текст запроса — имя таблицы из переменной окружения, + проверенное регуляркой. diff --git a/Maria_CRUD/main.tf b/Maria_CRUD/main.tf new file mode 100644 index 0000000..cfa4e26 --- /dev/null +++ b/Maria_CRUD/main.tf @@ -0,0 +1,32 @@ +# Требования к провайдеру. Источник — Terraform Registry платформы (namespace nubes-test = TEST-стенд), +# версия 3.1.0 (ломающе: postgres_conf → map-fixed; gate по isCreated — недосозданный +# инстанс не пишется в state). Менять namespace вместе со стендом: nubes / nubes-dev / nubes-test. +terraform { + required_providers { + nubes = { + source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-test/nubes" + version = "3.1.0" + } + } +} + +# Токен Nubes API. Значение задаётся в terraform.tfvars (файл в .gitignore) и не коммитится. +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} + +# Ресурсная платформа (кластер K8s), в которой создаётся ВСЁ: и кластер MariaDB, и БД, и приложения. +# Значение должно совпадать у всех ресурсов стенда и существовать в ЛК, иначе получите: +# «Ресурсной платформы '' нету в Личном кабинете. Не удалось получить kubeconfig». +variable "realm" { + type = string + description = "Resource platform shared by the MariaDB cluster and its apps" +} + +# api_endpoint — TEST-стенд. Для DEV/PROD менять вместе с source провайдера и токеном. +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc" +} diff --git a/Maria_CRUD/terraform.tfvars.example b/Maria_CRUD/terraform.tfvars.example new file mode 100644 index 0000000..f6a0e8a --- /dev/null +++ b/Maria_CRUD/terraform.tfvars.example @@ -0,0 +1,9 @@ +# Шаблон для terraform.tfvars (сам terraform.tfvars в .gitignore — токен не коммитить). + +# Токен Nubes API для TEST-стенда (secrets/test.token). +api_token = "" + +# Ресурсная платформа (кластер K8s) — одна и та же для БД и всех приложений. +# Список доступных можно получить через API: GET /resourceRealms/available?svcId=115 +# или посмотреть в ЛК в разделе кластеров/ресурсных платформ. +realm = "k8s-4-sandbox-nubes-ru" \ No newline at end of file