From dbaadccc5016151b6418009b641ca156b7d17f8d Mon Sep 17 00:00:00 2001 From: Nail Date: Fri, 25 Sep 2026 08:05:12 +0300 Subject: [PATCH] =?UTF-8?q?docs(resume):=20=D0=BF=D0=BE=D0=B4=D1=80=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B5=20=D1=80=D0=B5=D0=B7=D1=8E=D0=BC=D0=B5?= =?UTF-8?q?=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=A8=D1=82=D1=83=D1=80=D0=B2=D0=B0=D0=BB/freeze=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D1=87?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=20+=20=D1=81=D1=82=D0=B5=D0=BD=D0=B4=20DEV?= =?UTF-8?q?=5FSTAND/FPipeGmail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEV_STAND/FPipeGmail/edge.tf | 28 +++ DEV_STAND/FPipeGmail/modifiers.tf | 60 +++++++ DEV_STAND/FPipeGmail/outputs.tf | 29 ++++ DEV_STAND/FPipeGmail/provider.tf | 4 + DEV_STAND/FPipeGmail/shturval.tf | 161 ++++++++++++++++++ DEV_STAND/FPipeGmail/terraform.tfvars.example | 13 ++ DEV_STAND/FPipeGmail/variables.tf | 116 +++++++++++++ DEV_STAND/FPipeGmail/vdc.tf | 20 +++ DEV_STAND/FPipeGmail/versions.tf | 10 ++ ...RESUME_2026-09-25_shturval_freeze_state.md | 142 +++++++++++++++ 10 files changed, 583 insertions(+) create mode 100644 DEV_STAND/FPipeGmail/edge.tf create mode 100644 DEV_STAND/FPipeGmail/modifiers.tf create mode 100644 DEV_STAND/FPipeGmail/outputs.tf create mode 100644 DEV_STAND/FPipeGmail/provider.tf create mode 100644 DEV_STAND/FPipeGmail/shturval.tf create mode 100644 DEV_STAND/FPipeGmail/terraform.tfvars.example create mode 100644 DEV_STAND/FPipeGmail/variables.tf create mode 100644 DEV_STAND/FPipeGmail/vdc.tf create mode 100644 DEV_STAND/FPipeGmail/versions.tf create mode 100644 NOTES/40_chat_summaries/CHAT_RESUME_2026-09-25_shturval_freeze_state.md diff --git a/DEV_STAND/FPipeGmail/edge.tf b/DEV_STAND/FPipeGmail/edge.tf new file mode 100644 index 0000000..ec8f962 --- /dev/null +++ b/DEV_STAND/FPipeGmail/edge.tf @@ -0,0 +1,28 @@ +resource "nubes_vc_nsxt" "edge" { + resource_name = var.nsxt_resource_name + + # Тип родительской услуги: "vdc" (нужен vdc_uid) или "vdcGroup" (нужен vdc_group_uid) + vdc_type = var.nsxt_vdc_type + + # refSvc-поле: принимает UUID или имя. Здесь берём UID созданного VDC, + # чтобы Edge гарантированно создавался после vDC. + vdc_uid = nubes_vc_vdc.vdc.id + + need_enable_avi = var.nsxt_need_enable_avi + virtual_services_count = var.nsxt_virtual_services_count + + # routed-сеть, которую разворачивает Edge (SingleNestedAttribute -> объект) + routed_net_configuration = { + ip_addr_pool = var.nsxt_ip_addr_pool + main_dns = var.nsxt_main_dns + second_dns = var.nsxt_second_dns + } + + # «Заморозка»: destroy НЕ удаляет эдж (у платформы для эджа нет операции suspend), + # а только убирает его из состояния. Для полного удаления — keep_on_destroy = false. + keep_on_destroy = true + + # Повторный apply усыновляет уже работающий эдж, а не падает с + # «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (RUNNING)». + adopt_existing_on_create = true +} diff --git a/DEV_STAND/FPipeGmail/modifiers.tf b/DEV_STAND/FPipeGmail/modifiers.tf new file mode 100644 index 0000000..a148873 --- /dev/null +++ b/DEV_STAND/FPipeGmail/modifiers.tf @@ -0,0 +1,60 @@ +# ============================================================================= +# Ресурсы-модификаторы (операции modify, которых нет в create-схеме ресурсов) +# +# Порядок строго такой: +# орга (создана вручную в ЛК) +# -> nubes_vc_vdc.vdc +# -> nubes_vc_nsxt.edge +# -> nubes_vc_org_ip_allocation (выделение внешних IP на орге) +# -> nubes_vc_nsxt_snat (SNAT на эдже этим ipSpace) +# +# Почему аллокация ПОСЛЕ эджа: платформа строит список ipSpace из состояния +# `job.vcd.networkProvider` / `job.vcd.providerGateway`, то есть требует уже +# созданный vDC и Edge. Иначе modify на орге падает +# («Can't cast Complex Object Type Struct to String»). +# ============================================================================= + +# 1. Внешние IP на организации (modify: vIPConfigure, массив перезаписывается целиком) +resource "nubes_vc_org_ip_allocation" "org_ip" { + organization = var.organization + + vip_configure = jsonencode([ + { + name = var.ip_space_name + count = var.ip_count + } + ]) + + # true = «заморозка»: destroy не трогает квоту внешних IP (кластер Штурвала держит + # адреса, опустить count ниже занятых платформа не даёт). Для полного удаления — false + # (и только после удаления кластера). + keep_on_destroy = true + + depends_on = [nubes_vc_nsxt.edge] +} + +# 2. SNAT на эдже (modify: ipSpaceName) +resource "nubes_vc_nsxt_snat" "snat" { + nsxt_uid = nubes_vc_nsxt.edge.id + ip_space_name = var.ip_space_name + + # true = «заморозка»: destroy не выключает SNAT на эдже. Для полного удаления — false. + keep_on_destroy = true + + # ipSpace должен быть уже выделен на организации + depends_on = [nubes_vc_org_ip_allocation.org_ip] +} + +output "allocated_org_ip" { + description = "Выделено внешних IP на организации" + value = { + organization = var.organization + ip_space_name = var.ip_space_name + ip_count = var.ip_count + } +} + +output "snat_ip_space" { + description = "ipSpace, включённый как SNAT на эдже" + value = nubes_vc_nsxt_snat.snat.ip_space_name +} diff --git a/DEV_STAND/FPipeGmail/outputs.tf b/DEV_STAND/FPipeGmail/outputs.tf new file mode 100644 index 0000000..13ee3e0 --- /dev/null +++ b/DEV_STAND/FPipeGmail/outputs.tf @@ -0,0 +1,29 @@ +output "vdc_id" { + description = "UID созданного VDC" + value = nubes_vc_vdc.vdc.id +} + +output "vdc_name" { + description = "Имя VDC" + value = nubes_vc_vdc.vdc.resource_name +} + +output "vdc_state_params" { + description = "Параметры состояния VDC из API" + value = nubes_vc_vdc.vdc.state_params +} + +output "nsxt_id" { + description = "UID созданного Edge (vc_nsxt)" + value = nubes_vc_nsxt.edge.id +} + +output "nsxt_name" { + description = "Имя Edge (vc_nsxt)" + value = nubes_vc_nsxt.edge.resource_name +} + +output "nsxt_state_params" { + description = "Параметры состояния Edge (vc_nsxt) из API" + value = nubes_vc_nsxt.edge.state_params +} diff --git a/DEV_STAND/FPipeGmail/provider.tf b/DEV_STAND/FPipeGmail/provider.tf new file mode 100644 index 0000000..9d91910 --- /dev/null +++ b/DEV_STAND/FPipeGmail/provider.tf @@ -0,0 +1,4 @@ +provider "nubes" { + api_token = var.api_token + api_endpoint = var.api_endpoint +} diff --git a/DEV_STAND/FPipeGmail/shturval.tf b/DEV_STAND/FPipeGmail/shturval.tf new file mode 100644 index 0000000..9c9cce4 --- /dev/null +++ b/DEV_STAND/FPipeGmail/shturval.tf @@ -0,0 +1,161 @@ +# ============================================================================= +# Kubernetes кластер Штурвал — сервис 150, ресурс nubes_k8s_sthutrval_cluster +# (НЕ 148 «Менеджмент Kubernetes кластер Штурвал» — это другой сервис) +# +# Всё, что относится к Штурвалу, лежит ТОЛЬКО в этом файле: переменные, их +# значения по умолчанию и сам ресурс. Чтобы выключить Штурвал — удалить файл +# или закомментировать ресурс. +# +# Порядок (чек-лист из инструкции на услугу в ЛК): +# 1) Организация в Cloud Director — создана вручную в ЛК +# 2) nubes_vc_vdc.vdc — есть +# 3) nubes_vc_nsxt.edge — есть, обязательно ALB + AVI VS >= 3 +# 4) внешние адреса в организации — суммарно >= 3 (nubes_vc_org_ip_allocation) +# 5) SNAT на Edge — nubes_vc_nsxt_snat +# 6) Kubernetes кластер Штурвал — этот ресурс +# +# Минимальные требования к кластеру: мастер-нод >= 1, воркер-нод >= 1, +# 4 vCPU / 8 GB RAM / 50 GB диска на ноду. +# ============================================================================= + +# --- Переменные Штурвала --- + +variable "shturval_resource_name" { + type = string + default = "shturval-dev" + description = "Имя услуги «Kubernetes кластер Штурвал» в ЛК" +} + +variable "shturval_cluster_name" { + type = string + default = "shturval-dev-00" + description = "Имя кластера внутри Штурвала" +} + +variable "shturval_app_version" { + type = string + default = "2.14.0" + description = "Версия Штурвала (значение по умолчанию платформы — 2.14.0)" +} + +variable "shturval_cp_sizing_policy" { + type = string + default = "TKG 4CPU 8RAM" + description = "Политика размера control plane: 4 vCPU / 8 GB (минимум по инструкции). Должна существовать в ресурсной платформе vDC — список политик берётся из услуги «Виртуальный датацентр»" +} + +variable "shturval_cp_sizing_disk" { + type = number + default = 50 + description = "Диск control plane, ГБ (минимум 50)" +} + +variable "shturval_cp_count" { + type = number + default = 1 + description = "Количество мастер-нод: 1, 3 или 5" +} + +variable "shturval_worker_group_name" { + type = string + default = "workers-shturval-dev" + description = "Имя группы воркеров (уникальное в кластере; допустимы строчные латинские буквы, цифры и дефис)" +} + +variable "shturval_worker_sizing_policy" { + type = string + default = "TKG 4CPU 8RAM" + description = "Политика размера воркеров: 4 vCPU / 8 GB (минимум по инструкции)" +} + +variable "shturval_worker_sizing_disk" { + type = number + default = 50 + description = "Диск воркеров, ГБ (минимум 50)" +} + +variable "shturval_worker_count" { + type = number + default = 1 + description = "Количество воркер-нод (минимум 1)" +} + +# --- Значения, которые собираются из переменных --- + +locals { + # Группы воркеров передаются JSON-строкой ВНУТРЬ услуги как есть, поэтому ключи + # должны быть ровно такими, как в манифесте услуги 150: groupName, sizingPolicy, + # sizingDisk, count, autoscale, labelDeck. + # ВНИМАНИЕ: в сгенерированном примере провайдера (docs → Example) ключи показаны + # в snake_case — это ошибка генератора, платформа на них падает с + # «Cannot invoke method split() on null object» (не находит groupName → null). + shturval_worker_config = jsonencode([ + { + groupName = var.shturval_worker_group_name + sizingPolicy = var.shturval_worker_sizing_policy + sizingDisk = var.shturval_worker_sizing_disk + count = var.shturval_worker_count + autoscale = false # автоскейл выключен + labelDeck = true # разрешить разворачивать услуги из ЛК на этих нодах + } + ]) +} + +# --- Ресурс Штурвала --- + +resource "nubes_k8s_sthutrval_cluster" "shturval" { + resource_name = var.shturval_resource_name + + # Кластер Штурвала уже существует (инстанс «shturval-dev») и в проде не + # удаляется неделями, поэтому ресурс должен УСЫНОВИТЬ существующий инстанс, + # а не падать с «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ (SUSPEND)». + # Проверка/adopt выполняются в Create на apply (в plan будет «will be created»). + adopt_existing_on_create = true + + # «Заморозка»: destroy приостанавливает кластер (suspend), а не удаляет. + # Следующий apply усыновит его и разморозит (resume). + suspend_on_destroy = true + + # Штурвал создаётся долго (десятки минут) — поднимаем таймаут ожидания, + # иначе провайдер сдаётся на дефолтных 600 с. + operation_timeout = "60m" + + startup_configuration = { + # vDC и Edge из этого же конфига (обязательные поля) + vdc_uid = nubes_vc_vdc.vdc.id + nsxt_uid = nubes_vc_nsxt.edge.id + + cluster_name = var.shturval_cluster_name + + # Дополнительные возможности кластера (в ЛК — галочки при создании) + ex_logging = true # логи в Loki (без него логи услуг не видны в ЛК) + ex_monitoring = true # метрики в VictoriaMetrics (без него метрик в ЛК нет) + ex_local_csi = true + ex_vip = true + ex_update = true + ex_ingress = true + ex_named_csi = true + } + + cluster_configuration = { + app_version = var.shturval_app_version + } + + control_plane_configuration = { + sizing_policy = var.shturval_cp_sizing_policy + sizing_disk = var.shturval_cp_sizing_disk + count = var.shturval_cp_count + } + + worker_configuration = local.shturval_worker_config + + access_configuration = { + need_external_address_api = true # внешний адрес для Kubernetes API (false недопустим) + access_ip_list_api = jsonencode([]) # пусто = доступ всем + need_external_address_ingress = true # внешний адрес для Ingress + access_ip_list_ingress = jsonencode([]) # пусто = доступ всем + } + + # Кластер поднимается только после готовой сети: vDC -> Edge -> внешние IP -> SNAT + depends_on = [nubes_vc_nsxt_snat.snat] +} diff --git a/DEV_STAND/FPipeGmail/terraform.tfvars.example b/DEV_STAND/FPipeGmail/terraform.tfvars.example new file mode 100644 index 0000000..a938d21 --- /dev/null +++ b/DEV_STAND/FPipeGmail/terraform.tfvars.example @@ -0,0 +1,13 @@ +api_token = "ВАШ_ТОКЕН_ИЗ_ЛК" + +# Имя или UUID организации: +organization = "kontora" + +vdc_resource_name = "fullpipe-vdc" +vdc_network_provider = "snb1" +vdc_provider_vdc = "Intel Broadwell 2.4" +vdc_cpu_allocated = 8 +vdc_cpu_guaranteed = 0 +vdc_mem_allocated = 32 + +vdc_storage_config = "[{\"name\":\"SATA\",\"size\":\"200\"}]" diff --git a/DEV_STAND/FPipeGmail/variables.tf b/DEV_STAND/FPipeGmail/variables.tf new file mode 100644 index 0000000..7e934d3 --- /dev/null +++ b/DEV_STAND/FPipeGmail/variables.tf @@ -0,0 +1,116 @@ +variable "api_token" { + type = string + sensitive = true + description = "API-токен Nubes" +} + +variable "api_endpoint" { + type = string + default = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc" + description = "API Gateway URL" +} + +# Имя (display_name, напр. "kontora") ИЛИ UUID организации из ЛК +variable "organization" { + type = string + description = "Имя или UUID организации (vc_org)" +} + +# --- Модификаторы (IP на орге + SNAT на эдже) --- + +variable "ip_space_name" { + type = string + description = "Имя ipSpace, доступное организации (смотреть в ЛК, напр. internet-ipv4-v1)" +} + +variable "ip_count" { + type = string + default = "3" + description = "Сколько внешних IP выделить на организации (count — строка)" +} + +variable "vdc_resource_name" { + type = string + default = "fullpipe-vdc" + description = "Имя VDC" +} + +variable "vdc_network_provider" { + type = string + default = null + description = "Сетевой провайдер. Заполнить значением из текущей страницы ЛК" +} + +variable "vdc_provider_vdc" { + type = string + default = null + description = "Provider VDC. Заполнить значением из текущей страницы ЛК" +} + +variable "vdc_cpu_allocated" { + type = number + default = 8 + description = "vCPU (шт.)" +} + +variable "vdc_cpu_guaranteed" { + type = number + default = 0 + description = "Резервирование vCPU (%, допустимо: 0, 50, 80)" +} + +variable "vdc_mem_allocated" { + type = number + default = 32 + description = "RAM (GB)" +} + +variable "vdc_storage_config" { + type = string + default = "[{\"name\":\"SATA\",\"size\":\"200\"}]" + description = "Дисковое хранилище (JSON-массив, size в GB). Имя политики должно существовать в ресурсном пуле (например, SATA, SSD)" +} + +# --- vc_nsxt (Сетевой шлюз периметра / Edge) --- + +variable "nsxt_resource_name" { + type = string + default = "fullpipe-edge" + description = "Имя Edge (vc_nsxt)" +} + +variable "nsxt_vdc_type" { + type = string + default = "vdc" + description = "Тип родительской услуги: vdc или vdcGroup" +} + +variable "nsxt_need_enable_avi" { + type = bool + default = true + description = "Включить AVI Load Balancer (ALB)" +} + +variable "nsxt_virtual_services_count" { + type = number + default = 3 + description = "Кол-во виртуальных сервисов на AVI (1..4; Штурвал: ≥ 3)" +} + +variable "nsxt_ip_addr_pool" { + type = string + default = "10.10.102.0/24" + description = "Адресный пул routed-сети (маска /24 обязательна)" +} + +variable "nsxt_main_dns" { + type = string + default = "81.22.46.22" + description = "Основной DNS" +} + +variable "nsxt_second_dns" { + type = string + default = "185.247.187.77" + description = "Второй DNS" +} diff --git a/DEV_STAND/FPipeGmail/vdc.tf b/DEV_STAND/FPipeGmail/vdc.tf new file mode 100644 index 0000000..f69039e --- /dev/null +++ b/DEV_STAND/FPipeGmail/vdc.tf @@ -0,0 +1,20 @@ +resource "nubes_vc_vdc" "vdc" { + resource_name = var.vdc_resource_name + + # Организация: имя из ЛК ("kontora") или точный UUID + organization_uid = var.organization + + network_provider = var.vdc_network_provider + provider_vdc = var.vdc_provider_vdc + + cpu_allocated = var.vdc_cpu_allocated + cpu_guaranteed = var.vdc_cpu_guaranteed + mem_allocated = var.vdc_mem_allocated + + # JSON-массив дисковых политик (size в GB) + storage_config = var.vdc_storage_config + + suspend_on_destroy = true + + adopt_existing_on_create = true +} diff --git a/DEV_STAND/FPipeGmail/versions.tf b/DEV_STAND/FPipeGmail/versions.tf new file mode 100644 index 0000000..eca54b9 --- /dev/null +++ b/DEV_STAND/FPipeGmail/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + nubes = { + source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes" + version = "2.0.23" + } + } +} diff --git a/NOTES/40_chat_summaries/CHAT_RESUME_2026-09-25_shturval_freeze_state.md b/NOTES/40_chat_summaries/CHAT_RESUME_2026-09-25_shturval_freeze_state.md new file mode 100644 index 0000000..ffe9327 --- /dev/null +++ b/NOTES/40_chat_summaries/CHAT_RESUME_2026-09-25_shturval_freeze_state.md @@ -0,0 +1,142 @@ +# CHAT RESUME — Штурвал + «freeze on destroy»: состояние на 2026-09-25 + +> Цель файла: начать новый чат **без уточняющих вопросов** — здесь всё, что сделано, где живёт +> документация, что в каком состоянии и что делать дальше. + +## 0. Где что лежит (точки входа) + +| Что | Путь | +|---|---| +| Полный разбор сессии (диагностика, пайплайн, аудит регистра UUID) | `NOTES/30_analysis/SHTURVAL_DEV00_DIAG_AND_FREEZE_DESIGN_2026-09-24.md` | +| Страница для пользователя/DevOps: поведение и отличия от канонического Terraform | `docs/30_registry/guides/provider-behavior.md` | +| Регистр UUID (все нерабочие подходы + кейс UUID внутри JSON, §10) | `docs/60_strategy/terraform_case_sensitivity_fix.md` | +| Запись дня с коммитами и результатами | `HISTORY/2026-09-24_shturval_dev00_adopt_and_freeze_design.md` | +| Память репозитория (факты, шпаргалки, уроки) | `/memories/repo/shturval-destroy-freeze.md` | +| Версии провайдеров по стендам | `VERSIONS.md` | + +Репозиторий: `/home/naeel/TF/tf_provider`, ветка `master`, remote `origin` +(`https://gitea.services.ngcloud.ru/terraform/tf_provider.git`). Снапшот состояния — ветка +`snapshot/2026-09-25-shturval-freeze-state`. + +## 1. Что уже сделано + +1. **Диагностика стенда `shturval-dev-00`** (услуга 150, инстанс `shturval-dev`, uid `94627ff4-…`): + кластер здоров (2 ноды Ready, 41/41 Shturval-сервисов `ready`, NodeConfigItems 4/4, у всех сервисов + есть endpoints). Единственный мусор — 4 подвисших пода `kube-system/shturval-init-job` + (3 Error + 1 Unknown при `Complete 1/1`), причина — webhook-и Штурвала недоступны до готовности Cilium + (`connect: operation not permitted`); самоочистка по `ttlSecondsAfterFinished: 86400` (≈25.09 14:31 UTC). +2. **Расшифрованы счётчики ЛК/Штурвала**: `Pods X/Y` = готовые/всего (без `Completed`); + «Системные сервисы» = число сервисов в режиме `auto`; «Конфигурация узлов» = NodeConfigItems; + «Ingress» = домен-шаблон, не счётчик. +3. **Разобран провал `destroy`**: `nubes_vc_org_ip_allocation` отправлял `count=0`, платформа не даёт + опустить `count` ниже занятых адресов — их держит кластер (`.146` API и `.148` ingress), `suspend` + адреса не освобождает. +4. **Усыновление исправлено**: в `DEV_STAND/FullPipe/shturval.tf` добавлен `adopt_existing_on_create = true` + (коммит `57abb7b`); проверено вживую — apply усыновил существующий инстанс и сам сделал `resume`. +5. **Реализован третий режим destroy** (коммит `22c6c83`, генератор, универсально для всех instance-ресурсов): + `keep_on_destroy` → `state_only` (приоритет), иначе `suspend_on_destroy` → `suspend`, иначе `delete`; + в `Delete` добавлены предупреждения «Ресурс заморожен, а не удалён» / «оставлен как есть». +6. **Конфиг стенда переведён в режим «заморозки»** (коммит `40aef87`): `keep_on_destroy = true` у квоты IP, + SNAT и эджа; `adopt_existing_on_create = true` и `suspend_on_destroy = true` у кластера; у vDC оба флага + уже стояли. +7. **Проверен цикл `destroy` = заморозка** на живом стенде (`2.0.22`): `0 added, 0 changed, 5 destroyed`, + ошибок нет; кластер и vDC → `suspended`, эдж `running` с `ipSpaceName=internet-ipv4-v1`, квота IP `count=3`, + state пуст; в выводе — 5 предупреждений. +8. **Найден и исправлен баг регистра UUID внутри JSON** (коммит `621280a`, релиз `2.0.23`): первый `apply` + после заморозки падал на `required params mismatch … startupConfiguration` (`2c37fed1-…` в плане против + `2C37FED1-…` в живом инстансе). Проведён аудит 8 мест (см. §10 в `terraform_case_sensitivity_fix.md`), + добавлены `jsonutil.LowercaseUUIDsInText` и нормализация строк внутри JSON, `JsonNormalize()` приводит + UUID-подстроки к lowercase; покрыто тестами (`jsonutil_test.go`, `params_compare_test.go`), `go test ./...` зелёный. +9. **Документация**: страница `docs/30_registry/guides/provider-behavior.md`, обновлённый §10 в + `terraform_case_sensitivity_fix.md`, записи в `NOTES`/`HISTORY`, память репозитория. + +## 2. Текущее состояние (на момент записи) + +- `DEV_STAND/FullPipe`: **`terraform state list` пуст** (после проверочного `destroy`). +- В облаке: кластер `shturval-dev-00` — `suspended`; vDC — `suspended`; эдж — `running` + (`ipSpaceName = internet-ipv4-v1`, SNAT включён); квота IP организации — `count = 3`. +- Кластер «спит»: `.146:6443` TCP принимается эджем, но k8s не отвечает (`connection reset by peer`); + `.148:443` открыт (эдж/AVI живут). +- Версия провайдера: в реестре dev — **`2.0.23`**; пины `versions.tf` в `DEV_STAND/FullPipe` и + `DEV_STAND/FPipeGmail` — `2.0.23`. +- Новый (не проверенный вживую) стенд пользователя: `DEV_STAND/FPipeGmail/`. + +## 3. Что делать дальше + +1. **Проверить обратный ход** (запускает только пользователь): `terraform apply` в `DEV_STAND/FullPipe`. + Ожидание: `adopt` по имени + `resume` для кластера и vDC; эдж/SNAT/квота — no-op; затем `plan` = `No changes`. + Проверки: `terraform state list`, `terraform state show`, статусы инстансов в API ЛК, `kubectl get nodes` + (снова Ready), поды `44/48` (+ мусор `shturval-init-job`, уйдёт сам). +2. **Доку при необходимости**: добавить страницу `provider-behavior.md` в `nav` (`mkdocs.yml`) и запустить + `TOOLS/scripts/04_build_and_publish_docs.sh` — **не запускать без прямой команды**. +3. **Открытые техдолги:** + - ref-параметр внутри JSON **не валидируется** при adopt (`resources_core/ref_validation.go`); + - регистр ключей в `lookupLiveParam` (`core/operation_run.go`, `operation_run_bycode.go`) — требует живой проверки; + - тикет в платформу: TTL/очистка Failed-подов установщика + порядок установки компонентов до готовности Cilium; + - тикет в платформу: у Эджа нет операции `suspend` (в `availableOperations` только `delete/modify/reconcile`). +4. **Полный teardown** — осознанно: `keep_on_destroy = false` и `suspend_on_destroy = false`, порядок + кластер → `count=0` → SNAT → эдж → vDC (для vDC действует правило «14 дней после suspend»). + +## 4. Шпаргалка: режимы destroy + +| Ресурс | Флаг | Поведение при destroy | Предупреждение | +|---|---|---|---| +| `nubes_k8s_sthutrval_cluster` | `suspend_on_destroy = true` | `suspend` | «Ресурс заморожен, а не удалён» | +| `nubes_vc_vdc` | `suspend_on_destroy = true` | `suspend` | то же | +| `nubes_vc_nsxt` (эдж) | `keep_on_destroy = true` | не трогается | «Ресурс оставлен как есть, а не удалён» | +| `nubes_vc_nsxt_snat` | `keep_on_destroy = true` | не трогается | «SNAT не выключался» | +| `nubes_vc_org_ip_allocation` | `keep_on_destroy = true` | не трогается | «Аллокация IP не снималась» | + +Приоритет: `keep_on_destroy` > `suspend_on_destroy` > обычное удаление. Дефолты провайдера — разрушающие; +«заморозка» включается в `.tf` стенда. + +## 5. Факты платформы (проверено) + +- 1 кластер Штурвала = 1 vDC; vDC удаляется только через 14 дней после `suspend`. +- Квоту IP нельзя опустить ниже занятых адресов; адреса кластера `suspend` не освобождает. +- У эджа нет `suspend`; удаление эджа при живых зависимых (vApp/VM/кластер) недопустимо. +- API ЛК: `GET /api/v1/svc/instances/{uid}`, состояние — `instance.state.params`, статус — `explainedStatus`, + операции — `availableOperations`; ошибки операции — в теле (`isSuccessful=false`, `errorLog`), HTTP 200/201. +- Обязательные заголовки API ЛК: браузерный `User-Agent`, `Referer: https://deck-dev.ngcloud.ru/`, + `Authorization: Bearer ` — иначе `403`. +- Регистр UUID: облако отдаёт один и тот же UUID в разных регистрах → сравнивать всегда без учёта регистра + (в т.ч. **внутри JSON**). + +## 6. Релизы + +- Схема: prod `1.*`, dev `2.*`, test `3.*`; актуальная dev — `2.0.23` (`VERSIONS.md`). +- Релиз: `TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev <версия>` + (сам прогоняет `01` + `02`, собирает, подписывает, заливает, обновить `VERSIONS.md` вручную). +- Локальная проверка без релиза: `go build -o TMP/devbin/terraform-provider-nubes .` в `provider/` + и `TF_CLI_CONFIG_FILE=TMP/terraformrc.dev terraform validate|plan` (dev_overrides). +- YAML-спеки (`generated/*/resources_yaml/`) **не редактировать руками** — `01_generate_yamls.sh` перезапишет их из API. + +## 7. Правила работы (для нового чата) + +- `terraform apply` / `destroy` — только пользователь. Мне доступны `init/plan/validate/show/state show`. +- Никаких правок, коммитов, релизов и публикаций без прямой команды; после каждой правки — коммит. +- Перед правками важных файлов — бэкап в `TMP/backup_<дата>/`. +- Не «улучшать» соседние стенды/сервисы без команды (scope creep запрещён). + +## 8. Быстрые команды + +```bash +# состояние стенда +cd DEV_STAND/FullPipe && terraform state list && terraform plan +terraform state show nubes_k8s_sthutrval_cluster.shturval | grep -E "id|adopt|suspend|keep" + +# кластер +kubectl get nodes +kubectl get pods -A --no-headers | awk '{split($3,a,"/"); tot++; if($4=="Running"&&a[1]==a[2]) ok++} END{print tot, ok}' +kubectl get pods -A | grep -v -E "Running|Completed" + +# API ЛК (dev) +TOK=$(tr -d '\n' < secrets/narodDEV.token) +curl -s -H "Authorization: Bearer $TOK" \ + -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \ + -H "Referer: https://deck-dev.ngcloud.ru/" \ + 'https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc/instances/94627ff4-33a5-48f2-aca1-695741e0b6a2' + +# реестр провайдера (dev) +curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-dev/nubes/versions +```