# AGENTS.md — руководство для ИИ-агентов по репозиторию `tf_examples` Этот файл читается ИИ-агентами (GitHub Copilot, Claude, Cursor и др.), которые работают в этом репозитории — репозитории готовых примеров Terraform-провайдера **Nubes**. **Цель агента:** помогать пользователю писать и проверять Terraform-конфигурацию (`main.tf`, `variables.tf`, `terraform.tfvars`) для провайдера Nubes — **правильно и без выдуманных параметров**, не выполняя при этом деплой. `terraform plan` и `terraform apply` запускает **только пользователь**. > Главное правило: **параметры не берутся из памяти модели** — они сверяются со схемой > ресурса (раздел 2) и с содержимым самих примеров (раздел 6). --- ## 0. Кому адресован этот файл Файл написан для **стороннего ИИ-агента, который не знает о предметной области ничего**. Если ты такой агент — прочитай файл целиком, прежде чем советовать. Ниже объяснены: базовые понятия Terraform, что такое облако Nubes, как устроены примеры и как правильно помогать пользователю. Все утверждения о параметрах должны опираться на схему (раздел 2) или на содержимое примеров (раздел 6), а не на догадки. ## 0.1. Базовые понятия Terraform (с нуля) - **Terraform** — инструмент «инфраструктура как код» (Infrastructure as Code). Пользователь описывает желаемое состояние облака в текстовых файлах, а Terraform приводит облако к этому состоянию. Ничего не делается руками в интерфейсе — всё описано в коде. - **HCL** — язык этих файлов (расширение `.tf`). Пример блока ресурса: ```hcl resource "nubes_postgres" "main_pg" { … } # тип ресурса — nubes_postgres, локальное имя — main_pg ``` - **Провайдер** — плагин, через который Terraform общается с конкретным облаком. Здесь это провайдер **Nubes**; он скачивается автоматически при `terraform init`. - **Ресурс** — объект в облаке (кластер БД, виртуальная машина, бакет и т.п.). - **Переменные** объявляются блоком `variable "имя" { … }`, а их значения задаются в файле `terraform.tfvars` — это то, что заполняет пользователь под свою организацию. - **Файл состояния** (`terraform.tfstate`) хранит соответствие «код ↔ реальные объекты». Его **не редактируют вручную**. - **Команды**: `terraform init` (скачать провайдер), `terraform plan` (показать, что изменится, ничего не меняя), `terraform apply` (применить), `terraform destroy` (удалить). - Каждый пример — **отдельный каталог** с собственным состоянием; модули не используются. ## 0.2. Что такое Nubes **Nubes** — облачная платформа, предоставляющая услуги «как сервис»: базы данных (PostgreSQL, MongoDB, Redis, MariaDB, ClickHouse), очереди (Kafka, RabbitMQ), Kubernetes («Штурвал»), виртуализацию VMware (vDC, Edge, виртуальные машины), объектное хранилище S3, готовые приложения (Node.js, Flask, Lucee и др.) и сеть (DNS, внешние IP, SNAT). - Управляется через **Личный кабинет (ЛК)** и **API**. Terraform-провайдер Nubes — обёртка над этим API: каждая услуга платформы представлена ресурсом `nubes_*`. - Ресурсы бывают **составными**: например, кластер БД (`nubes_postgres`) → база (`nubes_postgres_database`) → пользователь (`nubes_postgres_user`). Часть ресурсов — **модификаторы** уже существующих услуг (внешние IP, SNAT). - У ресурса есть **операции** (создание/удаление/изменение/остановка/запуск). Провайдер сам вызывает нужную операцию по командам Terraform. - Значения, зависящие от организации (токен, кластер, IP-пространства, домены), задаёт пользователь; они берутся из ЛК. ## 0.3. Зачем эти примеры и как их используют Пользователь клонирует репозиторий, копирует `terraform.tfvars.example` → `terraform.tfvars`, заполняет значения из ЛК, затем выполняет `terraform init` / `plan` / `apply`. Пример — это готовый каркас конфигурации; правки сводятся к значениям и, при необходимости, к именам ресурсов. Задача агента — помогать на этих шагах, а не выполнять их вместо пользователя. --- ## 1. Что это за репозиторий Здесь лежат законченные сценарии развёртывания инфраструктуры в облаке Nubes через Terraform. Репозиторий — **эталонные примеры**, на которые ссылается сайт документации. | Папка | Что это | Состояние | Документация | |---|---|---|---| | **`vm_v3_minimal/`** | ручная организация → vDC → Edge → 3 IP → SNAT → vApp → одна ВМ, без Штурвала | отдельный VM-сценарий, проверенный на PROD 2026-10-17 | — | | **`fullpipe_chain/`** | vDC → Edge → внешние IP → SNAT → vApp → ВМ → Штурвал (полная цепочка) | расширенный сценарий с Штурвалом; VM/vApp в прежнем опыте пользователя не удалось завершить из-за проблемы с vApp | `/nubes-dev/curated/pipeline/vdc_edge_ip_snat/` | | **`Maria_CRUD/`** | MariaDB + три приложения (Lucee/Flask/Node.js) + HTTP-проверка | проверен на TEST-стенде | `/nubes-test/curated/mariadb/three_apps/` | | `CRUD/` | PostgreSQL + три приложения (Lucee/Flask/Node.js) | черновик (устаревшая схема, старый пин провайдера) — **с оговорками** | `/nubes-test/curated/crud/three_apps/` | | `modify_resources/` | внешние IP на организации + SNAT на шлюзе (для уже существующих услуг) | черновик | — | **Что рекомендовать пользователю:** для отдельной ВМ — `vm_v3_minimal`; для сценария с Штурвалом — `fullpipe_chain`; для CRUD — `Maria_CRUD`. Не используй `fullpipe_chain` как доказательство успешного самостоятельного развёртывания VM/vApp. --- ## 2. Источник правды о параметрах (схемы) Параметры ресурсов НЕ берутся из памяти. Их берут из схемы: | Источник | Что внутри | |---|---| | Страница ресурса на сайте доков | `https://tf-docs.nodejsk8s.dev.nubes.ru///` — таблица атрибутов = схема | | Машиночитаемая схема (JSON) | `https://tf-docs.nodejsk8s.dev.nubes.ru//schema/.json` | | Локально (если репозиторий внутри провайдера) | `../provider/resources_yaml/*.yaml` и `../stack///raw.json` | Структура `raw.json` / YAML-спеки (поля параметра): `name` (человеческое имя), `code` (машинное имя в API), `type` (`integer`/`string`/`map-fixed`/…), `required`, `default`, `sub_params[]`, `man` (описание). Тип ресурса в HCL — это `resource_type` (вида `nubes_*`); поля ресурса — `create.params[]`. Стенды: `nubes-test` (тест), `nubes-dev` (dev), `nubes` (prod). --- ## 3. Инвентарь ресурсов провайдера (`nubes_*`) Если пользователь называет ресурс, которого нет в этом списке — его не существует (например, `nubes_instance`, `nubes_floating_ip` — таких нет). | Ресурс | `resource_type` | |---|---| | PostgreSQL | `nubes_postgres`, `nubes_postgres_user`, `nubes_postgres_database` | | MongoDB | `nubes_mongodb`, `nubes_mongodb_user`, `nubes_mongodb_backup` | | Redis | `nubes_redis` | | MariaDB | `nubes_mariadb`, `nubes_mariadb_user`, `nubes_mariadb_database` | | ClickHouse | `nubes_clickhouse` | | Kafka | `nubes_kafka`, `nubes_kafka_topic`, `nubes_kafka_user` | | RabbitMQ | `nubes_rabbitmq` | | S3 | `nubes_s3`, `nubes_s3bucket`, `nubes_s3_sub_user` | | Сеть | `nubes_zones_v2`, `nubes_dnsrecord`, `nubes_vcexternalip` | | VMware / Cloud Director | `nubes_vc_org`, `nubes_vc_vdc`, `nubes_vc_vdc_group`, `nubes_vc_nsxt`, `nubes_vc_nsxt_snat`, `nubes_vc_org_ip_allocation`, `nubes_vapp`, `nubes_vc_vm_v3` | | Kubernetes (Штурвал) | `nubes_k8s_sthutrval_cluster`, `nubes_vc_mgmt_sthutrval_cluster`, `nubes_k8s_velero`, `nubes_k8s_openbao` | | Приложения | `nubes_nodejs`, `nubes_flask`, `nubes_lucee`, `nubes_nextcloud`, `nubes_gitea`, `nubes_harbor`, `nubes_superset`, `nubes_pgadmin`, `nubes_akhq`, `nubes_http` | | Прочее | `nubes_llm_ai`, `nubes_valo_tenant`, `nubes_template`, `nubes_dummy` | Полный актуальный список — в `provider/resources_yaml/` (у провайдера) или на сайте доков. --- ## 4. Структура примера и назначение файлов ``` tf_examples// README.md # что делает пример, порядок запуска, что заполнить main.tf # провайдер + объявление переменных variables.tf # описания и типы переменных (где есть) <ресурсы>.tf # ресурсы (например postgres.tf, vm.tf, resources.tf) locals.tf # настраиваемые значения (имена, домены, git-ревизии) terraform.tfvars.example # ШАБЛОН параметров; копируется в terraform.tfvars terraform.tfvars # реальные значения — в .gitignore, НЕ коммитится ``` Ключевая мысль: пользователь копирует `terraform.tfvars.example` в `terraform.tfvars` и заполняет. Всё, что «постоянное» (имена ресурсов, размеры), лежит в `.tf`/`locals.tf`; все значения, зависящие от организации пользователя (токен, кластер, IP-пространства) — в `terraform.tfvars`. --- ## 5. Конфигурация провайдера (одинакова во всех примерах) ```hcl terraform { required_providers { nubes = { source = "tf-registry.containerk8s.services.ngcloud.ru//nubes" version = "<версия>" } } } provider "nubes" { api_token = var.api_token # JWT из ЛК (ЛК → Профиль → Токены → «Технический») api_endpoint = "https://lk-api-gateway-.ngcloud.ru/api/v1/svc" } ``` - `api_token` — **всегда** из `terraform.tfvars`/`secrets.auto.tfvars`, никогда не в git. - `realm` — ресурсная платформа (кластер Kubernetes), должна существовать в ЛК. - `api_endpoint` — у тестового стенда `...-test.ngcloud.ru`, у прода — без суффикса. --- ## 6. Разбор примеров (что создаётся, что задаёт пользователь, грабли) ### 6.1. `fullpipe_chain` — полная цепочка (рекомендуемый) Создаёт: **vDC → Edge (AVI) → внешние IP на организации → SNAT → vApp → ВМ → кластер Штурвал**. Организацию пользователь создаёт заранее в ЛК (Terraform её не создаёт и не удаляет). Обязательные значения `terraform.tfvars`: | Переменная | Где брать | |---|---| | `api_token` | ЛК → Профиль → Токены → «Технический» | | `organization` | точное название услуги «Организация в Cloud Director» | | `vdc_network_provider` | ЛК → создание vDC → «Сетевой провайдер» | | `vdc_provider_vdc` | ЛК → создание vDC → «Provider VDC» (тот же ресурсный пул) | | `vdc_storage_config` | JSON-массив: `[{"name":"SATA","size":"200"}]` (дисковая политика + ГБ) | | `ip_space_name` | имя ipSpace из карточки организации | | `vm_user_public_key` | содержимое `~/.ssh/id_ed25519.pub` | Порядок: `terraform init` → `validate` → `plan` (7 ресурсов) → `apply`. Проверка: `terraform output` (UUID/имена услуг), ВМ — `terraform output vm_state_flat` (`externalConnect` для SSH), Штурвал — `shturval_state_params` (`kubernetesApiAddress`, `ingressAddress`). Кластер Штурвала создаётся **десятки минут** (`operation_timeout = "60m"`). `destroy` по умолчанию = **«заморозка»**: почти ничего не удаляется (см. раздел 10). Полное удаление — осознанное: снять `keep_on_destroy`/`suspend_on_destroy`, порядок — кластер → ВМ → vApp → `count = 0` → SNAT → Edge → vDC. ### 6.2. `Maria_CRUD` — CRUD на MariaDB (проверенный) Создаёт: кластер **MariaDB 9.4.0**, базу `maria`, пользователя `cruduser` (ALL PRIVILEGES), три приложения (**Lucee / Flask / Node.js**) и контейнер **HTTP-проверки** (`nubes_http`). Все приложения пишут в одну таблицу `crud_items`. Обязательные `terraform.tfvars`: `api_token`, `realm`, `mariadb_user_password` (пароль БД). Проверка работоспособности: `curl .../readyz` у HTTP-проверки → `{"status":"ready","database":"connected"}`. **Грабли (важно объяснять пользователю):** - логин БД — **без `_`** (`cruduser`, не `crud_user`): `_` ломает RFC 1123 в имени k8s-объекта; - креды берутся из подресурса `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" }])`; - `registry_path` образа — **полный** путь (`<реестр>//<образ>:<тег>`); - `adopt_existing_on_create` — повторный `apply` усыновляет существующий инстанс (не падает). ### 6.3. `CRUD` — PostgreSQL + три приложения Создаёт: PostgreSQL (`nubes_postgres`), пользователя и БД, приложения Lucee/Flask/Node.js. Особенность: PostgreSQL не успевает отдать пароль до старта приложений — нужен **двойной `terraform apply`** (или два этапа через `*_git_revision`). Домены приложений задаются в `locals.tf` (должны быть уникальны). Папка — черновик (старая плоская схема, старый пин провайдера): использовать осторожно, при вопросах сверяться с `Maria_CRUD`. ### 6.4. `modify_resources` — IP и SNAT на существующих услугах Два ресурса для **уже созданных** в ЛК услуг: `nubes_vc_org_ip_allocation` (внешние IP на организации) и `nubes_vc_nsxt_snat` (SNAT на шлюзе). `terraform.tfvars`: `api_token`, `organization`, `nsxt_uid` (UUID шлюза Edge из карточки услуги), `ip_space_name`, `ip_count`. ### 6.5. `vm_v3_minimal` — одна ВМ без дополнительных рабочих нагрузок Создаёт только цепочку **vDC → Edge → выделение IP → SNAT → vApp → ВМ** внутри организации, которую пользователь предварительно создал вручную в ЛК. Штурвал, базы данных и приложения в пример не входят. **Минимум IP для проверенного PROD-сценария — 3.** При `org_vip_count = 2` API сообщил, что свободных IP нет и для создания ВМ требуется ещё один; с `3` развёртывание ВМ запустилось. Если другие услуги используют тот же ipSpace, квоту нужно увеличить. Не делай вывод о занятых адресах только по статусам услуг в ЛК. Пример основан на отдельном сценарии `PROD_STAND/VM_TF_Docs`; он не зависит от `fullpipe_chain` и не включает его дополнительные ресурсы. --- ## 7. Секреты — жёсткое правило 1. `api_token`, пароли, ключи, приватные ключи **никогда** не попадают в `terraform.tfvars` в репозитории и тем более в git. 2. Реальные значения — в локальном **`secrets.auto.tfvars`** (Terraform загружает файлы `*.auto.tfvars` автоматически). Этот файл — в `.gitignore`. 3. Значения-заглушки (``, `eyJhbGciOi...`, `AAAA...`) — это примеры; их заменяет пользователь на свои. 4. Если файл передаётся внешнему сервису/ЛЛМ — только **без секретов**. --- ## 8. Workflow агента (проверка/написание файла) Когда просят «проверить/написать `terraform.tfvars` (или `.tf`) для X»: 1. **Определи ресурс** и найди его схему (раздел 2): `resource_type` + `params[]`. 2. **Сверь каждое поле** со схемой: имя (`code`/`name`), тип, `required`. Пропуск обязательного поля — ошибка; поле не из схемы — ошибка/предупреждение. 3. **Значения не выдумывай.** Не подставляй «правдоподобные» имена кластеров, IP, UUID, домены — их даёт пользователь или ЛК. 4. **Проверь слои**: провайдер-конфиг (`api_token`/`realm`/`api_endpoint`), параметры ресурса, зависимости (например, база должна существовать до пользователя — `depends_on`). 5. **Верни результат**: исправленный файл целиком + список изменений + предупреждения. 6. **`plan`/`apply` — пользователь.** Агент не деплоит и не удаляет. Дополнительно: проверить файл можно и через сервис — `POST //_api/file` (`{"content":"...","instruction":"..."}` → `{content, changes[], warnings[]}`), см. раздел 11. --- ## 9. Типичные ошибки (реальные, встречаются в примерах) - **Секрет в `terraform.tfvars`** (`api_token`, пароли) вместо `secrets.auto.tfvars`. - **`realm` не существует в ЛК** → «Ресурсной платформы … нету в Личном кабинете» (при этом `/resourceRealms/available` может её показывать — доверяй ЛК, не API-списку). - **Логин БД с `_`** (`crud_user`) → платформа падает на имени k8s-объекта. Только буквы/цифры. - **Короткий `registry_path`** (без хоста реестра) → образ «не находится». - **`s3_uid`, которого нет на стенде** → `invalid input syntax for type uuid`. - **Домен занят** (не уникален) → ошибка деплоя приложения. - **Пропущен `required`-параметр** (`cluster_configuration` и т.п.) → `plan`/`apply` падает. - **Опечатка в имени поля** (`clusterConfig` вместо `clusterConfiguration`) → поле игнорируется. - **Эмодзи (4-байтовые) в MariaDB-примере** → `Incorrect string value` (частичная utf8mb3-таблица). - **Асинхронные сбои платформы** («Kubernetes cluster unreachable», IAM-сбои) — не всегда вина конфигурации: повтор команды обычно проходит. --- ## 10. Жизненный цикл ресурсов (что объяснять пользователю) Провайдер управляет не только созданием, но и «судьбой» ресурса при удалении: - **`suspend_on_destroy = true`** — `destroy` не удаляет, а **останавливает** (данные сохраняются). Повторный `apply` усыновляет и размораживает. - **`keep_on_destroy = true`** — ресурс при `destroy` **не трогается вовсе** (например, Edge, SNAT, квота IP, пользователь/база внутри кластера). - **`adopt_existing_on_create = true`** — если инстанс с таким именем уже есть, провайдер его **усыновит**, а не упадёт с «уже существует». - **Организация в Cloud Director** создаётся вручную в ЛК — Terraform её не создаёт и не удаляет. Практический вывод: `terraform destroy` в этих примерах по умолчанию **не удаляет данные** — это «заморозка». Полное удаление требует явно снять флаги и соблюсти порядок. --- ## 11. Карта документации и сервисов - Ресурс на сайте доков: `https://tf-docs.nodejsk8s.dev.nubes.ru///` (таблица атрибутов = схема); например `/nubes-test/postgres/`. - Машиночитаемая схема: `…//schema/.json`. - Готовые примеры (curated): `…//curated/…` — приоритетны (проверены на деплоях). - Стенды: `nubes-test`, `nubes-dev`, `nubes` (prod). - Проверка файла через ЛЛМ: `POST //_api/file` (`{content, instruction}`) → `{content, changes, warnings}`. - Справка по документации: `POST //_api/assist` (ответ по шаблону Роль/Задача/Контекст/Требования/Ограничения). --- ## 12. Чего агент делать НЕ должен - **Деплоить**: `terraform apply`/`destroy` — только пользователь. - **Удалять/менять** реальные ресурсы и состояние Terraform. - **Читать секреты** из файлов пользователя и подставлять их в вывод. - **Выдумывать** параметры, значения, домены, UUID без источника (схема/ЛК/пользователь). - **Навязывать свой workflow** — помощь и проверка, а не «делай так, как я сказал». --- ## 13. Глоссарий | Термин | Значение | |---|---| | `realm` / ресурсная платформа | кластер Kubernetes, в котором разворачивается ресурс | | `resource_name` | имя услуги в ЛК; уникально внутри своего сервиса; используется для усыновления | | `sizing_policy` | профиль ресурсов (`TKG 4CPU 8RAM` и т.п.) | | `cluster_configuration` | размеры кластера: `cpu` (мили-ядра, 1000 = 1 vCPU), `memory` (МБ), `replicas`, `disk` (ГБ), `shards` | | `ipSpace` | пул внешних IP организации | | `vDC` | виртуальный датацентр (Cloud Director) | | `Edge` | сетевой шлюз периметра (NSX-T), балансировщик AVI | | `SNAT` | трансляция исходящего трафика через шлюз | | `vApp` | виртуальный каталог; контейнер для ВМ | | `suspend` | остановка ресурса с сохранением данных | | `adopt` | «усыновление» уже существующего ресурса по имени | | **ЛК** | Личный кабинет Nubes — веб-интерфейс управления услугами | | **услуга / инстанс** | конкретный объект в облаке, созданный по ресурсу | | **операция** | действие платформы над услугой: `create`/`delete`/`modify`/`suspend`/`resume` и др. | | `resource_name` | имя услуги в ЛК; по нему провайдер находит и усыновляет объект | | `startup_configuration` | параметры размещения (например `resource_realm` — кластер Kubernetes) | | `access_configuration` | сетевой доступ: IP-списки, внешний IP | | `backup_configuration` | резервное копирование: `schedule` (cron), `retain` (срок хранения) | | `json_env` | переменные окружения приложения (JSON) | | `git_revision` | версия кода приложения; смена значения → redeploy | | `domain` | доменное имя приложения (полный суффикс платформа добавляет сама) | | **HCL** | язык конфигурации Terraform (файлы `.tf`) | | `terraform.tfvars` | файл значений переменных; в этом репозитории — в `.gitignore` | | `terraform.tfstate` | файл состояния Terraform; хранит соответствие «код ↔ облако» |