Files
tf_examples/Maria_CRUD/README.md
T
Repinoid 15397298de 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 его тоже не меняли.
2026-10-03 09:27:36 +03:00

16 KiB
Raw Blame History

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 и второй каталог не нужны.

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

# 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»
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:

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"
}

Как проверить, что всё работает

# 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"}
  1. Открыть любое приложение в браузере (адреса — в разделе «Быстрый старт»), добавить запись и убедиться, что она появилась в двух других приложениях и в колонке «КЕМ» указано, какое приложение её добавило.

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

Задача Команда
Изменить/передеплоить стенд 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 и что реально создано:

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, ?), а единственная подстановка в текст запроса — имя таблицы из переменной окружения, проверенное регуляркой.