Maria_CRUD: пример стенда (MariaDB + Lucee + Flask + Node.js + HTTP-проверка) размещён в репозитории примеров
Взят пользовательский набор отслеживаемых 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 его тоже не меняли.
This commit is contained in:
@@ -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 | <https://maria-crud-lucee.luceek8s.dev.nubes.ru> |
|
||||
| Flask | <https://maria-crud-flask.pythonk8s.dev.nubes.ru> |
|
||||
| Node.js | <https://maria-crud-nodejs.nodejsk8s.dev.nubes.ru> |
|
||||
| HTTP-проверка | <https://maria-crud-http.containerk8s.dev.nubes.ru/readyz> |
|
||||
|
||||
Добавьте запись в одном приложении — она появится в двух других (таблица `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` | **полный** путь до образа: `<реестр>/<namespace>/<образ>:<тег>`. Короткий путь (без хоста реестра) платформа не найдёт |
|
||||
| `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` — полный путь** до образа (`<реестр>/<namespace>/<образ>:<тег>`); короткий
|
||||
путь уходит в 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`, `?`), а единственная подстановка в текст запроса — имя таблицы из переменной окружения,
|
||||
проверенное регуляркой.
|
||||
Reference in New Issue
Block a user