Author SHA1 Message Date
Repinoid 034096d16d chore: бэкап FPipeGmail перед добавлением ВМ (без tfvars/tfstate — они в .gitignore) + HISTORY по vpn-transit-213 2026-09-28 08:04:52 +03:00
Repinoid 15cd369ec3 feat(fpipeline): ВМ в пайплайн FPipeGmail — самодостаточный vm.tf (vApp 26 + ВМ 28), внешний IP через общий SNAT, suspend_on_destroy 2026-09-28 07:54:50 +03:00
Repinoid 2fd9ef8aba docs(resume): правки по фактам — ответы по vApp/ВМ (обязательные параметры, suspend, образы, сеть), реальное состояние аккаунтов, исправлено ложное наблюдение про список инстансов (ключ results) 2026-09-27 19:20:22 +03:00
Repinoid ef22e78d8a docs(resume): резюме для нового чата — добавление ВМ (vApp 26 + ВМ 28) в пайплайн; что уже сделано, состояние стендов/облака, точки входа, развилки 2026-09-27 19:17:08 +03:00
Repinoid a366f47eff docs: move diagram artifacts to docs/diagrams, add vApp-IP precondition link and creation guide 2026-09-26 19:30:41 +03:00
Repinoid 6791e8fb5f docs: add styled infrastructure dependency diagram (generator + png + svg) 2026-09-26 19:13:57 +03:00
Repinoid ff38352bfc docs: remove duplicated SNAT label from edge node in flow diagram 2026-09-26 19:06:01 +03:00
Repinoid dc837e8e1a docs: verify cloud service dependency chains against YAML, add VM/Shurval checklists, redraw diagram 2026-09-26 18:49:17 +03:00
Repinoid 223b0ccdca docs: fix compute pool link to originate from vDC 2026-09-26 18:33:01 +03:00
Repinoid ea725bd8b9 docs: add infrastructure services flow diagram (mmd, svg, png) 2026-09-26 08:22:03 +03:00
Repinoid 48009583a6 Add September 25 Terraform backups 2026-09-26 07:20:59 +03:00
Repinoid e72eb75d10 stand(FPipeGmail): свои имена Штурвала — shturval-dev1 / shturval-dev-01 (занятое имя кластера не подошло) 2026-09-25 13:31:33 +03:00
Repinoid 8d25de9e79 docs: страница «Как работает провайдер (отличия от Terraform)» в навигации + ссылка на страницу пайплайна vDC → Edge → IP → SNAT → Штурвал 2026-09-25 09:59:56 +03:00
Repinoid f8d64948c8 docs(curated): страница «vDC → Edge → IP → SNAT → Штурвал» (требования, заморозка, проверка результата) + nav + HISTORY 2026-09-25 09:47:33 +03:00
Repinoid e20c22e0cb stand(FPipeGmail): организация kontra (аккаунт tazetdinovn@gmail.com) вместо устаревшей kontora 2026-09-25 08:53:33 +03:00
46 changed files with 2819 additions and 20 deletions
+2 -2
View File
@@ -22,13 +22,13 @@
variable "shturval_resource_name" {
type = string
default = "shturval-dev"
default = "shturval-dev1"
description = "Имя услуги «Kubernetes кластер Штурвал» в ЛК"
}
variable "shturval_cluster_name" {
type = string
default = "shturval-dev-00"
default = "shturval-dev-01"
description = "Имя кластера внутри Штурвала"
}
@@ -1,7 +1,7 @@
api_token = "ВАШ_ТОКЕН_ИЗ_ЛК"
# Имя или UUID организации:
organization = "kontora"
organization = "kontra"
vdc_resource_name = "fullpipe-vdc"
vdc_network_provider = "snb1"
+183
View File
@@ -0,0 +1,183 @@
# =============================================================================
# Виртуальная машина внутри vApp — услуги 26 (vApp) и 28 (ВМ)
#
# Всё, что относится к ВМ, лежит ТОЛЬКО в этом файле: переменные, их значения
# по умолчанию, оба ресурса и выводы. Чтобы выключить ВМ — удалить или
# закомментировать этот файл (по аналогии с shturval.tf).
#
# Место в цепочке:
# орга (вручную в ЛК) → vDC (21) → Edge (22) → внешние IP → SNAT
# → [ vApp (26) → ВМ (28) ] → Штурвал (150)
#
# Зависимости (из манифестов услуг, сгенерированные ресурсы):
# vApp (26) — nubes_vapp: требует vdc_uid (21) и nsxt_uid (22)
# ВМ (28) — nubes_vc_vm_v3: требует vapp_uid (26)
#
# Внешний доступ: ВМ публикуется за общим SNAT эджа (same_snat = false), для
# этого ipSpace должен быть выделен на организации и включён как SNAT
# (см. modifiers.tf). Нужен ВЫДЕЛЕННЫЙ внешний адрес — same_snat = true.
# ipSpace для ВМ берём тот же, что у SNAT (var.ip_space_name).
#
# Режим destroy: у обеих услуг операция delete требует предварительного
# suspend, поэтому по умолчанию suspend_on_destroy = true («заморозка»).
# Полное удаление vApp возможно только через 14 дней после suspend.
# =============================================================================
# --- Переменные vApp ---
variable "vapp_resource_name" {
type = string
default = "fullpipe-vapp"
description = "Имя услуги «Виртуальный каталог ВМ (vApp)» в ЛК"
}
variable "vapp_name" {
type = string
default = "fullpipe-vapp-01"
description = "Имя vApp. Маска ^[a-z0-9][a-z0-9.-]{3,61}[a-z0-9]$, уникально в организации; участвует в DNS-имени ВМ. НЕ оставлять дефолтом платформы."
}
# --- Переменные ВМ ---
variable "vm_resource_name" {
type = string
default = "fullpipe-vm-01"
description = "Имя услуги «Виртуальная машина» в ЛК"
}
variable "vm_name" {
type = string
default = "web01"
description = "Имя ВМ. Маска ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$. Определяет имя NSX-T IP Set: {vapp_name}-{vm_name}"
}
variable "vm_image" {
type = string
default = "Ubuntu_22-20G"
description = "Образ ОС. Доступные значения: RockyLinux_9-16G-cloudinit, Ubuntu_22-20G, Debian_13-20G. Не изменяется после создания"
}
variable "vm_cpu" {
type = number
default = 2
description = "vCPU (1..64), шт"
}
variable "vm_ram" {
type = number
default = 2
description = "RAM (1..256), GB"
}
variable "vm_disk" {
type = number
default = 20
description = "Дополнительный диск, GB (основной диск зависит от образа)"
}
variable "vm_user_login" {
type = string
default = "ubuntu"
description = "Учётка SSH. Не изменяется после создания"
}
variable "vm_user_public_key" {
type = string
description = "Публичная часть SSH-ключа в формате OpenSSH. Не изменяется после создания. Значение — в terraform.tfvars"
}
variable "vm_access_port_list" {
type = list(object({
port = string
type = string
}))
default = [
{ port = "22", type = "tcp" }
]
description = "Белый список портов для доступа извне; type: tcp | udp | all"
}
variable "vm_access_ip_list" {
type = list(string)
default = ["0.0.0.0/0"]
description = "Белый список адресов, которым разрешён доступ к ВМ. Требует выделенного внешнего IP"
}
variable "vm_same_snat" {
type = bool
default = false
description = "false — публикация за общим SNAT эджа; true — за выделенным внешним IP услуги"
}
# --- vApp (услуга 26) ---
resource "nubes_vapp" "vapp" {
resource_name = var.vapp_resource_name
vapp_name = var.vapp_name
vdc_uid = nubes_vc_vdc.vdc.id # ref 21 — вычислительная инфраструктура
nsxt_uid = nubes_vc_nsxt.edge.id # ref 22 — сеть/маршрутизация
# «Заморозка»: destroy переводит vApp в suspend (delete требует suspend).
suspend_on_destroy = true
# Повторный apply усыновляет существующий vApp, а не падает с
# «РЕСУРС С ТАКИМ ИМЕНЕМ УЖЕ СУЩЕСТВУЕТ».
adopt_existing_on_create = true
# vApp требует готовую сеть (ipSpace на организации + SNAT на эдже).
depends_on = [nubes_vc_nsxt_snat.snat]
}
# --- ВМ (услуга 28) ---
resource "nubes_vc_vm_v3" "vm" {
resource_name = var.vm_resource_name
vm_name = var.vm_name
vapp_uid = nubes_vapp.vapp.id # ref 26 — ВМ размещается в vApp
image_vm = var.vm_image
vm_cpu = var.vm_cpu
vm_ram = var.vm_ram
vm_disk = var.vm_disk
user_login = var.vm_user_login
user_public_key = var.vm_user_public_key
# Внешний доступ
ip_space_name = var.ip_space_name # тот же ipSpace, что у SNAT эджа
same_snat = var.vm_same_snat
access_port_list = jsonencode(var.vm_access_port_list)
access_ip_list = jsonencode(var.vm_access_ip_list)
# «Заморозка»: destroy переводит ВМ в suspend.
suspend_on_destroy = true
adopt_existing_on_create = true
# ВМ создаётся платформой долго — поднимаем таймаут ожидания.
operation_timeout = "15m"
}
# --- Выводы ---
output "vapp_id" {
description = "UID созданного vApp (услуга 26)"
value = nubes_vapp.vapp.id
}
output "vapp_name" {
description = "Имя vApp"
value = nubes_vapp.vapp.vapp_name
}
output "vm_id" {
description = "UID созданной ВМ (услуга 28)"
value = nubes_vc_vm_v3.vm.id
}
output "vm_state_flat" {
description = "Плоский state ВМ — IP-адреса, статус и т.д."
value = nubes_vc_vm_v3.vm.state_out_flat
}
@@ -0,0 +1,43 @@
# 2026-09-25 — Штурвал в примере `fullpipe_chain` + страница документации
## Что сделано
Примеры (`tf_examples`, отдельный репозиторий `https://gitea.services.ngcloud.ru/Nail/tf_examples.git`)
и страница документации приведены к рабочей конфигурации стенда `DEV_STAND/FullPipe`
(аккаунт `tazet@narod.ru`) — теперь цепочка полная: **vDC → Edge → внешние IP → SNAT → Штурвал**.
| Файл | Изменение |
|---|---|
| `tf_examples/fullpipe_chain/shturval.tf` | **новый**: все настройки Штурвала в одном файле (переменные + `locals` + ресурс `nubes_k8s_sthutrval_cluster`), как в рабочем стенде |
| `tf_examples/fullpipe_chain/versions.tf` | провайдер `2.0.21` → `2.0.23` (последняя dev) |
| `tf_examples/fullpipe_chain/edge.tf` | `keep_on_destroy = true`, `adopt_existing_on_create = true` |
| `tf_examples/fullpipe_chain/modifiers.tf` | `keep_on_destroy = true` у квоты IP и SNAT (было `false`) |
| `tf_examples/fullpipe_chain/outputs.tf` | выводы Штурвала: `shturval_id`, `shturval_name`, `shturval_state_params` |
| `tf_examples/fullpipe_chain/terraform.tfvars.example` | блок параметров Штурвала (закомментированные значения = рабочие default) |
| `tf_examples/fullpipe_chain/README.md`, `tf_examples/README.md` | цепочка со Штурвалом, 5 ресурсов, требования, таблица «заморозки», состав файлов |
| `docs/curated/pipeline/vdc_edge_ip_snat.md` | переписан: требования, чек-лист услуги 150 (ALB + AVI ≥ 3, IP ≥ 3), проверка результата (адреса API/Ingress), «заморозка» при destroy, полное удаление |
| `mkdocs.yml` | заголовок в nav: «Пайплайн vDC → Edge → IP → SNAT → Штурвал» |
## Проверки
- `terraform init` + `terraform validate` + `terraform fmt -check` на копии примера в `/tmp` — без ошибок.
- `terraform plan` (копия в `/tmp`, организация `kontra`, токен `secrets/dev.token`) — `5 to add, 0 change, 0 destroy`, ошибок нет.
- Копия для проверки делалась в `/tmp`, **не** в `tf_examples/`: там нет `.gitignore` для `.terraform/`, и служебные файлы уехали бы в публичный репозиторий.
## Факты и правила, подтверждённые по ходу
- Все настройки Штурвала держим **в одном файле** `shturval.tf` (переменные + ресурс): чтобы выключить Штурвал — удалить файл.
- Порядок из чек-листа услуги 150: организация (вручную в ЛК) → vDC → Edge (ALB, AVI VS ≥ 3) →
внешние IP (≥ 3) → SNAT → кластер. Минимум ноды: 1 + 1 по 4 vCPU / 8 ГБ / 50 ГБ.
- `worker_configuration` — JSON-строка с **camelCase**-ключами (`groupName`…): snake_case валит платформу
(«Cannot invoke method size.split() on null object»).
- «Заморозка»: `keep_on_destroy` важнее `suspend_on_destroy`; у Edge операции `suspend` нет вообще.
- Публикация доков: `TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/dev <версия>`
(версию надо передавать аргументом — в `profile.env` она отстаёт);
сборка локальным mkdocs (docker на этой машине недоступен).
## Ошибка в работе (зафиксировано)
При первой проверке стенда я вывел в терминал содержимое `DEV_STAND/FullPipe/terraform.tfvars` —
файл содержит живой `api_token`. В git файл не попадает (`*.tfvars` в `.gitignore`), но токен оказался
в логе вывода. Правило: секреты из `.tfvars` не печатать, сравнивать по хешу/маскировать.
@@ -0,0 +1,96 @@
# VPN transit via VM 213 and Vultr
Date: 2026-09-27 to 2026-09-28
## Goal
Provide access from Russian residential/mobile networks to services restricted by Russian network filtering, while retaining the existing foreign egress on Vultr.
## Verified network facts
- Test host `3060`: `46.39.251.163`, connection from Khimki / Iskratelecom.
- Transit VM `213`: `5.172.178.213`, public egress observed as `5.172.178.65`; hosted in NUBES data centre.
- Vultr addresses: primary `95.179.252.111`; secondary `104.238.177.67`.
- `3060 -> 213`: ICMP approximately 3 ms, 0% loss.
- `213 -> Vultr`: ICMP approximately 34 ms, 0% loss; HTTPS response returned in about 0.07-0.11 s.
- Direct `213 -> Vultr` test file transfer: 10 MiB in 1.59 s, about 6.27 MiB/s / 50.2 Mbit/s.
- Direct `3060 -> Vultr` test file transfer timed out / was throttled.
- Direct `213 -> OVH proof endpoint`: 10 MiB in 1.18 s, about 8.5 MiB/s.
- Direct access from `213` to YouTube and Telegram failed with `HTTP=000` and timeout/SSL errors, while OVH and Google returned HTTP 200. Therefore a foreign egress remains required for those services.
## Persistent changes on VM 213
- Created backup:
- `/etc/nginx/sites-available/check.kube5s.ru.bak_vpn`
- Modified:
- `/etc/nginx/sites-available/check.kube5s.ru`
- Added an Nginx `/ws` reverse-proxy location with:
- upstream `https://95.179.252.111:443`
- SNI `vipien.kube5s.ru`
- upstream Host header `vipien.kube5s.ru`
- WebSocket upgrade headers
- 3600-second proxy timeouts
- Ran `nginx -t` successfully and reloaded Nginx.
- Existing unrelated Nginx warnings about duplicate `contracts.kube5s.ru` server names remained.
## Persistent/previously existing changes on Vultr
The following configuration was read or used during validation:
- `/etc/nginx/conf.d/vipien.conf`: TLS/WebSocket endpoint for `vipien.kube5s.ru`.
- `/etc/v2ray-agent/xray/conf/08_VLESS_ws_inbound.json`: VLESS WebSocket inbound on `127.0.0.1:10086`, path `/ws`.
- `/etc/systemd/system/hysteria-server.service`: Hysteria service was stopped and disabled; it was not changed in this work.
- Xray service was confirmed active.
- Nginx service was confirmed active.
- Cloudflared tunnel configuration was inspected earlier, but it is not used by the final working route.
- A temporary 10 MiB test file was created on Vultr and removed after testing.
## Temporary files on test VM 3060
The following temporary client files were created under `/tmp/xray-test/` for validation and are not repository files:
- `client-cf.json`
- `client-213.json`
- `client-directip.json`
- temporary log/test artifacts where applicable
The files contained test Xray client configurations. They were used only to verify the route from `3060`; no permanent system service was installed there.
## Final tested route
`client in Russia -> 5.172.178.213:443 -> Nginx WebSocket proxy -> 95.179.252.111:443 -> Xray -> Internet`
Final test from `3060` through the route:
- observed outbound IP: `95.179.252.111`
- 10 MiB OVH download: 1.76-1.91 s
- measured speed: approximately 5.5-6.0 MiB/s
## Final client parameters
- Address: `5.172.178.213`
- Port: `443`
- UUID: existing UUID used by the Vultr Xray inbound
- TLS SNI: `check.kube5s.ru`
- WebSocket path: `/ws`
- WebSocket Host: `vipien.kube5s.ru`
The final direct-IP test used Xray 26.3.27. The client-side `allowInsecure` option was not used because this Xray version reports that the option was removed.
## Secondary Vultr IP
Before removal, the Nginx upstream on VM 213 was switched from `104.238.177.67` to `95.179.252.111`. A post-switch end-to-end test succeeded, with outbound IP `95.179.252.111` and approximately 6.0 MiB/s.
No Vultr IP deletion was performed in this work. The secondary address was only confirmed as no longer referenced by the transit configuration.
## Scope audit
- No repository source/configuration files were edited before this record.
- `git status` was clean before this documentation file was created.
- This documentation file is the only workspace file created by the current documentation action.
- Server-side files were changed on VM 213 and earlier on Vultr; temporary test files were also created on VM 3060.
- No commit was created for this record.
## Important limitations
The measurements prove the route worked at test time. They do not guarantee permanent availability: NUBES, Vultr, upstream providers, or network filtering policy can change independently.
@@ -0,0 +1,257 @@
# CHAT RESUME — добавляем ВМ в пайплайн (состояние на 2026-09-27)
> Цель файла: новый чат стартует **без уточняющих вопросов** — здесь всё, что уже сделано,
> что где лежит, что проверено на живых стендах и что решать по ВМ.
> Репозиторий: `/home/naeel/TF/tf_provider`, ветка `master`.
> Remote: `https://gitea.services.ngcloud.ru/terraform/tf_provider.git`.
> Репозиторий примеров: `tf_examples/` (отдельный git) → `https://gitea.services.ngcloud.ru/Nail/tf_examples.git`.
---
## 0. Где что лежит (точки входа)
| Тема | Путь |
|---|---|
| Предыдущее резюме (freeze/adopt, состояние Штурвала) | `NOTES/40_chat_summaries/CHAT_RESUME_2026-09-25_shturval_freeze_state.md` |
| Разбор диагностики + дизайн «заморозки» | `NOTES/30_analysis/SHTURVAL_DEV00_DIAG_AND_FREEZE_DESIGN_2026-09-24.md` |
| Зависимости сервисов DEV + чек-листы Штурвал/ВМ | `docs/60_strategy/dev_stand_service_dependencies.md` |
| Диаграмма зависимостей + генератор | `docs/diagrams/README.md`, `docs/diagrams/infra_services_flow.mmd`, `render_infra_diagram.py` |
| Страница для пользователя: поведение провайдера | `docs/30_registry/guides/provider-behavior.md` |
| Страница-инструкция пайплайна | `docs/curated/pipeline/vdc_edge_ip_snat.md` |
| Пример в репе примеров | `tf_examples/fullpipe_chain/` (в т.ч. `shturval.tf`) |
| Регламент публикации доков | `DOCS_PIPELINE/README.md`, `TOOLS/scripts/04_build_and_publish_docs.sh`, `scripts/publish-docs.sh` |
| Инструкции «как сделать руками» | `HOW_TO/README.md` (индекс), `HOW_TO/howitwasdone.md`, `HOW_TO/DEVOPS_BUILD_PIPELINE.md` |
| Версии провайдеров по стендам (источник правды) | `VERSIONS.md` |
| Память (репозиторий) | `/memories/repo/shturval-destroy-freeze.md`, `/memories/repo/registry-versions.md` |
| **ВМ: старые материалы** | `docs/20_discovery/vm_service.md`, `vm-ui-verification.md`, `vm_provisioning_failures.md`, `vapp-service.md`, `docs/40_analysis/vm_creation_failure_analysis.md`, `docs/50_history/04_vm_hang_fix_and_500_error.md`, `docs/50_history/12_vm_resource_hardening.md`, `docs/50_history/23_vapp_uid_inconsistency_displayname_resolve_bug.md` |
| **ВМ: старое резюме (ЛЕГАСИ)** | `NOTES/40_chat_summaries/CHAT_RESUME_PLAN_VM.md` — другой репозиторий (`/home/naeel/terra`), ресурс `nubes_vm`, пути неактуальны; только как история |
| **ВМ: спеки услуг** | `generated/dev/resources_yaml/26_vapp.yaml` (услуга 26, vApp), `generated/dev/resources_yaml/28_vc_vm_v3.yaml` (услуга 28, ВМ) |
| Референс от платформы (Cloud Director) | каталог `!/` в корне: `vmware_org.tf`, `vdc.tf`, `network.tf.tmpl` (цепочка ipSpace → providerGateway → providerVdc) |
| Стенды | `DEV_STAND/FullPipe/` (проверенный, аккаунт `tazet@narod.ru`), `DEV_STAND/FPipeGmail/` (аккаунт `tazetdinovn@gmail.com`, организация `kontra`) |
---
## 1. Что уже сделано (только факты, по коммитам)
**21–24.09 — ресурсы-модификаторы и релизы 2.0.18 → 2.0.23**
- `nubes_vc_org_ip_allocation` (modify `vIPConfigure`) и `nubes_vc_nsxt_snat` (modify `ipSpaceName`, обратный `no-needed`) — генератор + код + тесты (`22cf259`, `80d82a1`, `73a7459`, `4b497e6`, `ba6c4f5`, `1236c59`).
- Ресурс аллокации принимает **имя** организации (резолв в UUID) — `5bd197f`.
- Убран plan-modifier, менявший пользовательское значение (Terraform: planned value must match config) — `807dfde`.
**24.09 — Штурвал: adopt + «заморозка»**
- Всё про Штурвал собрано в один файл `shturval.tf` (`b8adeb6`); правильный сервис — **150**, не 148 (`1a90c77`, `d98f603`).
- `worker_configuration` — JSON с **camelCase** (`groupName`, `sizingPolicy`, `sizingDisk`, `labelDeck`) — snake_case валит платформу (`72d5771`).
- `adopt_existing_on_create = true` для кластера (`57abb7b`).
- Реализован **третий режим destroy** `keep_on_destroy` → `state_only` для всех instance-ресурсов + предупреждения в Delete (`22c6c83`), релиз **2.0.22** (`c29df21`).
- Конфиг стенда переведён в «заморозку» (`40aef87`): квота IP/SNAT/эдж — `keep_on_destroy = true`, кластер — `suspend_on_destroy = true`, у vDC — `suspend` + adopt.
- Живая проверка `destroy` = заморозка: `5 destroyed`, кластер и vDC → `suspended`, эдж/SNAT/квота не тронуты (`c9d7345`).
**24.09 — фикс регистра UUID, релиз 2.0.23**
- Adopt suspended-инстанса падал на регистре UUID внутри JSON (`startupConfiguration`: план `2c37fed1-…` против живого `2C37FED1-…`). Добавлены `jsonutil.LowercaseUUIDsInText`, нормализация строк внутри JSON, `JsonNormalize()` приводит UUID-подстроки к lowercase; покрыто тестами (`621280a`), релиз **2.0.23** (`208d97e`, `03fff05`).
**24–25.09 — документация**
- Новая страница «Как работает провайдер Nubes: поведение и отличия от канонического Terraform» (`aaf87d9`, читаемость `c808a3b`): §1 суть, §2 ресурсы, §3 отличия от Terraform, §4 CRUD, §5 delete/suspend/state_only, **§6 конкретный пайплайн стенда vDC → Edge → IP → SNAT → Штурвал**, §7 регистр UUID, §8 чек-лист DevOps, §9 FAQ, §10 ссылки.
- Страница пайплайна переписана (`f8d6494`): требования, чек-лист услуги 150 (ALB + AVI ≥ 3, внешних IP ≥ 3), проверка результата (адреса API/Ingress), таблица «заморозки» при destroy, полное удаление, состав файлов.
- `provider-behavior.md` добавлена в навигацию `mkdocs.yml` («Руководства → Как работает провайдер (отличия от Terraform)») и связана ссылкой со страницей пайплайна (`8d25de9`).
**25.09 — стенд второго пользователя `FPipeGmail`**
- Организация `kontra` (аккаунт `tazetdinovn@gmail.com`, компания `naeel_test`, ClientID `WZ03709`), токен — `secrets/dev.token`; в `.tfvars` было закомментировано устаревшее `kontora` (`e20c22e`).
- Имена Штурвала сделаны свои: `shturval-dev1` / `shturval-dev-01` — занятое имя кластера (даже созданное другим пользователем) платформа не принимает (`e72eb75`).
- Проверено на 25.09: `terraform init` (провайдер 2.0.23), `validate` — ок, `plan` = `5 to add, 0 change, 0 destroy`. **apply не запускался** (запускает только пользователь).
**25.09 — репозиторий примеров `tf_examples`**
- `fullpipe_chain`: добавлен `shturval.tf` (переменные + locals + ресурс, самодостаточно), пин провайдера `2.0.21 → 2.0.23`, флаги «заморозки» как в рабочем стенде (эдж/SNAT/квота `keep_on_destroy = true`, vDC `suspend` + adopt), выводы Штурвала, блок параметров в `terraform.tfvars.example`, переписаны README примера и корневой (`21618a7`); добавлено предупреждение про уникальность имени кластера (`435a685`).
- Всё запушено: последний коммит примеров — `435a685`.
**26.09 (не в этой сессии, но уже в `master` — чтобы не переспрашивать)**
- Диаграмма зависимостей сервисов: `docs/diagrams/*` (`ea725bd`, `ff38352`, `6791e8f`), генератор `render_infra_diagram.py`, README с инструкцией перегенерации.
- Сверка цепочек зависимостей с YAML + чек-листы Штурвала и ВМ: `docs/60_strategy/dev_stand_service_dependencies.md` (`dc837e8`), перенос артефактов диаграмм + гайд по созданию vApp (`a366f47`).
- Закоммичены бэкапы от 25.09: `TMP/backup_2026-09-25/**` (`4800958`).
---
## 2. Текущее состояние (проверено 2026-09-27)
**Terraform**
- `DEV_STAND/FullPipe`: `terraform state list` — **пусто**.
- `DEV_STAND/FPipeGmail`: `terraform state list` — **пусто**; при этом конфиг валиден, `plan` = `5 to add` (по состоянию на 25.09).
- Terraform `1.9.5`; `go build ./...` и `go test ./...` — чисто (проверено 25.09).
**Облако, аккаунт `tazet@narod.ru` (токен `secrets/narodDEV.token`)**
| Услуга | Имя | UID | Статус |
|---|---|---|---|
| 150 Штурвал | `shturval-dev` | `94627ff4-33a5-48f2-aca1-695741e0b6a2` | `suspended` |
| 21 vDC | `fullpipe-vdc` | `d0937335-276b-475b-baa4-d8e6d16bad51` | `suspended` |
| 22 Edge | `fullpipe-edge` | `2c37fed1-e8f8-4a84-8434-7851c7c8b5d6` | `running`, SNAT `internet-ipv4-v1` |
| 19 Организация | `organ` | `57eeacd1-dc7f-4a52-b903-7e5f7d3c1164` | `running`, `vIPConfigure` = `[{internet-ipv4-v1, count: 3}]` |
Всего в аккаунте 13 инстансов, в том числе: `dummy-5`/`dummy-6` (1), `External ip` и `vIp` (25, `running`), `A-record` (111, `running`), инстансы mgmt-кластера (21/22, `deleted`). Из 3 инстансов `fullpipe-edge` — 2 `deleted` (история), 1 `running` — не пугаться.
**Облако, аккаунт `tazetdinovn@gmail.com` (токен `secrets/dev.token`, срок до 2026-12-27)**
- Организация `kontra` (`df5ec5f2-5f5d-4c82-952c-3dffa91c61d3`, услуга 19): `running`, `isTrial`, создана 2026-09-24 08:20, `vIPConfigure` = `count: 3`, realm `sandbox.nubes.ru`.
- В этом аккаунте **уже созданы объекты стенда FPipeGmail**: `fullpipe-vdc` (21) — `suspended`, `fullpipe-edge` (22) — `running`, `shturval-dev1` (150) — `suspended` (локальный `state` при этом пуст → следующий `apply` усыновит их по имени и разморозит). Всего 12 инстансов, плюс `kontra` (19), S3-бакеты (13), `s3naeeldev` (12), `mariamdb` (115), `vc_v`/`vc_vdc-1`/`tedj` — `deleted`.
- ⛔ **Исправление ранее ложного наблюдения** (в первой версии этого резюме было «список отдаёт 0 инстансов»): это была **ошибка парсинга ответа с моей стороны** — API отдаёт список в ключе `results`, а не `instances`. Оба аккаунта отдают данные корректно (narod — 13, gmail — 12). Никакой аномалии платформы нет.
**Провайдер / реестр / доки**
- DEV в реестре: **`2.0.23`** (последняя), пины `2.0.23` в `DEV_STAND/FullPipe/versions.tf`, `DEV_STAND/FPipeGmail/versions.tf`, `tf_examples/fullpipe_chain/versions.tf`, `.terraform.lock.hcl` FullPipe.
- PROD `1.0.0`, TEST `3.0.0` (новая схема: prod `1.*`, dev `2.*`, test `3.*`).
- Доки dev опубликованы (`2.0.23`) и зеркало на ВМ обновлено; живые URL:
- https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/curated/pipeline/vdc_edge_ip_snat/ (в меню: «Проверенные примеры»)
- https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/30_registry/guides/provider-behavior/ (в меню: «Руководства»)
**Git**
- Основной репо: `master` = `origin/master` = `e72eb75`; примеры: `435a685` = `origin/master`.
- В истории уже лежат коммиты от 26.09 (диаграммы, чек-листы, бэкапы) — см. §1.
---
## 3. Что делать дальше: **добавляем ВМ в пайплайн** (главная цель нового чата)
**Целевая цепочка (текущая + ВМ):**
организация (руками в ЛК) → vDC (21) → Edge (22) → внешние IP (modify) → SNAT (modify) → **[vApp (26) → ВМ (28)]** → Штурвал (150)
**Подтверждённые факты (сверено с YAML, коммит `dc837e8`)**
- D-06: vApp (26) → Edge (22), параметр `nsxtUid` — vApp использует Edge для сети.
- D-07: vApp (26) → vDC (21), параметр `vdcUid`.
- D-08: ВМ (28) → vApp (26), параметр `vappUid` — ВМ размещается в vApp.
- ВМ можно создавать в **уже существующих** vApp / vDC / Edge — ссылками `vappUid` / `vdcUid` / `nsxtUid`.
- Ресурсы (CPU/RAM/Storage) ВМ: `vmCpu`, `vmRam`, `vmDisk` (доп. диск); квоты выделяются на уровне vDC (21) и услуги ВМ (28).
- Чек-лист создания ВМ (28) по create-параметрам, обязательные: `vappUid`, `vmName`, `vmCpu`, `vmRam`, `accessPortList`, `imageVm`, `userLogin`, `userPublicKey`, `ipSpaceName`. Полный текст — `docs/60_strategy/dev_stand_service_dependencies.md` (§«Чек-лист создания ВМ (28)»).
- Спеки ресурсов: `generated/dev/resources_yaml/26_vapp.yaml`, `28_vc_vm_v3.yaml` (**руками не править** — перезапишет `01_generate_yamls.sh`).
- Имена ресурсов в провайдере: `nubes_vc_vapp`, `nubes_vc_vm_v3` (услуга 148 «Менеджмент Kubernetes Штурвал» — **не** то, что нужно; для кластера только 150).
**Известные проблемы ВМ (читать до работ)**
- `docs/20_discovery/vm_provisioning_failures.md` — падения на создании, разбор payload Terraform vs ручного запроса.
- `docs/40_analysis/vm_creation_failure_analysis.md`, `docs/50_history/04_vm_hang_fix_and_500_error.md` — зависание на FW и 500 («String[] → GUID»).
- `docs/50_history/12_vm_resource_hardening.md` — правки по надёжности ресурса ВМ.
- `docs/50_history/23_vapp_uid_inconsistency_displayname_resolve_bug.md` — резолв по имени мог вернуть **удалённый** инстанс (лечится фильтром `isDeleted=false`); проверить, что фикс жив в 2.0.23.
**Что уже выяснено фактами (проверено 27.09 — чтобы не гадать)**
- vApp (26), обязательные параметры create: `vappName`, `vdcUid`, `nsxtUid`. Операции: `create`, `delete`, `suspend`, `resume`; в YAML — `suspend_on_destroy_default: true`. Из инструкции услуги: `delete` требует **предварительного `suspend`**, полное удаление — через 14 дней. Отдельных параметров сети/подсети/gateway у vApp нет — сеть даёт Edge (`nsxtUid`).
- ВМ (28), обязательные параметры create: `vappUid`, `vmName`, `vmCpu`, `vmRam`, `ipSpaceName`, `imageVm`, `userLogin`, `userPublicKey`, `accessPortList` (`vmDisk` — дополнительный диск, не обязателен). Операции: `create`, `delete`, `modify`, `reconcile`, `redeploy`, `resume`, `suspend`; в YAML — `suspend_on_destroy_default: true`. В modify-наборе есть параметр `sameSnat` (разобраться, что даёт).
- `ipSpaceName` у ВМ — «Public IP setting», допускает значение `no-needed` (без публичного адреса). Если ВМ нужен внешний IP, квоту организации придётся увеличивать: сейчас `count = 3` и все три держит кластер Штурвала, а `suspend` адреса не освобождает.
- Соответствие кодов и ID параметров ВМ (`docs/20_discovery/vm_service.md`): `vappUid` 407, `vmName` 408, `vmCpu` 409/493, `vmRam` 410/494, `vmDisk` 411/495, `ipSpaceName` 412/496, `accessIpList` 413/497, `imageVm` 414, `cloudInit` 415, `userLogin` 416, `userPublicKey` 417, `accessPortList` 448/498, `needAddZabbixTemplate` 449/499.
- **Список образов (`imageVm`) в репозитории отсутствует**: в документации зафиксирован только пример `Ubuntu_22-20G`; HAR с созданием ВМ (`vmOK.har`, `vmsuspendresumemodify.har`) были в старом репо `/home/naeel/terra` — в этом репо в `HAR/` их нет (там `org*`, `vdc`, `edge_`, `ipSpace0`, `pgmodify`, `dummy`, `globak`). Значит: смотреть список в ЛК либо снять свежий HAR.
- Живых инстансов vApp (26) и ВМ (28) сейчас **нет ни в одном аккаунте** → `availableOperations` по ним снять не с чего, проверять после первого создания.
- Наличие `suspend` у обоих сервисов означает, что режим «заморозка» (`suspend_on_destroy` / `keep_on_destroy`) у них применим — как у кластера и vDC.
**Что предстоит решить (НЕ выбрано, требует решения пользователя)**
1. Где создавать vApp: в `DEV_STAND/FullPipe` (проверенный стенд, аккаунт `narod`) или отдельным файлом-аналогом `shturval.tf` (например `vm.tf`) — по сложившейся конвенции «всё про сервис в одном файле».
2. Очерёдность: ВМ до Штурвала или после (влияет и на внешние адреса, и на время apply).
3. `imageVm` — откуда брать список образов (в текущих заметках не зафиксировано) → проверять в ЛК/HAR.
4. `ipSpaceName` для ВМ — тот же `internet-ipv4-v1` или другой; хватит ли квоты (сейчас `count = 3`, занято кластером).
5. `userPublicKey` — какой ключ использовать (в репо есть `secrets/id_ed25519.pub`).
6. Режим destroy для vApp и ВМ (есть ли `suspend` у 26/28 — проверить `availableOperations` на живом инстансе перед выбором флагов).
7. Обновить после работ: `docs/curated/pipeline/vdc_edge_ip_snat.md` (или новую страницу), `tf_examples/fullpipe_chain` (файл ВМ + README + выводы), `docs/60_strategy/dev_stand_service_dependencies.md`, диаграмму `docs/diagrams/*` (перегенерировать `render_infra_diagram.py`).
**Правило порядка (из инструкций ЛК):** платформа строит список ipSpace из состояния `job.vcd.networkProvider` / `providerGateway`, то есть аллокация IP и SNAT идут **после** готовых vDC и Edge; иначе modify падает с «Can't cast Complex Object Type Struct to String».
---
## 4. Факты платформы (проверено, не перепроверять)
- 1 кластер Штурвала = 1 vDC; vDC удаляется только через **14 дней** после `suspend`.
- Квоту IP нельзя опустить ниже занятых адресов; `suspend` кластера адреса **не** освобождает.
- У Edge операции `suspend` нет вообще: доступны `delete`, `modify`, `reconcile`.
- Доступные операции: организация (19) — `delete`, `suspend`, `resume`, `modify`, `reconcile`, `create_user`, `delete_user`; vDC (21) — `delete`, `modify`, `suspend`, `resume`, `reconcile`; Штурвал (150) — то же + `create_user`, `delete_user`.
- API ЛК (dev): `GET /api/v1/svc/instances/{uid}`; поля: `explainedStatus`, `state.isSuspended`, `state.isDeleted`, `state.params`, `availableOperations`; ошибки операций приходят **в теле** (`isSuccessful=false`, `errorLog`) при HTTP 200/201.
- Обязательные заголовки: браузерный `User-Agent`, `Referer: https://deck-dev.ngcloud.ru/`, `Authorization: Bearer <токен>`; иначе 403.
- Регистр UUID: облако отдаёт один и тот же UUID в разных регистрах → сравнивать **без учёта регистра, в том числе внутри JSON** (учтено в 2.0.23).
- Имя кластера Штурвала должно быть **уникальным**: занятое (даже другим пользователем) не подойдёт; имя услуги и имя кластера — свои у каждого пользователя.
- `plan` не видит существующие объекты: adopt/конфликт проверяются в `Create` (в плане всегда «will be created»).
---
## 5. Шпаргалка: режимы 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` > обычное удаление. Дефолты генератора: `suspend_on_destroy = true` (для сервисов с операцией `suspend`), `keep_on_destroy = false`, `adopt_existing_on_create = false`. Обратный ход: `apply` усыновляет объект по имени и делает `resume`.
---
## 6. Релизы и публикация (команды)
```bash
# Релиз провайдера (dev)
TOOLS/scripts/03_build_and_upload_provider.sh --profile TOOLS/config/dev <версия>
# → сам прогоняет 01 (YAML) + 02 (Go/доки), собирает, подписывает, заливает в реестр
# → VERSIONS.md обновить вручную
# Локальная проверка без релиза
cd provider && go build -o ../TMP/devbin/terraform-provider-nubes .
TF_CLI_CONFIG_FILE=TMP/terraformrc.dev terraform validate|plan # dev_overrides
# Документация: собрать и залить в S3
TOOLS/scripts/04_build_and_publish_docs.sh --profile TOOLS/config/dev 2.0.23
# ⚠️ версию передавать АРГУМЕНТОМ: в TOOLS/config/dev/profile.env всё ещё VERSION="2.0.17"
# docker недоступен → сборка идёт локальным mkdocs 1.6.1 + mkdocs-material
# Довезти до домена (сейчас обязательно руками: с ВМ нет маршрута до S3)
tar -C site -cf - . | ssh vps 'rm -rf ~/tmp-docs-site && mkdir -p ~/tmp-docs-site && tar -C ~/tmp-docs-site -xf -'
ssh vps 'rsync -a --delete ~/tmp-docs-site/ /var/www/tf-docs/nubes-dev/'
# vps = naeel@5.172.178.213 (алиас в ~/.ssh/config, ключ ~/.ssh/naeel_vm_id_ed25519)
# путь из secrets/HOW_TO_SSH.md устарел (файла /home/naeel/remote_dev/... нет)
# Примеры
cd tf_examples && git add -A && git commit -m "..." && git push origin master
```
Проверка доков: `curl -s -o /dev/null -w "%{http_code}" <URL>` — должно быть 200; содержимое локальной сборки лежит в `site/` (каталог в `.gitignore`).
---
## 7. Правила работы (не нарушать)
- `terraform apply` и `destroy` — **только пользователь**. Мне доступны `init/plan/validate/show/state list/state show`.
- Никаких правок, коммитов, релизов и публикаций без прямой команды; команда непонятна — **спросить, не гадать**.
- После каждой правки — коммит с осмысленным сообщением; перед правкой важных файлов — бэкап в `TMP/backup_<дата>/`.
- Секреты (`terraform.tfvars`, токены) **не печатать** в терминал и не коммитить (`*.tfvars` в `.gitignore`); сравнивать по хешу/маскировать.
- Не расширять область задач (scope creep), не «улучшать» соседние стенды и репозитории.
- Не зацикливаться: два вызова инструмента с близкими аргументами без результата — стоп и доклад.
- Проверка примера — на копии в `/tmp`, не в `tf_examples/` (там нет gitignore для `.terraform/`, служебные файлы уехали бы в публичный репозиторий).
---
## 8. Быстрые команды
```bash
# состояние стендов
cd DEV_STAND/FullPipe && terraform state list && terraform plan
cd DEV_STAND/FPipeGmail && terraform state list && terraform plan
# кластер (когда running)
kubectl config current-context # tazet@narod.ru@shturval-dev-00
kubectl get nodes
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?page=1&size=200'
# реестр провайдера (dev)
curl -s https://tf-registry.containerk8s.services.ngcloud.ru/v1/providers/nubes-dev/nubes/versions
# услуги каталога: 19 орга, 21 vDC, 22 Edge/SNAT, 26 vApp, 28 ВМ, 150 Штурвал, 148 — не то
```
---
## 9. Открытые вопросы и техдолги
1. Ref-параметр **внутри JSON** не валидируется при adopt — `provider/internal/resources_core/ref_validation.go`.
2. Регистр ключей в `lookupLiveParam` — `provider/internal/core/operation_cfs.go:38` (в прежних заметках был указан неверный файл); нужна живая проверка.
3. Тикеты в платформу: TTL/очистка Failed-подов установщика Штурвала (порядок установки компонентов до готовности Cilium); у Edge нет операции `suspend`.
4. `TOOLS/config/dev/profile.env`: `VERSION="2.0.17"` — конфиг отстаёт от реестра (`2.0.23`); правка конфига без команды не делалась.
5. `DEV_STAND/FPipeGmail`: локальный `state` пуст, но объекты в облаке **уже есть** (`fullpipe-vdc` suspended, `fullpipe-edge` running, `shturval-dev1` suspended) — при применении Terraform их усыновит.
6. `DEV_STAND/FPipeGmail` — применять или нет (сейчас `plan` = 5 ресурсов, `state` пуст) — решение за пользователем.
+6 -3
View File
@@ -7,7 +7,10 @@
| Файл | Тема | Статус |
|---|---|---|
| `CHAT_RESUME_IAC_2026-09-24.md` | **Текущая линия: IaC + `modify` (Штурвал).** Задача, позиции участников (Георгий/Дмитрий/Виталий), 10 подтверждённых фактов, почему не работает простое, каноничное решение, развилка A–E, **исправленные ошибки**, карта файлов, открытые вопросы | ✅ **Актуально — начинать отсюда** |
| `CHAT_RESUME_2026-09-27_vm_in_pipeline.md` | **Актуальная линия: добавляем ВМ (услуги 26 vApp + 28 ВМ) в пайплайн.** Всё сделанное (freeze/adopt, 2.0.19→2.0.23, пример, доки), состояние стендов и облака на 27.09, точки входа по ВМ, известные проблемы, развилки | ✅ **Актуально — начинать отсюда** |
| `CHAT_RESUME_2026-09-25_shturval_freeze_state.md` | Состояние Штурвал/freeze на 25.09: диагностика стенда, режимы destroy, релиз 2.0.23, схема публикаций | ⚠️ История (всё вошло в резюме от 27.09) |
| `CHAT_RESUME_2026-09-24_shturval_freeze.md` | Adopt кластера + дизайн freeze-on-destroy | ⚠️ История |
| `CHAT_RESUME_IAC_2026-09-24.md` | Линия IaC + `modify` (Штурвал): факты, развилка A–E, карта файлов | ⚠️ История |
| `CHAT_RESUME_2026-09-22.md` | Предыдущая сессия (дата в имени) | ⚠️ История |
| `CHAT_RESUME_2026-09-21.md` | Предыдущая сессия | ⚠️ История |
| `CHAT_RESUME_2026-09-20.md` | Предыдущая сессия | ⚠️ История |
@@ -17,8 +20,8 @@
## Как пользоваться
1. Открыть `CHAT_RESUME_IAC_2026-09-24.md` — это сводка всей линии по IaC.
2. Внутри него есть ссылки на детальные документы (`../30_analysis/*`, `../20_prompts/*`).
1. Открыть `CHAT_RESUME_2026-09-27_vm_in_pipeline.md` — это сводка всей текущей линии (и всё, что уже сделано ранее).
2. Внутри него есть ссылки на детальные документы (`../30_analysis/*`, `../10_plans/*`, `../20_prompts/*`).
3. Устаревшие рестюме **не удалять** — они фиксируют состояние на свою дату (полезно для хронологии).
## Важно про даты
+28
View File
@@ -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
}
@@ -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
}
@@ -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
}
@@ -0,0 +1,4 @@
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
@@ -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]
}
@@ -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\"}]"
@@ -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"
}
+20
View File
@@ -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
}
@@ -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"
}
}
}
@@ -0,0 +1,85 @@
# vDC → Edge → внешние IP → SNAT
Пример поднимает сетевую основу в Nubes:
- виртуальный датацентр (vDC);
- сетевой шлюз периметра (Edge);
- внешние IP на организации;
- SNAT на шлюзе.
Кластер Штурвал сюда **не входит** — он разворачивается долго, отдельным шагом.
Организацию создайте заранее в ЛК: Terraform её не создаёт и не удаляет.
**Перед началом:** нужен установленный Terraform 1.5 или новее. Провайдер скачается сам
при `terraform init` — отдельно ставить ничего не нужно.
## 1. Скачайте пример
```bash
git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git
cd tf_examples/fullpipe_chain
```
## 2. Возьмите значения в ЛК
| Значение | Где взять | Пример |
|---|---|---|
| `api_token` | ЛК → Профиль → Токены → «Технический» | `eyJhbGciOi...` |
| `organization` | ЛК → услуга «Организация в Cloud Director» → название услуги | `organ` |
| `vdc_network_provider` | ЛК → создание vDC → «Сетевой провайдер» | `snb1` |
| `vdc_provider_vdc` | ЛК → создание vDC → «Provider VDC» | `Intel Broadwell 2.4` |
| `vdc_storage_config` | ЛК → создание vDC → доступные дисковые политики | `SATA` |
| `ip_space_name` | ЛК → карточка организации → внешние IP | `internet-ipv4-v1` |
Остальные значения уже подставлены в `terraform.tfvars.example` — меняйте, если нужно.
## 3. Заполните terraform.tfvars
```bash
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars # подставьте значения из шага 2
```
Полный список параметров — в `variables.tf`, у каждой переменной есть описание.
## 4. Выполните команды
```bash
terraform init # один раз — скачает провайдер
terraform plan # покажет, что будет создано (4 ресурса), ничего не меняет
terraform apply # создаст (подтвердить: yes)
```
## 5. Проверьте результат
```bash
terraform output # UUID и имена созданных услуг
```
И в ЛК: появились vDC и Edge, на организации выделены внешние IP, на шлюзе включён SNAT.
Повторный `terraform plan` должен показать `No changes`.
## 6. Удаление
```bash
terraform destroy
```
Порядок обратный: SNAT выключается, квота внешних IP обнуляется, затем удаляется шлюз,
а vDC приостанавливается (данные сохраняются).
**Организация не удаляется.**
## Состав файлов
| Файл | Что делает |
|---|---|
| `versions.tf` | версия Terraform и провайдера Nubes |
| `provider.tf` | подключение к API (токен, адрес) |
| `variables.tf` | все параметры с описанием |
| `vdc.tf` | виртуальный датацентр |
| `edge.tf` | сетевой шлюз периметра (Edge) |
| `modifiers.tf` | внешние IP на организации + SNAT на шлюзе |
| `outputs.tf` | UUID и имена созданных услуг |
| `terraform.tfvars.example` | шаблон значений (копируется в `terraform.tfvars`) |
`terraform.tfvars` с токеном никому не передавайте и не коммитьте в git.
@@ -0,0 +1,23 @@
# Сетевой шлюз периметра (Edge) — создаётся внутри vDC, объявленного в vdc.tf.
resource "nubes_vc_nsxt" "edge" {
# имя услуги в ЛК (любое, удобное вам)
resource_name = var.nsxt_resource_name
# родительская услуга: vdc (нужен vdc_uid) или vdcGroup
vdc_type = var.nsxt_vdc_type
# ссылка на vDC: сначала создаётся vDC, потом шлюз
vdc_uid = nubes_vc_vdc.vdc.id
# балансировщик (ALB): включён; виртуальных сервисов 1..4,
# для кластера Штурвал нужно не меньше 3
need_enable_avi = var.nsxt_need_enable_avi
virtual_services_count = var.nsxt_virtual_services_count
# сеть шлюза: пул адресов (маска /24 обязательна) и DNS для машин
routed_net_configuration = {
ip_addr_pool = var.nsxt_ip_addr_pool
main_dns = var.nsxt_main_dns
second_dns = var.nsxt_second_dns
}
}
@@ -0,0 +1,61 @@
# =============================================================================
# Ресурсы, которые выполняют операции modify над уже созданными услугами.
#
# Порядок строго такой:
# организация (создаётся заранее в ЛК)
# -> nubes_vc_vdc.vdc
# -> nubes_vc_nsxt.edge
# -> nubes_vc_org_ip_allocation (внешние IP на организации)
# -> nubes_vc_nsxt_snat (SNAT на шлюзе этим ipSpace)
#
# Аллокация IP идёт ПОСЛЕ шлюза: платформа строит список доступных ipSpace
# только когда vDC и Edge уже созданы. Если поменять порядок — modify упадёт
# с ошибкой «Can't cast Complex Object Type Struct to String».
# =============================================================================
# 1. Внешние IP на организации.
resource "nubes_vc_org_ip_allocation" "org_ip" {
# та же организация, что у vDC: название услуги из ЛК или её UUID
organization = var.organization
# сколько IP выделить: name — имя ipSpace, count — количество (строкой).
# массив передаётся целиком, поэтому старые значения перезаписываются
vip_configure = jsonencode([
{
name = var.ip_space_name
count = var.ip_count
}
])
# при destroy: false — квота обнуляется (count=0); true — оставить как есть
keep_on_destroy = false
depends_on = [nubes_vc_nsxt.edge]
}
# 2. SNAT на шлюзе: внешние адреса для машин.
resource "nubes_vc_nsxt_snat" "snat" {
# UUID шлюза, созданного выше
nsxt_uid = nubes_vc_nsxt.edge.id
ip_space_name = var.ip_space_name
# при destroy: false — SNAT выключается (no-needed); true — оставить как есть
keep_on_destroy = false
# 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
}
@@ -0,0 +1,32 @@
# Значения, нужные для дальнейшей работы (например, чтобы привязать к шлюзу
# кластер Штурвал или другие услуги).
output "vdc_id" {
description = "UUID созданного vDC"
value = nubes_vc_vdc.vdc.id
}
output "vdc_name" {
description = "Имя vDC"
value = nubes_vc_vdc.vdc.resource_name
}
output "nsxt_id" {
description = "UUID созданного Edge (сетевого шлюза периметра)"
value = nubes_vc_nsxt.edge.id
}
output "nsxt_name" {
description = "Имя Edge"
value = nubes_vc_nsxt.edge.resource_name
}
output "vdc_state_params" {
description = "Параметры vDC из API (что реально создалось)"
value = nubes_vc_vdc.vdc.state_params
}
output "nsxt_state_params" {
description = "Параметры Edge из API (что реально создалось)"
value = nubes_vc_nsxt.edge.state_params
}
@@ -0,0 +1,6 @@
# Подключение к API Nubes.
# Токен и адрес API берутся из terraform.tfvars (переменные api_token, api_endpoint).
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
@@ -0,0 +1,53 @@
# =============================================================================
# Шаблон значений для fullpipe_chain.
#
# 1. Скопируйте файл: cp terraform.tfvars.example terraform.tfvars
# 2. Заполните значения ниже (откуда брать — в комментариях и в README.md).
# 3. terraform.tfvars НЕ публикуйте и не коммитьте: в нём токен доступа.
# =============================================================================
# ЛК → Профиль → Токены → «Технический»
api_token = "eyJhbGciOi..."
# Название услуги «Организация в Cloud Director» в ЛК (например: organ).
# Организацию создайте заранее в ЛК — Terraform её не создаёт и не удаляет.
organization = "organ"
# Имя ipSpace для внешнего IP. Смотреть в ЛК в карточке организации.
ip_space_name = "internet-ipv4-v1"
# Сколько внешних IP выделить (строкой).
ip_count = "3"
# --- vDC (виртуальный датацентр) ---
# Имя vDC в ЛК — любое.
vdc_resource_name = "fullpipe-vdc"
# ЛК → создание vDC → «Сетевой провайдер»
vdc_network_provider = "snb1"
# ЛК → создание vDC → «Provider VDC»
vdc_provider_vdc = "Intel Broadwell 2.4"
# vCPU, шт.
vdc_cpu_allocated = 8
# Резервирование vCPU, %: допустимы 0, 50 или 80
vdc_cpu_guaranteed = 0
# RAM, ГБ
vdc_mem_allocated = 32
# Дисковая политика: JSON-массив, name — имя политики из ЛК, size — размер в ГБ.
# Пример: политика SATA, 200 ГБ.
vdc_storage_config = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
# --- Edge (сетевой шлюз периметра) ---
# Имя Edge в ЛК — любое.
nsxt_resource_name = "fullpipe-edge"
# Родительская услуга: vdc или vdcGroup.
nsxt_vdc_type = "vdc"
# Балансировщик AVI (ALB).
nsxt_need_enable_avi = true
# Виртуальных сервисов AVI: 1..4. Для кластера Штурвал нужно не меньше 3.
nsxt_virtual_services_count = 3
# Адресный пул routed-сети шлюза. Маска /24 обязательна.
nsxt_ip_addr_pool = "10.10.102.0/24"
# DNS для машин за шлюзом.
nsxt_main_dns = "81.22.46.22"
nsxt_second_dns = "185.247.187.77"
@@ -0,0 +1,123 @@
# =============================================================================
# Переменные. Все значения задаются в terraform.tfvars.
# Шаблон файла: terraform.tfvars.example (скопируйте в terraform.tfvars).
# Где взять каждое значение — в описании переменной и в README.md.
# =============================================================================
variable "api_token" {
type = string
sensitive = true
description = "Токен API. ЛК → Профиль → Токены → «Технический». Никому не передавайте: токен даёт полный доступ к услугам."
}
variable "api_endpoint" {
type = string
default = "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc"
description = "Адрес API. Менять не нужно."
}
# --- Организация ---
variable "organization" {
type = string
description = "Название услуги «Организация в Cloud Director» в ЛК (например: organ). Можно указать и её UUID. Организация создаётся заранее в ЛК — Terraform её не создаёт."
}
variable "ip_space_name" {
type = string
description = "Имя ipSpace для внешнего IP. Смотреть в ЛК в карточке организации (например: internet-ipv4-v1)."
}
variable "ip_count" {
type = string
default = "3"
description = "Сколько внешних IP выделить на организации. Строкой — так его отдаёт платформа."
}
# --- vDC (виртуальный датацентр) ---
variable "vdc_resource_name" {
type = string
default = "fullpipe-vdc"
description = "Имя vDC в ЛК. Любое, удобное вам."
}
variable "vdc_network_provider" {
type = string
default = null
description = "Сетевой провайдер vDC. Взять в ЛК на странице создания vDC (например: snb1)."
}
variable "vdc_provider_vdc" {
type = string
default = null
description = "Provider VDC. Взять в ЛК на странице создания vDC (например: Intel Broadwell 2.4)."
}
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, ГБ."
}
variable "vdc_storage_config" {
type = string
default = "[{\"name\":\"SATA\",\"size\":\"200\"}]"
description = "Дисковая политика: JSON-массив, name — имя политики из ЛК, size — размер в ГБ (строкой). Имя политики должно существовать в ресурсном пуле (например: SATA, SSD)."
}
# --- Edge (сетевой шлюз периметра, vc_nsxt) ---
variable "nsxt_resource_name" {
type = string
default = "fullpipe-edge"
description = "Имя Edge в ЛК. Любое, удобное вам."
}
variable "nsxt_vdc_type" {
type = string
default = "vdc"
description = "Тип родительской услуги: vdc (нужен vdc_uid) или vdcGroup."
}
variable "nsxt_need_enable_avi" {
type = bool
default = true
description = "Включить балансировщик AVI (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 для машин за шлюзом."
}
@@ -0,0 +1,27 @@
# Виртуальный датацентр (vDC) — создаётся внутри организации из terraform.tfvars.
resource "nubes_vc_vdc" "vdc" {
# имя услуги в ЛК (любое, удобное вам)
resource_name = var.vdc_resource_name
# организация, к которой привязан vDC: название услуги из ЛК или её UUID
organization_uid = var.organization
# значения из ЛК со страницы создания vDC
network_provider = var.vdc_network_provider
provider_vdc = var.vdc_provider_vdc
# квоты: vCPU (шт.), резервирование vCPU (%), RAM (ГБ)
# для cpu_guaranteed допустимы только 0, 50 или 80
cpu_allocated = var.vdc_cpu_allocated
cpu_guaranteed = var.vdc_cpu_guaranteed
mem_allocated = var.vdc_mem_allocated
# дисковые политики: JSON-массив, name — имя политики из ЛК, size — размер в ГБ
storage_config = var.vdc_storage_config
# при destroy не удалять vDC, а приостановить (данные сохраняются)
suspend_on_destroy = true
# если услуга с таким именем уже есть — подключиться к ней, а не падать с ошибкой
adopt_existing_on_create = true
}
@@ -0,0 +1,12 @@
# Версия Terraform и провайдера Nubes.
# Провайдер скачается сам при `terraform init` — вручную ставить ничего не нужно.
terraform {
required_version = ">= 1.5.0"
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.21"
}
}
}
@@ -0,0 +1,118 @@
# Как развернуть vDC, Edge, внешние IP и SNAT
Пошаговая инструкция. Готовые файлы примера — в репозитории `tf_examples`, папка `fullpipe_chain`.
Что получится в итоге:
- виртуальный датацентр (vDC);
- сетевой шлюз периметра (Edge);
- внешние IP на организации;
- SNAT на шлюзе.
Кластер Штурвал в эту инструкцию не входит — он разворачивается долго, отдельным шагом.
**Организацию создайте заранее в ЛК** — Terraform её не создаёт и не удаляет.
## 1. Скачайте пример
```bash
git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git
cd tf_examples/fullpipe_chain
```
Понадобится Terraform 1.5 или новее. Провайдер скачается сам при `terraform init`.
## 2. Возьмите значения в ЛК
| Значение | Где взять | Пример |
|---|---|---|
| `api_token` | ЛК → Профиль → Токены → «Технический» | `eyJhbGciOi...` |
| `organization` | ЛК → услуга «Организация в Cloud Director» → название услуги | `organ` |
| `vdc_network_provider` | ЛК → создание vDC → «Сетевой провайдер» | `snb1` |
| `vdc_provider_vdc` | ЛК → создание vDC → «Provider VDC» | `Intel Broadwell 2.4` |
| `vdc_storage_config` | ЛК → создание vDC → доступные дисковые политики | `SATA` |
| `ip_space_name` | ЛК → карточка организации → внешние IP | `internet-ipv4-v1` |
## 3. Заполните значения
```bash
cp terraform.tfvars.example terraform.tfvars
nano terraform.tfvars
```
Файл `terraform.tfvars` выглядит так:
```hcl
# Токен из ЛК
api_token = "eyJhbGciOi..."
# Организация из ЛК (создана заранее)
organization = "organ"
# Внешние IP
ip_space_name = "internet-ipv4-v1"
ip_count = "3"
# vDC
vdc_resource_name = "fullpipe-vdc" # имя услуги в ЛК, любое
vdc_network_provider = "snb1" # ЛК → создание vDC → «Сетевой провайдер»
vdc_provider_vdc = "Intel Broadwell 2.4" # ЛК → создание vDC → «Provider VDC»
vdc_cpu_allocated = 8 # vCPU, шт.
vdc_cpu_guaranteed = 0 # резервирование vCPU, %: 0, 50 или 80
vdc_mem_allocated = 32 # RAM, ГБ
vdc_storage_config = "[{\"name\":\"SATA\",\"size\":\"200\"}]" # политика и размер, ГБ
# Edge (сетевой шлюз периметра)
nsxt_resource_name = "fullpipe-edge" # имя услуги в ЛК, любое
nsxt_vdc_type = "vdc" # родитель: vdc или vdcGroup
nsxt_need_enable_avi = true # балансировщик AVI (ALB)
nsxt_virtual_services_count = 3 # виртуальных сервисов AVI: 1..4 (Штурвал: не меньше 3)
nsxt_ip_addr_pool = "10.10.102.0/24" # пул адресов routed-сети, маска /24
nsxt_main_dns = "81.22.46.22" # основной DNS
nsxt_second_dns = "185.247.187.77" # второй DNS
```
Описание всех параметров — в `variables.tf`. `terraform.tfvars` с токеном никому не передавайте
и не коммитьте в git.
## 4. Выполните команды
```bash
terraform init # один раз — скачает провайдер
terraform plan # покажет, что будет создано: 4 ресурса
terraform apply # создаст (подтвердить: yes)
```
## 5. Проверьте результат
```bash
terraform output # UUID и имена созданных услуг
```
И в ЛК: появились vDC и Edge, на организации выделены внешние IP, на шлюзе включён SNAT.
Повторный `terraform plan` должен показать `No changes`.
## 6. Удаление
```bash
terraform destroy
```
Порядок обратный: SNAT выключается, квота внешних IP обнуляется, затем удаляется шлюз,
а vDC приостанавливается (данные сохраняются). **Организация не удаляется.**
## Файлы примера
| Файл | Что делает |
|---|---|
| `versions.tf` | версия Terraform и провайдера Nubes |
| `provider.tf` | подключение к API (токен, адрес) |
| `variables.tf` | все параметры с описанием |
| `vdc.tf` | виртуальный датацентр |
| `edge.tf` | сетевой шлюз периметра (Edge) |
| `modifiers.tf` | внешние IP на организации + SNAT на шлюзе |
| `outputs.tf` | UUID и имена созданных услуг |
| `terraform.tfvars.example` | шаблон значений (копируется в `terraform.tfvars`) |
Подробнее про два последних ресурса — на странице
[«Ресурсы-модификаторы (IP организации, SNAT)»](../modifiers/org_ip_and_snat.md).
@@ -0,0 +1,32 @@
# Примеры Terraform для Nubes
> ## Внимание: рабочая папка одна
>
> Для работы используйте **только** [`fullpipe_chain`](fullpipe_chain/) — это пример
> «vDC → Edge → внешние IP → SNAT». Он прогнан на живом стенде (создание и удаление)
> и снабжён пошаговой инструкцией.
>
> Остальные папки репозитория — черновики: могут быть сырыми, содержать неактуальные
> версии провайдера и работать неправильно. **Не используйте их.**
Пошаговая инструкция: <https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/curated/pipeline/vdc_edge_ip_snat/>
## fullpipe_chain — что использовать
```bash
git clone https://gitea.services.ngcloud.ru/Nail/tf_examples.git
cd tf_examples/fullpipe_chain
cp terraform.tfvars.example terraform.tfvars # заполнить значениями из ЛК
terraform init
terraform plan # покажет 4 ресурса, ничего не меняет
terraform apply # подтвердить: yes
```
Подробности и список значений — в [README примера](fullpipe_chain/README.md).
Организацию создайте заранее в ЛК: Terraform её не создаёт и не удаляет.
## Остальные папки — не использовать
`CRUD`, `modify_resources`, `SHTURVAL_MGMT`, `iot-rmq-demo` — черновики от прошлых работ.
Могут быть сырыми и не поддерживаются. Берите только `fullpipe_chain`.
+28
View File
@@ -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
}
@@ -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
}
@@ -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
}
@@ -0,0 +1,4 @@
provider "nubes" {
api_token = var.api_token
api_endpoint = var.api_endpoint
}
@@ -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-dev1"
description = "Имя услуги «Kubernetes кластер Штурвал» в ЛК"
}
variable "shturval_cluster_name" {
type = string
default = "shturval-dev-01"
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]
}
@@ -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"
}
+20
View File
@@ -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
}
@@ -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"
}
}
}
+2 -1
View File
@@ -198,7 +198,8 @@ UUID в облаке не имеет «правильного» регистра
## 10. Куда смотреть дальше
- `curated/pipeline/vdc_edge_ip_snat.md` — пошаговый разбор цепочки vDC → Edge → IP → SNAT.
- [Как развернуть vDC, Edge, внешние IP, SNAT и кластер Штурвал](https://tf-docs.nodejsk8s.dev.nubes.ru/nubes-dev/curated/pipeline/vdc_edge_ip_snat/) —
пошаговая инструкция по всей цепочке вашего стенда.
- `curated/modifiers/org_ip_and_snat.md` — ресурсы-модификаторы.
- `30_registry/guides/terraform-structure.md` — структура манифестов.
- Внутренние документы (не публикуются): `60_strategy/provider_philosophy.md`,
@@ -0,0 +1,116 @@
# Зависимости облачных сервисов каталога DEV
Промежуточная инвентаризация для будущего общего графа. Источник — YAML-контракты
в `generated/dev/resources_yaml`.
Вершина — сервис каталога. Направление будущей стрелки: `потребитель -> зависимость`.
В таблицу зависимостей включены только связи, явно подтверждённые полем
`ref_svc_id` или тем же контрактом сервиса.
## Все сервисы каталога
| ID | Сервис | YAML |
|---:|---|---|
| 1 | Болванка | `1_dummy.yaml` |
| 2 | Темплейт k8s | `2_template.yaml` |
| 12 | S3 Object Storage | `12_s3.yaml` |
| 13 | S3 бакет | `13_s3bucket.yaml` |
| 19 | Организация в Cloud Director | `19_vc_org.yaml` |
| 21 | Виртуальный датацентр (vDC) | `21_vc_vdc.yaml` |
| 22 | Сетевой шлюз периметра (Edge) | `22_vc_nsxt.yaml` |
| 25 | Публичные IP адреса | `25_vcexternalip.yaml` |
| 26 | Виртуальный каталог ВМ (vApp) | `26_vapp.yaml` |
| 28 | Виртуальная машина | `28_vc_vm_v3.yaml` |
| 29 | Группа датацентров | `29_vc_vdc_group.yaml` |
| 50 | Nextcloud | `50_nextcloud.yaml` |
| 81 | Apache Superset | `81_superset.yaml` |
| 82 | Container Registry | `82_harbor.yaml` |
| 86 | Velero | `86_k8s_velero.yaml` |
| 87 | Valkey | `87_k8svalkey.yaml` |
| 88 | k8sZitiController | `88_k8s_ziti_controller.yaml` |
| 89 | Web-сервер с фреймворком Flask | `89_flask.yaml` |
| 90 | PostgreSQL | `90_postgres.yaml` |
| 91 | Redis | `91_redis.yaml` |
| 92 | MongoDB | `92_mongodb.yaml` |
| 93 | RabbitMQ | `93_rabbitmq.yaml` |
| 94 | Lucee | `94_lucee.yaml` |
| 95 | NodeJS | `95_nodejs.yaml` |
| 96 | pgAdmin | `96_pgadmin.yaml` |
| 97 | NodeRed | `97_nodered.yaml` |
| 98 | Простой HTTP контейнер | `98_http.yaml` |
| 99 | Gitea | `99_gitea.yaml` |
| 109 | Управление DNS | `109_zones_v2.yaml` |
| 111 | DNS запись | `111_dnsrecord.yaml` |
| 115 | Mariadb | `115_mariadb.yaml` |
| 116 | ApacheKafka | `116_kafka.yaml` |
| 119 | Akhq | `119_akhq.yaml` |
| 120 | ClickHouse | `120_clickhouse.yaml` |
| 148 | Менеджмент Kubernetes кластер Штурвал | `148_vc_mgmt_sthutrval_cluster.yaml` |
| 149 | VALO Cloud | `149_valo_tenant.yaml` |
| 150 | Kubernetes кластер Штурвал | `150_k8s_sthutrval_cluster.yaml` |
| 151 | Vault | `151_k8s_openbao.yaml` |
| 153 | Nifi | `153_nifi.yaml` |
| 163 | ML Улей | `163_llm_ai.yaml` |
## Подтверждённые зависимости
| ID | Потребитель | Зависимость | Поле связи | Смысл связи |
|---|---|---|---|---|
| D-01 | DNS запись (111) | Управление DNS (109) | `zoneUid` | запись создаётся в DNS-зоне |
| D-02 | S3 бакет (13) | S3 Object Storage (12) | `s3UserUid` | бакет создаётся для корневой S3-услуги |
| D-03 | Виртуальный датацентр (21) | Организация в Cloud Director (19) | `organizationUid` | vDC принадлежит организации |
| D-04 | Сетевой шлюз периметра (22) | Виртуальный датацентр (21) | `vdcUid` | Edge привязывается к vDC |
| D-05 | Сетевой шлюз периметра (22) | Группа датацентров (29) | `vdcGroupUid` | альтернативная привязка Edge к группе vDC (vdcType=vdcGroup) |
| D-06 | Виртуальный каталог ВМ (26) | Сетевой шлюз периметра (22) | `nsxtUid` | vApp использует Edge для сети |
| D-07 | Виртуальный каталог ВМ (26) | Виртуальный датацентр (21) | `vdcUid` | vApp размещается в vDC |
| D-08 | Виртуальная машина (28) | Виртуальный каталог ВМ (26) | `vappUid` | VM размещается в vApp |
| D-09 | Группа датацентров (29) | Виртуальный датацентр (21) | `vdcUid` | группа объединяет vDC (create и add_vdc) |
| D-10 | Akhq (119) | ApacheKafka (116) | `kafkaUid` | UI подключается к Kafka-кластеру |
| D-11 | Container Registry (82) | S3 Object Storage (12) | `s3Uid` | registry использует S3 для хранения |
| D-12 | Gitea (99) | PostgreSQL (90) | `psqlUid` | Gitea использует PostgreSQL |
## Переиспользование существующих инстансов
Зависимости (кроме Организации) являются ссылками на **существующие инстансы** через UUID:
- Новую ВМ (28) можно создать в уже существующем vApp, vDC, Edge — ссылкой `vappUid`/`vdcUid`/`nsxtUid`.
- Edge (22) можно переиспользовать, если он привязан к нужному vDC или groupvDC.
- Для Штурвала (150) переиспользуемый Edge должен иметь включённый ALB и `virtualServicesCount >= 3`.
- Внешние IP резервируются только после: Организация + vDC + Каталог (vApp) + Edge (по `25_vcexternalip.yaml`, `service_man`).
## Где выделяются ресурсы (CPU / RAM / Storage / IP)
| Элемент | Где задаётся | Параметр(ы) |
|---|---|---|
| Пул CPU/RAM/Storage организации | vDC (21) create/modify | `cpuAllocated`, `memAllocated`, `storageConfig` (+ разовый `cpuGuaranteed` 0/50/80) |
| Запрос ресурсов нод Штурвала | Кластер Штурвал (150) | `controlPlaneConfiguration.sizingPolicy/sizingDisk/count`, `workerConfiguration…` (политики из vDC) |
| Запрос ресурсов ВМ | Виртуальная машина (28) | `vmCpu`, `vmRam`, `vmDisk` (доп. диск) |
| Внешние IP | vcOrg (19) `modify` (`vIPConfigure`) или `vcexternalip` (25) | `ipSpaceName`, квота адресов |
| SNAT-правило | Edge (22) `modify` | `ipSpaceName` |
| DNAT/SNAT-правила адреса | Публичные IP (25) | `dnatCreate`/`snatCreate`, `internalAddrAccess`, `internalPortAccess` |
## Чек-лист создания Штурвала (150) — по `service_man`
1. Организация (19) — create.
2. vDC (21) — create, выделить ресурсы под все ноды.
3. Edge (22) — create, включить ALB и `virtualServicesCount >= 3`.
4. Внешние адреса в организации — vcOrg `modify`, суммарно 3 адреса.
5. SNAT — Edge `modify`, выбрать `ipSpaceName`.
6. Кластер Штурвал (150) — create (требует `vdcUid` и `nsxtUid`).
## Чек-лист создания ВМ (28) — по create-параметрам
1. vDC (21) — существующий (`vdcUid`).
2. Edge (22) — существующий (`nsxtUid`).
3. vApp (26) — существующий (`vappUid`), привязан к vDC и Edge.
4. ВМ (28) — create; обязательные: `vappUid`, `vmName`, `vmCpu`, `vmRam`, `accessPortList`, `imageVm`, `userLogin`, `userPublicKey`, `ipSpaceName`.
## Правила для будущего графа
1. Рисовать стрелку только по подтверждённой связи сервиса каталога.
2. `ref_svc_id` считать прямой зависимостью; `descr` использовать для подписи
стрелки и проверки смысла.
3. Не считать общей платформой, DNS-полем или внешним IP автоматическую связь
между сервисами без ссылки на конкретный сервисный ID.
4. Поля без `ref_svc_id` вынести в отдельный список кандидатов на проверку, но не
смешивать с подтверждёнными рёбрами.
+49 -12
View File
@@ -1,17 +1,33 @@
# Как развернуть vDC, Edge, внешние IP и SNAT
# Как развернуть vDC, Edge, внешние IP, SNAT и кластер Штурвал
Пошаговая инструкция. Готовые файлы примера — в репозитории `tf_examples`, папка `fullpipe_chain`.
Что получится в итоге:
- виртуальный датацентр (vDC);
- сетевой шлюз периметра (Edge);
- сетевой шлюз периметра (Edge) с балансировщиком AVI;
- внешние IP на организации;
- SNAT на шлюзе.
Кластер Штурвал в эту инструкцию не входит — он разворачивается долго, отдельным шагом.
- SNAT на шлюзе;
- Kubernetes-кластер Штурвал (сервис 150) на этой сети.
**Организацию создайте заранее в ЛК** — Terraform её не создаёт и не удаляет.
Кластер Штурвал создаётся десятки минут, поэтому провайдер ждёт его до часу
(`operation_timeout = "60m"`); остальные ресурсы — минуты.
## Что нужно перед началом
| Требование | Зачем | Где смотреть |
|---|---|---|
| Terraform 1.5 или новее | работает провайдер | `terraform version` |
| Токен API | доступ к ЛК | ЛК → Профиль → Токены → «Технический» |
| Организация в Cloud Director | всё создаётся внутри неё | услуга «Организация в Cloud Director» |
| Edge с балансировщиком AVI | без ALB кластер Штурвал не поднимется | `nsxt_need_enable_avi = true`, `nsxt_virtual_services_count >= 3` |
| Не меньше 3 внешних IP | адрес Kubernetes API, адрес Ingress и запас | `ip_count = "3"` |
| SNAT на Edge | выход в интернет для машин кластера | ресурс `nubes_vc_nsxt_snat` |
Порядок из чек-листа услуги 150 (именно так связаны ресурсы в примере):
организация → vDC → Edge (ALB, AVI ≥ 3) → внешние IP (≥ 3) → SNAT → кластер Штурвал.
Минимум для кластера: 1 мастер-нода и 1 воркер-нода по 4 vCPU / 8 ГБ RAM / 50 ГБ диска.
## 1. Скачайте пример
@@ -79,7 +95,7 @@ nsxt_second_dns = "185.247.187.77" # второй DNS
```bash
terraform init # один раз — скачает провайдер
terraform plan # покажет, что будет создано: 4 ресурса
terraform plan # покажет, что будет создано: 5 ресурсов
terraform apply # создаст (подтвердить: yes)
```
@@ -89,17 +105,37 @@ terraform apply # создаст (подтвердить: yes)
terraform output # UUID и имена созданных услуг
```
И в ЛК: появились vDC и Edge, на организации выделены внешние IP, на шлюзе включён SNAT.
Повторный `terraform plan` должен показать `No changes`.
И в ЛК: появились vDC, Edge и кластер Штурвал, на организации выделены внешние IP,
на шлюзе включён SNAT. Повторный `terraform plan` должен показать `No changes`.
## 6. Удаление
Кластер создаётся десятки минут — провайдер ждёт его (`operation_timeout = "60m"`),
в ЛК он появится со статусом `running`. Адреса Kubernetes API и Ingress — в
`terraform output shturval_state_params` (поля `kubernetesApiAddress` и `ingressAddress`).
## 6. Удаление: «заморозка» вместо удаления
```bash
terraform destroy
```
Порядок обратный: SNAT выключается, квота внешних IP обнуляется, затем удаляется шлюз,
а vDC приостанавливается (данные сохраняются). **Организация не удаляется.**
По умолчанию пример повторяет рабочую конфигурацию — при `destroy` объекты **не удаляются**:
| Ресурс | Что делает `destroy` | Флаг в примере |
|---|---|---|
| Кластер Штурвал | `suspend`: выключается, данные и адреса сохраняются | `suspend_on_destroy = true` |
| vDC | `suspend` | `suspend_on_destroy = true` |
| Edge | не трогается: у эджа нет операции `suspend` | `keep_on_destroy = true` |
| SNAT | не выключается | `keep_on_destroy = true` |
| Квота внешних IP | не меняется: адреса держит кластер Штурвала | `keep_on_destroy = true` |
Приоритет флагов: `keep_on_destroy` важнее `suspend_on_destroy`. Следующий `apply`
усыновит объекты по имени и разморозит кластер и vDC
(`adopt_existing_on_create = true` → `resume`). **Организация не удаляется никогда.**
Полное удаление — осознанно: поставьте `keep_on_destroy = false` и
`suspend_on_destroy = false` и удаляйте по порядку: кластер → квота IP (`count = 0`) →
SNAT → Edge → vDC. Квоту нельзя опустить ниже занятых адресов, поэтому — только
после удаления кластера; vDC удаляется лишь через 14 дней после `suspend`.
## Файлы примера
@@ -111,7 +147,8 @@ terraform destroy
| `vdc.tf` | виртуальный датацентр |
| `edge.tf` | сетевой шлюз периметра (Edge) |
| `modifiers.tf` | внешние IP на организации + SNAT на шлюзе |
| `outputs.tf` | UUID и имена созданных услуг |
| `shturval.tf` | Kubernetes-кластер Штурвал: переменные Штурвала, группы воркеров и сам ресурс |
| `outputs.tf` | UUID и имена созданных услуг (vDC, Edge, Штурвал) |
| `terraform.tfvars.example` | шаблон значений (копируется в `terraform.tfvars`) |
Подробнее про два последних ресурса — на странице
+129
View File
@@ -0,0 +1,129 @@
# Схема зависимостей облачных сервисов (docs/diagrams)
Папка содержит все артефакты схемы: исходники, отрендеренные изображения и
генератор. Здесь же описан процесс создания и результаты сверки с контрактами.
## 1. Состав папки
| Файл | Что это | Чем создаётся |
|---|---|---|
| `infra_services_flow.mmd` | исходник Mermaid-версии схемы | правится вручную |
| `infra_services_flow.svg` | вектор Mermaid-версии | рендер через `mermaid.ink` |
| `infra_services_flow.png` | растр Mermaid-версии | рендер через `mermaid.ink` |
| `render_infra_diagram.py` | генератор стилизованной схемы (PNG + SVG) | чистый Python + Pillow |
| `infra_services_diagram.png` | стилизованная схема, растр 1600×720 (рендер 2x + LANCZOS) | `render_infra_diagram.py` |
| `infra_services_diagram.svg` | стилизованная схема, вектор | `render_infra_diagram.py` |
Две версии существуют одновременно: Mermaid — для правок текстом и встраивания
в Markdown; стилизованная — как готовая картинка для показа.
## 2. Источник данных
Единственный источник фактов — YAML-контракты сервисов:
```
generated/dev/resources_yaml/*.yaml
```
Из них берутся:
- `service_id`, `service_display_name` — идентичность сервиса;
- `operations[].params[].ref_svc_id` — **прямая зависимость** от другого сервиса;
- `operations[].params[].depends_on` — зависимость порядка внутри операции;
- `value_list`, `default`, `man`, `descr` — значения на блоках (CPU, сети и т.п.);
- `service_man` — чек-листы и порядок действий, заданные самой платформой.
Terraform-файлы `DEV_STAND/*` **не используются как источник** для этой схемы —
они лишь один из примеров потребления.
## 3. Как перегенерировать
```bash
cd /home/naeel/TF/tf_provider
python3 docs/diagrams/render_infra_diagram.py
```
Скрипт пишет `infra_services_diagram.png` и `.svg` **в свою же папку**
(`docs/diagrams/`), путь вычисляется от расположения файла.
Требования: `python3` с `Pillow` и шрифты DejaVu
(`/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf`, `...-Bold.ttf`).
Сторонние библиотеки для SVG не нужны — SVG формируется текстом.
Для Mermaid-версии нужен рендерер (`mermaid.ink`, `mmdc` или предпросмотр
Markdown в VS Code); в этом репозитории использовался `mermaid.ink` через
`python3` + `urllib`.
## 4. Структура картинки
Пять колонок = этапы создания, слева направо:
1. **Тенант** — Организация в Cloud Director (19)
2. **Пул ресурсов** — Виртуальный датацентр (21)
3. **Сетевой периметр** — Сетевой шлюз периметра / Edge (22)
4. **Среда запуска** — Каталог ВМ / vApp (26) + Внешние IP (vcOrg modify / 25)
5. **Потребители** — Виртуальная машина (28) и Kubernetes кластер Штурвал (150)
### Обозначения
- **Сплошная стрелка** — обязательная зависимость, ссылка на UUID существующего
инстанса (`ref_svc_id`).
- **Пунктирная стрелка** — выделение ресурса, операция `modify` или предусловие.
- Направление стрелки всегда **от зависимости к потребителю**: `Org → vDC`,
`vApp → VM`. То есть стрелка отвечает на вопрос «что должно существовать до».
- Цвет колонки кодирует этап, цвет рамки блока — группу (тенант / ресурсы / сеть /
потребители / адреса). Легенда — в правом нижнем углу картинки.
### Бейджи точек выделения ресурсов
| Бейдж | Где на схеме | Что означает |
|---|---|---|
| «здесь выделяются CPU / RAM / Диск» | блок vDC (21) | `cpuAllocated`, `memAllocated`, `storageConfig` |
| «SNAT настраивается операцией modify» | блок Edge (22) | SNAT — не сервис, а `modify` у Edge |
| «здесь выделяются IP» | блок «Внешние IP» | выделение адресов на организации |
| «требует ALB + VS ≥ 3» | блок Штурвала (150) | обязательное условие из чек-листа |
## 5. Результат сверки с YAML
Проверено построчно (коммит `6791e8f`, дополнение — связь `vApp → Внешние IP`).
| Утверждение на схеме | Источник |
|---|---|
| Организация: типы `iaas / saas` | `19_vc_org.yaml:47-53` |
| Выделение IP через `vcOrg modify (vIPConfigure)` | `19_vc_org.yaml:115` |
| Для внешних IP нужны vDC и Edge | `19_vc_org.yaml:112` (`man`) |
| vDC: `cpuGuaranteed: 0% / 50% / 80%` | `21_vc_vdc.yaml:71-78` |
| vDC: `cpuAllocated`, `memAllocated`, `storageConfig` | `21_vc_vdc.yaml:49,83,93` |
| Edge: AVI Load Balancer | `22_vc_nsxt.yaml` (`needEnableAVI`) |
| Edge: routed-сеть `10.10.102.0/24` | `22_vc_nsxt.yaml:5` |
| Edge: SNAT настраивается операцией `modify` | `22_vc_nsxt.yaml` (`modify → ipSpaceName`) |
| vApp → vDC (`vdcUid`), Edge (`nsxtUid`) | `26_vapp.yaml:38,56` |
| ВМ → vApp (`vappUid`), `vmCpu · vmRam · vmDisk` | `28_vc_vm_v3.yaml:39,49,59,69` |
| Штурвал: `vdcUid`, `nsxtUid`; `sizingPolicy · sizingDisk · count` | `150_k8s_sthutrval_cluster.yaml:41,47,132,141,148` |
| Публичные IP: NAT/DNAT через сервис 25 | `25_vcexternalip.yaml:41,57,100` |
| **Публичные IP требуют «Каталог» (vApp)** | `25_vcexternalip.yaml:5` (`service_man`) |
| Штурвал: обязательны ALB и VS ≥ 3; суммарно 3 внешних адреса | `150_k8s_sthutrval_cluster.yaml` (`service_man`, чек-лист) |
## 6. Известные ограничения
1. **Переиспользование инстансов** («vDC и Edge могут быть переиспользованы»)
не написано в YAML буквально — это следствие семантики ссылок на UUID
существующего инстанса. Уверенность средняя, не высокая.
2. Блок «Внешние IP» объединяет два механизма: выделение адресов на организации
(`vcOrg modify`) и правила NAT у сервиса 25 (`vcexternalip`). Они подписаны
раздельно внутри блока, но нарисованы одной вершиной.
3. Остальные 33 сервиса каталога на схеме не показаны — отрисованы только
инфраструктурные цепочки и их прямые потребители.
4. Схема — компоновка по контрактам, а не результат наблюдения за развёртыванием.
Фактический порядок и доступность нужно подтверждать отдельно.
## 7. История
| Дата | Изменение |
|---|---|
| 2026-09-26 | Создана Mermaid-версия (`infra_services_flow.*`) |
| 2026-09-26 | Исправлена связь «вычислительный пул»: от vDC, а не от внешних IP |
| 2026-09-26 | Добавлен генератор стилизованной схемы (`render_infra_diagram.py`) |
| 2026-09-26 | Убрана дублирующая подпись `modify: SNAT` у блока Edge |
| 2026-09-26 | Сверка с YAML; добавлена связь `vApp → Внешние IP` — предусловие сервиса 25 |
| 2026-09-26 | Все артефакты перенесены в `docs/diagrams/` |
Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

+145
View File
@@ -0,0 +1,145 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="720" viewBox="0 0 1600 720" font-family="DejaVu Sans, Verdana, sans-serif">
<rect width="1600" height="720" fill="#FFFFFF"/>
<rect x="16" y="8" width="270" height="552" rx="12" fill="#E8F3FB"/>
<rect x="16" y="14" width="270" height="44" rx="8" fill="#1565C0"/>
<text x="151.0" y="25" font-size="15" fill="#FFFFFF" font-weight="bold" text-anchor="middle" dominant-baseline="hanging">Шаг 1. Тенант</text>
<text x="151.0" y="68" font-size="12" fill="#7A8794" text-anchor="middle" dominant-baseline="hanging">базовый уровень изоляции</text>
<rect x="340" y="8" width="270" height="552" rx="12" fill="#FDF1E3"/>
<rect x="340" y="14" width="270" height="44" rx="8" fill="#E65100"/>
<text x="475.0" y="25" font-size="15" fill="#FFFFFF" font-weight="bold" text-anchor="middle" dominant-baseline="hanging">Шаг 2. Пул ресурсов</text>
<text x="475.0" y="68" font-size="12" fill="#7A8794" text-anchor="middle" dominant-baseline="hanging">CPU / RAM / Storage</text>
<rect x="664" y="8" width="270" height="552" rx="12" fill="#EEEAF8"/>
<rect x="664" y="14" width="270" height="44" rx="8" fill="#4527A0"/>
<text x="799.0" y="25" font-size="15" fill="#FFFFFF" font-weight="bold" text-anchor="middle" dominant-baseline="hanging">Шаг 3. Сетевой периметр</text>
<text x="799.0" y="68" font-size="12" fill="#7A8794" text-anchor="middle" dominant-baseline="hanging">маршрутизация и балансировка</text>
<rect x="988" y="8" width="270" height="552" rx="12" fill="#FDF4E3"/>
<rect x="988" y="14" width="270" height="44" rx="8" fill="#EF6C00"/>
<text x="1123.0" y="25" font-size="15" fill="#FFFFFF" font-weight="bold" text-anchor="middle" dominant-baseline="hanging">Шаг 4. Среда запуска</text>
<text x="1123.0" y="68" font-size="12" fill="#7A8794" text-anchor="middle" dominant-baseline="hanging">контейнер и внешние адреса</text>
<rect x="1312" y="8" width="270" height="552" rx="12" fill="#E9F5EA"/>
<rect x="1312" y="14" width="270" height="44" rx="8" fill="#2E7D32"/>
<text x="1447.0" y="25" font-size="15" fill="#FFFFFF" font-weight="bold" text-anchor="middle" dominant-baseline="hanging">Шаг 5. Потребители</text>
<text x="1447.0" y="68" font-size="12" fill="#7A8794" text-anchor="middle" dominant-baseline="hanging">что разворачивается в итоге</text>
<rect x="31" y="150" width="240" height="150" rx="10" fill="#FFFFFF" stroke="#0288D1" stroke-width="2"/>
<rect x="43" y="162" width="22" height="22" rx="5" fill="#0288D1"/>
<text x="73" y="162" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Организация</text>
<text x="73" y="182" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">в Cloud Director</text>
<text x="73" y="202" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 19 · vcOrg</text>
<circle cx="51" cy="231" r="2.4" fill="#0288D1"/>
<text x="61" y="224" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Типы: iaas / saas</text>
<rect x="355" y="130" width="240" height="230" rx="10" fill="#FFF3E0" stroke="#F57C00" stroke-width="2"/>
<rect x="367" y="142" width="22" height="22" rx="5" fill="#F57C00"/>
<text x="397" y="142" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Виртуальный</text>
<text x="397" y="162" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">датацентр (vDC)</text>
<text x="397" y="182" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 21 · vcVdc</text>
<circle cx="375" cy="211" r="2.4" fill="#F57C00"/>
<text x="385" y="204" font-size="10" fill="#2E3A45" dominant-baseline="hanging">cpuGuaranteed: 0% / 50% / 80%</text>
<circle cx="375" cy="231" r="2.4" fill="#F57C00"/>
<text x="385" y="224" font-size="10" fill="#2E3A45" dominant-baseline="hanging">cpuAllocated, memAllocated</text>
<circle cx="375" cy="251" r="2.4" fill="#F57C00"/>
<text x="385" y="244" font-size="10" fill="#2E3A45" dominant-baseline="hanging">storageConfig</text>
<rect x="367" y="318" width="216" height="30" rx="6" fill="#FFFFFF" stroke="#F57C00" stroke-width="1"/>
<text x="475.0" y="328" font-size="10" fill="#F57C00" text-anchor="middle" dominant-baseline="hanging">здесь выделяются CPU / RAM / Диск</text>
<rect x="679" y="120" width="240" height="200" rx="10" fill="#EDE7F6" stroke="#5E35B1" stroke-width="2"/>
<rect x="691" y="132" width="22" height="22" rx="5" fill="#5E35B1"/>
<text x="721" y="132" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Сетевой шлюз</text>
<text x="721" y="152" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">периметра (Edge)</text>
<text x="721" y="172" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 22 · vcNsxt</text>
<circle cx="699" cy="201" r="2.4" fill="#5E35B1"/>
<text x="709" y="194" font-size="10" fill="#2E3A45" dominant-baseline="hanging">AVI Load Balancer</text>
<circle cx="699" cy="221" r="2.4" fill="#5E35B1"/>
<text x="709" y="214" font-size="10" fill="#2E3A45" dominant-baseline="hanging">routed-сеть 10.10.102.0/24</text>
<rect x="691" y="278" width="216" height="30" rx="6" fill="#FFFFFF" stroke="#5E35B1" stroke-width="1"/>
<text x="799.0" y="288" font-size="10" fill="#5E35B1" text-anchor="middle" dominant-baseline="hanging">SNAT настраивается операцией modify</text>
<rect x="1003" y="100" width="240" height="110" rx="10" fill="#FFF3E0" stroke="#F57C00" stroke-width="2"/>
<rect x="1015" y="112" width="22" height="22" rx="5" fill="#F57C00"/>
<text x="1045" y="112" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Каталог ВМ (vApp)</text>
<text x="1045" y="132" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 26 · vapp</text>
<circle cx="1023" cy="161" r="2.4" fill="#F57C00"/>
<text x="1033" y="154" font-size="10" fill="#2E3A45" dominant-baseline="hanging">логический контейнер для ВМ</text>
<rect x="1003" y="245" width="180" height="150" rx="10" fill="#FAFAFA" stroke="#9E9E9E" stroke-width="2" stroke-dasharray="9 6"/>
<rect x="1015" y="257" width="22" height="22" rx="5" fill="#9E9E9E"/>
<text x="1045" y="257" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Внешние IP</text>
<text x="1045" y="277" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 25</text>
<circle cx="1023" cy="306" r="2.4" fill="#9E9E9E"/>
<text x="1033" y="299" font-size="10" fill="#2E3A45" dominant-baseline="hanging">выделение: vcOrg</text>
<circle cx="1023" cy="326" r="2.4" fill="#9E9E9E"/>
<text x="1033" y="319" font-size="10" fill="#2E3A45" dominant-baseline="hanging">NAT: сервис 25</text>
<circle cx="1023" cy="346" r="2.4" fill="#9E9E9E"/>
<text x="1033" y="339" font-size="10" fill="#2E3A45" dominant-baseline="hanging">квота IP (ipSpace)</text>
<rect x="1015" y="353" width="156" height="30" rx="6" fill="#FFFFFF" stroke="#9E9E9E" stroke-width="1"/>
<text x="1093.0" y="363" font-size="10" fill="#9E9E9E" text-anchor="middle" dominant-baseline="hanging">здесь выделяются IP</text>
<rect x="1327" y="100" width="240" height="120" rx="10" fill="#E8F5E9" stroke="#2E7D32" stroke-width="2"/>
<rect x="1339" y="112" width="22" height="22" rx="5" fill="#2E7D32"/>
<text x="1369" y="112" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Виртуальная машина</text>
<text x="1369" y="132" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 28 · vc_vm_v3</text>
<circle cx="1347" cy="161" r="2.4" fill="#2E7D32"/>
<text x="1357" y="154" font-size="10" fill="#2E3A45" dominant-baseline="hanging">vmCpu · vmRam · vmDisk</text>
<rect x="1327" y="255" width="240" height="170" rx="10" fill="#E8F5E9" stroke="#2E7D32" stroke-width="2"/>
<rect x="1339" y="267" width="22" height="22" rx="5" fill="#2E7D32"/>
<text x="1369" y="267" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Kubernetes кластер</text>
<text x="1369" y="287" font-size="15" fill="#1A1A1A" font-weight="bold" dominant-baseline="hanging">Штурвал</text>
<text x="1369" y="307" font-size="11" fill="#5F6B76" dominant-baseline="hanging">Сервис 150</text>
<circle cx="1347" cy="336" r="2.4" fill="#2E7D32"/>
<text x="1357" y="329" font-size="10" fill="#2E3A45" dominant-baseline="hanging">sizingPolicy · sizingDisk</text>
<circle cx="1347" cy="356" r="2.4" fill="#2E7D32"/>
<text x="1357" y="349" font-size="10" fill="#2E3A45" dominant-baseline="hanging">· count</text>
<rect x="1339" y="383" width="216" height="30" rx="6" fill="#FFFFFF" stroke="#2E7D32" stroke-width="1"/>
<text x="1447.0" y="393" font-size="10" fill="#2E7D32" text-anchor="middle" dominant-baseline="hanging">требует ALB + VS ≥ 3</text>
<path d="M271 225 L355 225" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="355,225 346.0,228.78 346.0,221.22" fill="#455A64"/>
<text x="313" y="262" font-size="10" fill="#455A64" text-anchor="middle" dominant-baseline="middle" transform="rotate(-90 313 262)">organizationUid</text>
<path d="M595 200 L679 200" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="679,200 670.0,203.78 670.0,196.22" fill="#455A64"/>
<text x="637" y="186" font-size="10" fill="#455A64" dominant-baseline="hanging">vdcUid</text>
<path d="M500 130 L500 92 L961 92 L961 140 L1003 140" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1003,140 994.0,143.78 994.0,136.22" fill="#455A64"/>
<text x="706" y="96" font-size="10" fill="#455A64" dominant-baseline="hanging">vdcUid</text>
<path d="M919 180 L1003 180" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1003,180 994.0,183.78 994.0,176.22" fill="#455A64"/>
<text x="961" y="166" font-size="10" fill="#455A64" dominant-baseline="hanging">nsxtUid</text>
<path d="M1243 140 L1327 140" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1327,140 1318.0,143.78 1318.0,136.22" fill="#455A64"/>
<text x="1285" y="126" font-size="10" fill="#455A64" dominant-baseline="hanging">vappUid</text>
<path d="M500 360 L500 470 L1447 470 L1447 425" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1447,425 1450.78,434.0 1443.22,434.0" fill="#455A64"/>
<text x="1180" y="456" font-size="10" fill="#455A64" dominant-baseline="hanging">vdcUid</text>
<path d="M799 320 L799 505 L1467 505 L1467 425" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1467,425 1470.78,434.0 1463.22,434.0" fill="#455A64"/>
<text x="1180" y="491" font-size="10" fill="#455A64" dominant-baseline="hanging">nsxtUid</text>
<path d="M151 300 L151 540 L1050 540 L1050 395" fill="none" stroke="#7A8794" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1050,395 1053.78,404.0 1046.22,404.0" fill="#7A8794"/>
<text x="161" y="526" font-size="10" fill="#7A8794" dominant-baseline="hanging">modify: vIPConfigure</text>
<path d="M870 320 L870 400 L1140 400 L1140 395" fill="none" stroke="#7A8794" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1140,395 1143.78,404.0 1136.22,404.0" fill="#7A8794"/>
<text x="880" y="386" font-size="10" fill="#7A8794" dominant-baseline="hanging">modify: SNAT</text>
<path d="M1050 210 L1050 245" fill="none" stroke="#7A8794" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1050,245 1046.22,236.0 1053.78,236.0" fill="#7A8794"/>
<text x="1060" y="218" font-size="10" fill="#7A8794" dominant-baseline="hanging">требует Каталог</text>
<path d="M1183 270 L1255 270 L1255 160 L1327 160" fill="none" stroke="#7A8794" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1327,160 1318.0,163.78 1318.0,156.22" fill="#7A8794"/>
<text x="1187" y="222" font-size="10" fill="#7A8794" dominant-baseline="hanging">внешний IP</text>
<path d="M1183 350 L1327 350" fill="none" stroke="#C62828" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1327,350 1318.0,353.78 1318.0,346.22" fill="#C62828"/>
<text x="1187" y="330" font-size="10" fill="#C62828" dominant-baseline="hanging">≥ 3 адреса</text>
<text x="24" y="590" font-size="14" fill="#2E3A45" dominant-baseline="hanging">&gt; Порядок создания: Организация → vDC → Edge → (vApp + внешние IP) → ВМ / Штурвал.</text>
<text x="24" y="616" font-size="14" fill="#2E3A45" dominant-baseline="hanging">&gt; SNAT — не отдельный сервис, а операция modify у Edge.</text>
<text x="24" y="642" font-size="14" fill="#2E3A45" dominant-baseline="hanging">&gt; vDC и Edge могут быть переиспользованы существующие.</text>
<path d="M980 606 L1040 606" fill="none" stroke="#455A64" stroke-width="2" stroke-linejoin="round"/>
<polygon points="1040,606 1031.0,609.78 1031.0,602.22" fill="#455A64"/>
<text x="1052" y="597" font-size="11" fill="#2E3A45" dominant-baseline="hanging">Сплошная линия — обязательная зависимость</text>
<text x="1052" y="613" font-size="10" fill="#7A8794" dominant-baseline="hanging">(ссылка на UUID существующего инстанса)</text>
<path d="M980 650 L1040 650" fill="none" stroke="#7A8794" stroke-width="2" stroke-dasharray="9 6" stroke-linejoin="round"/>
<polygon points="1040,650 1031.0,653.78 1031.0,646.22" fill="#7A8794"/>
<text x="1052" y="641" font-size="11" fill="#2E3A45" dominant-baseline="hanging">Пунктир — выделение ресурса / modify / предусловие</text>
<rect x="980" y="684" width="12" height="12" rx="3" fill="#1565C0"/>
<text x="996" y="682" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Тенант</text>
<rect x="1038" y="684" width="12" height="12" rx="3" fill="#F57C00"/>
<text x="1054" y="682" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Ресурсы</text>
<rect x="1102" y="684" width="12" height="12" rx="3" fill="#5E35B1"/>
<text x="1118" y="682" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Сеть</text>
<rect x="1147" y="684" width="12" height="12" rx="3" fill="#2E7D32"/>
<text x="1163" y="682" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Потребители</text>
<rect x="1237" y="684" width="12" height="12" rx="3" fill="#9E9E9E"/>
<text x="1253" y="682" font-size="10" fill="#2E3A45" dominant-baseline="hanging">Адреса</text>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

+43
View File
@@ -0,0 +1,43 @@
flowchart LR
classDef base fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
classDef net fill:#ede7f6,stroke:#5e35b1,stroke-width:2px;
classDef compute fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
classDef target fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;
classDef note fill:#fafafa,stroke:#9e9e9e,stroke-width:1px,stroke-dasharray: 4 4;
subgraph S1 ["Шаг 1. Тенант"]
ORG["<b>Организация (vcOrg: 19)</b><br/>• Тип: iaas / saas"]:::base
end
subgraph S2 ["Шаг 2. Пул ресурсов"]
VDC["<b>Виртуальный ДЦ (vcVdc: 21)</b><br/>• cpuGuaranteed: 0% / 50% / 80%<br/>• Пул: cpuAllocated, memAllocated, storage"]:::compute
end
subgraph S3 ["Шаг 3. Сетевой периметр"]
EDGE["<b>Шлюз Edge (vcNsxt: 22)</b><br/>• AVI Load Balancer<br/>• routed-сеть 10.10.102.0/24"]:::net
end
subgraph S4 ["Шаг 4. Среда запуска и адреса"]
VAPP["<b>Каталог vApp (26)</b><br/>(контейнер для ВМ)"]:::compute
IP["<b>Внешние IP</b><br/>• vcOrg modify / 25<br/>• квота адресов (ipSpace)"]:::note
end
subgraph S5 ["Шаг 5. Потребители"]
VM["<b>Виртуальная машина (28)</b><br/>• vmCpu, vmRam, vmDisk"]:::target
SHTURVAL["<b>Кластер Штурвал (150)</b><br/>• sizingPolicy, sizingDisk, count"]:::target
end
ORG -->|"organizationUid"| VDC
VDC -->|"vdcUid"| EDGE
ORG -.->|"modify: vIPConfigure"| IP
EDGE -.->|"modify: SNAT (ipSpaceName)"| IP
VDC -->|"vdcUid"| VAPP
EDGE -->|"nsxtUid"| VAPP
VAPP -->|"vappUid"| VM
IP -.->|"внешний IP"| VM
VDC -->|"vdcUid"| SHTURVAL
EDGE -->|"nsxtUid"| SHTURVAL
IP -.->|"внешний IP (&gt;= 3)"| SHTURVAL
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

+1
View File
@@ -0,0 +1 @@
<svg id="mermaid-svg" width="100%" xmlns="http://www.w3.org/2000/svg" class="flowchart" style="max-width: 2338.671875px;" viewBox="0 0 2338.671875 552" role="graphics-document document" aria-roledescription="flowchart-v2" xmlns:xlink="http://www.w3.org/1999/xlink"><style xmlns="http://www.w3.org/1999/xhtml">@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css");</style><style>#mermaid-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg .error-icon{fill:#552222;}#mermaid-svg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg .marker{fill:#333333;stroke:#333333;}#mermaid-svg .marker.cross{stroke:#333333;}#mermaid-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg p{margin:0;}#mermaid-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg .cluster-label text{fill:#333;}#mermaid-svg .cluster-label span{color:#333;}#mermaid-svg .cluster-label span p{background-color:transparent;}#mermaid-svg .label text,#mermaid-svg span{fill:#333;color:#333;}#mermaid-svg .node rect,#mermaid-svg .node circle,#mermaid-svg .node ellipse,#mermaid-svg .node polygon,#mermaid-svg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg .rough-node .label text,#mermaid-svg .node .label text,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-anchor:middle;}#mermaid-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg .rough-node .label,#mermaid-svg .node .label,#mermaid-svg .image-shape .label,#mermaid-svg .icon-shape .label{text-align:center;}#mermaid-svg .node.clickable{cursor:pointer;}#mermaid-svg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg .arrowheadPath{fill:#333333;}#mermaid-svg .edgePath .path{stroke:#333333;stroke-width:1px;}#mermaid-svg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg .cluster text{fill:#333;}#mermaid-svg .cluster span{color:#333;}#mermaid-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg rect.text{fill:none;stroke-width:0;}#mermaid-svg .icon-shape,#mermaid-svg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg .icon-shape p,#mermaid-svg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg .icon-shape .label rect,#mermaid-svg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg .node .neo-node{stroke:#9370DB;}#mermaid-svg [data-look="neo"].node rect,#mermaid-svg [data-look="neo"].cluster rect,#mermaid-svg [data-look="neo"].node polygon{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mermaid-svg [data-look="neo"].swimlane.cluster rect{filter:none;}#mermaid-svg [data-look="neo"].node path{stroke:#9370DB;stroke-width:1px;}#mermaid-svg [data-look="neo"].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mermaid-svg [data-look="neo"].node .neo-line path{stroke:#9370DB;filter:none;}#mermaid-svg [data-look="neo"].node circle{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mermaid-svg [data-look="neo"].node circle .state-start{fill:#000000;}#mermaid-svg [data-look="neo"].icon-shape .icon{fill:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mermaid-svg [data-look="neo"].icon-shape .icon-neo path{stroke:#9370DB;filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mermaid-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg .base&gt;*{fill:rgb(225, 245, 254)!important;stroke:rgb(2, 136, 209)!important;stroke-width:2px!important;}#mermaid-svg .base span{fill:rgb(225, 245, 254)!important;stroke:rgb(2, 136, 209)!important;stroke-width:2px!important;}#mermaid-svg .net&gt;*{fill:rgb(237, 231, 246)!important;stroke:rgb(94, 53, 177)!important;stroke-width:2px!important;}#mermaid-svg .net span{fill:rgb(237, 231, 246)!important;stroke:rgb(94, 53, 177)!important;stroke-width:2px!important;}#mermaid-svg .compute&gt;*{fill:rgb(255, 243, 224)!important;stroke:rgb(245, 124, 0)!important;stroke-width:2px!important;}#mermaid-svg .compute span{fill:rgb(255, 243, 224)!important;stroke:rgb(245, 124, 0)!important;stroke-width:2px!important;}#mermaid-svg .target&gt;*{fill:rgb(232, 245, 233)!important;stroke:rgb(46, 125, 50)!important;stroke-width:2px!important;}#mermaid-svg .target span{fill:rgb(232, 245, 233)!important;stroke:rgb(46, 125, 50)!important;stroke-width:2px!important;}#mermaid-svg .note&gt;*{fill:rgb(250, 250, 250)!important;stroke:rgb(158, 158, 158)!important;stroke-width:1px!important;stroke-dasharray:4,4!important;}#mermaid-svg .note span{fill:rgb(250, 250, 250)!important;stroke:rgb(158, 158, 158)!important;stroke-width:1px!important;stroke-dasharray:4,4!important;}</style><g><marker id="mermaid-svg_flowchart-v2-pointEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-pointStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="4.5" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-pointEnd-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="11.5" refY="7" markerUnits="userSpaceOnUse" markerWidth="10.5" markerHeight="14" orient="auto"><path d="M 0 0 L 11.5 7 L 0 14 z" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-pointStart-margin" class="marker flowchart-v2" viewBox="0 0 11.5 14" refX="1" refY="7" markerUnits="userSpaceOnUse" markerWidth="11.5" markerHeight="14" orient="auto"><polygon points="0,7 11.5,14 11.5,0" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-circleEnd" class="marker flowchart-v2" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-circleStart" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-circleEnd-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refY="5" refX="12.25" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-circleStart-margin" class="marker flowchart-v2" viewBox="0 0 10 10" refX="-2" refY="5" markerUnits="userSpaceOnUse" markerWidth="14" markerHeight="14" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 0; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-crossEnd" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-crossStart" class="marker cross flowchart-v2" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"/></marker><marker id="mermaid-svg_flowchart-v2-crossEnd-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="17.7" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5;"/></marker><marker id="mermaid-svg_flowchart-v2-crossStart-margin" class="marker cross flowchart-v2" viewBox="0 0 15 15" refX="-3.5" refY="7.5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 1,1 L 14,14 M 1,14 L 14,1" class="arrowMarkerPath" style="stroke-width: 2.5; stroke-dasharray: 1, 0;"/></marker><g class="root"><g class="clusters"><g class="cluster " id="mermaid-svg-S5" data-look="classic"><rect style="" x="2020.671875" y="60" width="310" height="449"/><g class="cluster-label " transform="translate(2101.4140625, 60)"><foreignObject width="148.515625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel "><p>Шаг 5. Потребители</p></span></div></foreignObject></g></g><g class="cluster " id="mermaid-svg-S4" data-look="classic"><rect style="" x="1539.546875" y="37" width="298.9375" height="322"/><g class="cluster-label " transform="translate(1573.4921875, 37)"><foreignObject width="231.046875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel "><p>Шаг 4. Среда запуска и адреса</p></span></div></foreignObject></g></g><g class="cluster " id="mermaid-svg-S3" data-look="classic"><rect style="" x="979.546875" y="98" width="310" height="276"/><g class="cluster-label " transform="translate(1039.9453125, 98)"><foreignObject width="189.203125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel "><p>Шаг 3. Сетевой периметр</p></span></div></foreignObject></g></g><g class="cluster " id="mermaid-svg-S2" data-look="classic"><rect style="" x="471.03125" y="178" width="310" height="366"/><g class="cluster-label " transform="translate(549.703125, 178)"><foreignObject width="152.65625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel "><p>Шаг 2. Пул ресурсов</p></span></div></foreignObject></g></g><g class="cluster " id="mermaid-svg-S1" data-look="classic"><rect style="" x="8" y="8" width="301.84375" height="403"/><g class="cluster-label " transform="translate(107.265625, 8)"><foreignObject width="103.3125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5;"><span class="nodeLabel "><p>Шаг 1. Тенант</p></span></div></foreignObject></g></g></g><g class="edgePaths"><path d="M201.574,265L219.619,281.5C237.664,298,273.754,331,305.231,347.5C336.708,364,363.573,364,390.438,364C417.302,364,444.167,364,461.099,364C478.031,364,485.031,364,488.531,364L492.031,364" id="mermaid-svg-L_ORG_VDC_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_ORG_VDC_0" data-points="W3sieCI6MjAxLjU3MzcwOTIzOTEzMDQ0LCJ5IjoyNjV9LHsieCI6MzA5Ljg0Mzc1LCJ5IjozNjR9LHsieCI6MzkwLjQzNzUsInkiOjM2NH0seyJ4Ijo0NzEuMDMxMjUsInkiOjM2NH0seyJ4Ijo0OTYuMDMxMjUsInkiOjM2NH1d" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M740.311,277L747.098,271.833C753.884,266.667,767.458,256.333,790.787,251.167C814.117,246,847.203,246,880.289,246C913.375,246,946.461,246,966.504,246C986.547,246,993.547,246,997.047,246L1000.547,246" id="mermaid-svg-L_VDC_EDGE_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_VDC_EDGE_0" data-points="W3sieCI6NzQwLjMxMDkxMTAxNjk0OTEsInkiOjI3N30seyJ4Ijo3ODEuMDMxMjUsInkiOjI0Nn0seyJ4Ijo4ODAuMjg5MDYyNSwieSI6MjQ2fSx7IngiOjk3OS41NDY4NzUsInkiOjI0Nn0seyJ4IjoxMDA0LjU0Njg3NSwieSI6MjQ2fV0=" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M195.481,187L214.541,166.667C233.602,146.333,271.723,105.667,304.216,85.333C336.708,65,363.573,65,390.438,65C417.302,65,444.167,65,483.432,65C522.698,65,574.365,65,626.031,65C677.698,65,729.365,65,771.741,65C814.117,65,847.203,65,880.289,65C913.375,65,946.461,65,988.837,65C1031.214,65,1082.88,65,1134.547,65C1186.214,65,1237.88,65,1284.547,78.333C1331.214,91.667,1372.88,118.333,1414.547,131.667C1456.214,145,1497.88,145,1522.221,144.484C1546.561,143.968,1553.575,142.935,1557.082,142.419L1560.59,141.903" id="mermaid-svg-L_ORG_IP_0" class=" edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_ORG_IP_0" data-points="W3sieCI6MTk1LjQ4MDU5MDA2MjExMTgsInkiOjE4N30seyJ4IjozMDkuODQzNzUsInkiOjY1fSx7IngiOjM5MC40Mzc1LCJ5Ijo2NX0seyJ4Ijo0NzEuMDMxMjUsInkiOjY1fSx7IngiOjYyNi4wMzEyNSwieSI6NjV9LHsieCI6NzgxLjAzMTI1LCJ5Ijo2NX0seyJ4Ijo4ODAuMjg5MDYyNSwieSI6NjV9LHsieCI6OTc5LjU0Njg3NSwieSI6NjV9LHsieCI6MTEzNC41NDY4NzUsInkiOjY1fSx7IngiOjEyODkuNTQ2ODc1LCJ5Ijo2NX0seyJ4IjoxNDE0LjU0Njg3NSwieSI6MTQ1fSx7IngiOjE1MzkuNTQ2ODc1LCJ5IjoxNDV9LHsieCI6MTU2NC41NDY4NzUsInkiOjE0MS4zMjAzMDEwNjYyNzY0fV0=" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1258.154,183L1263.387,180.333C1268.619,177.667,1279.083,172.333,1305.148,158.667C1331.214,145,1372.88,123,1414.547,112C1456.214,101,1497.88,101,1522.221,101.516C1546.561,102.032,1553.575,103.065,1557.082,103.581L1560.59,104.097" id="mermaid-svg-L_EDGE_IP_0" class=" edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EDGE_IP_0" data-points="W3sieCI6MTI1OC4xNTQ0Njk5MzY3MDksInkiOjE4M30seyJ4IjoxMjg5LjU0Njg3NSwieSI6MTY3fSx7IngiOjE0MTQuNTQ2ODc1LCJ5IjoxMDF9LHsieCI6MTUzOS41NDY4NzUsInkiOjEwMX0seyJ4IjoxNTY0LjU0Njg3NSwieSI6MTA0LjY3OTY5ODkzMzcyMzZ9XQ==" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M756.031,399.226L760.198,400.355C764.365,401.484,772.698,403.742,793.408,404.871C814.117,406,847.203,406,880.289,406C913.375,406,946.461,406,988.837,406C1031.214,406,1082.88,406,1134.547,406C1186.214,406,1237.88,406,1284.547,390.333C1331.214,374.667,1372.88,343.333,1414.547,327.667C1456.214,312,1497.88,312,1525.684,310.741C1553.487,309.482,1567.428,306.964,1574.398,305.704L1581.368,304.445" id="mermaid-svg-L_VDC_VAPP_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_VDC_VAPP_0" data-points="W3sieCI6NzU2LjAzMTI1LCJ5IjozOTkuMjI1ODA2NDUxNjEyOX0seyJ4Ijo3ODEuMDMxMjUsInkiOjQwNn0seyJ4Ijo4ODAuMjg5MDYyNSwieSI6NDA2fSx7IngiOjk3OS41NDY4NzUsInkiOjQwNn0seyJ4IjoxMTM0LjU0Njg3NSwieSI6NDA2fSx7IngiOjEyODkuNTQ2ODc1LCJ5Ijo0MDZ9LHsieCI6MTQxNC41NDY4NzUsInkiOjMxMn0seyJ4IjoxNTM5LjU0Njg3NSwieSI6MzEyfSx7IngiOjE1ODUuMzA0Njg3NSwieSI6MzAzLjczNDMxOTQ2NDc3MTF9XQ==" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1264.547,224.194L1268.714,223.495C1272.88,222.796,1281.214,221.398,1306.214,220.699C1331.214,220,1372.88,220,1414.547,220C1456.214,220,1497.88,220,1528.067,224.067C1558.253,228.135,1576.96,236.27,1586.313,240.337L1595.666,244.405" id="mermaid-svg-L_EDGE_VAPP_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EDGE_VAPP_0" data-points="W3sieCI6MTI2NC41NDY4NzUsInkiOjIyNC4xOTM1NDgzODcwOTY3N30seyJ4IjoxMjg5LjU0Njg3NSwieSI6MjIwfSx7IngiOjE0MTQuNTQ2ODc1LCJ5IjoyMjB9LHsieCI6MTUzOS41NDY4NzUsInkiOjIyMH0seyJ4IjoxNTk5LjMzNDM3NSwieSI6MjQ2fV0=" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1792.727,285L1800.353,285C1807.979,285,1823.232,285,1846.04,285C1868.849,285,1899.214,285,1929.578,285C1959.943,285,1990.307,285,2019.627,274.237C2048.947,263.474,2077.222,241.949,2091.36,231.186L2105.498,220.423" id="mermaid-svg-L_VAPP_VM_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_VAPP_VM_0" data-points="W3sieCI6MTc5Mi43MjY1NjI1LCJ5IjoyODV9LHsieCI6MTgzOC40ODQzNzUsInkiOjI4NX0seyJ4IjoxOTI5LjU3ODEyNSwieSI6Mjg1fSx7IngiOjIwMjAuNjcxODc1LCJ5IjoyODV9LHsieCI6MjEwOC42ODAzNDk1NzYyNzEsInkiOjIxOH1d" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1813.484,123L1817.651,123C1821.818,123,1830.151,123,1849.5,123C1868.849,123,1899.214,123,1929.578,123C1959.943,123,1990.307,123,2009.015,124.001C2027.723,125.001,2034.773,127.003,2038.299,128.004L2041.824,129.004" id="mermaid-svg-L_IP_VM_0" class=" edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_IP_VM_0" data-points="W3sieCI6MTgxMy40ODQzNzUsInkiOjEyM30seyJ4IjoxODM4LjQ4NDM3NSwieSI6MTIzfSx7IngiOjE5MjkuNTc4MTI1LCJ5IjoxMjN9LHsieCI6MjAyMC42NzE4NzUsInkiOjEyM30seyJ4IjoyMDQ1LjY3MTg3NSwieSI6MTMwLjA5Njc3NDE5MzU0ODM4fV0=" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M756.031,441.161L760.198,443.634C764.365,446.108,772.698,451.054,793.408,453.527C814.117,456,847.203,456,880.289,456C913.375,456,946.461,456,988.837,456C1031.214,456,1082.88,456,1134.547,456C1186.214,456,1237.88,456,1284.547,456C1331.214,456,1372.88,456,1414.547,456C1456.214,456,1497.88,456,1543.625,456C1589.37,456,1639.193,456,1689.016,456C1738.839,456,1788.661,456,1828.755,456C1868.849,456,1899.214,456,1929.578,456C1959.943,456,1990.307,456,2011.337,453.434C2032.368,450.869,2044.063,445.738,2049.911,443.172L2055.759,440.607" id="mermaid-svg-L_VDC_SHTURVAL_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_VDC_SHTURVAL_0" data-points="W3sieCI6NzU2LjAzMTI1LCJ5Ijo0NDEuMTYxMjkwMzIyNTgwNn0seyJ4Ijo3ODEuMDMxMjUsInkiOjQ1Nn0seyJ4Ijo4ODAuMjg5MDYyNSwieSI6NDU2fSx7IngiOjk3OS41NDY4NzUsInkiOjQ1Nn0seyJ4IjoxMTM0LjU0Njg3NSwieSI6NDU2fSx7IngiOjEyODkuNTQ2ODc1LCJ5Ijo0NTZ9LHsieCI6MTQxNC41NDY4NzUsInkiOjQ1Nn0seyJ4IjoxNTM5LjU0Njg3NSwieSI6NDU2fSx7IngiOjE2ODkuMDE1NjI1LCJ5Ijo0NTZ9LHsieCI6MTgzOC40ODQzNzUsInkiOjQ1Nn0seyJ4IjoxOTI5LjU3ODEyNSwieSI6NDU2fSx7IngiOjIwMjAuNjcxODc1LCJ5Ijo0NTZ9LHsieCI6MjA1OS40MjE4NzUsInkiOjQzOX1d" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1264.547,303.871L1268.714,305.726C1272.88,307.581,1281.214,311.29,1306.214,325.812C1331.214,340.333,1372.88,365.667,1414.547,378.333C1456.214,391,1497.88,391,1543.625,391C1589.37,391,1639.193,391,1689.016,391C1738.839,391,1788.661,391,1828.755,391C1868.849,391,1899.214,391,1929.578,391C1959.943,391,1990.307,391,2008.99,390.932C2027.672,390.865,2034.672,390.729,2038.172,390.661L2041.673,390.594" id="mermaid-svg-L_EDGE_SHTURVAL_0" class=" edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_EDGE_SHTURVAL_0" data-points="W3sieCI6MTI2NC41NDY4NzUsInkiOjMwMy44NzA5Njc3NDE5MzU1fSx7IngiOjEyODkuNTQ2ODc1LCJ5IjozMTV9LHsieCI6MTQxNC41NDY4NzUsInkiOjM5MX0seyJ4IjoxNTM5LjU0Njg3NSwieSI6MzkxfSx7IngiOjE2ODkuMDE1NjI1LCJ5IjozOTF9LHsieCI6MTgzOC40ODQzNzUsInkiOjM5MX0seyJ4IjoxOTI5LjU3ODEyNSwieSI6MzkxfSx7IngiOjIwMjAuNjcxODc1LCJ5IjozOTF9LHsieCI6MjA0NS42NzE4NzUsInkiOjM5MC41MTYxMjkwMzIyNTgwNX1d" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/><path d="M1725.143,174L1744.033,200.667C1762.924,227.333,1800.704,280.667,1834.776,307.333C1868.849,334,1899.214,334,1929.578,334C1959.943,334,1990.307,334,2009.027,335.232C2027.746,336.465,2034.82,338.929,2038.357,340.161L2041.895,341.394" id="mermaid-svg-L_IP_SHTURVAL_0" class=" edge-thickness-normal edge-pattern-dotted edge-thickness-normal edge-pattern-solid flowchart-link" style=";" data-edge="true" data-et="edge" data-id="L_IP_SHTURVAL_0" data-points="W3sieCI6MTcyNS4xNDMxNDI3NzI1MTIsInkiOjE3NH0seyJ4IjoxODM4LjQ4NDM3NSwieSI6MzM0fSx7IngiOjE5MjkuNTc4MTI1LCJ5IjozMzR9LHsieCI6MjAyMC42NzE4NzUsInkiOjMzNH0seyJ4IjoyMDQ1LjY3MTg3NSwieSI6MzQyLjcwOTY3NzQxOTM1NDh9XQ==" data-look="classic" marker-end="url(#mermaid-svg_flowchart-v2-pointEnd)"/></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(390.4375, 364)"><g class="label" data-id="L_ORG_VDC_0" transform="translate(-55.59375, -12)"><foreignObject width="111.1875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>organizationUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(880.2890625, 246)"><g class="label" data-id="L_VDC_EDGE_0" transform="translate(-24.453125, -12)"><foreignObject width="48.90625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>vdcUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(880.2890625, 65)"><g class="label" data-id="L_ORG_IP_0" transform="translate(-74.2578125, -12)"><foreignObject width="148.515625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>modify: vIPConfigure</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1414.546875, 101)"><g class="label" data-id="L_EDGE_IP_0" transform="translate(-100, -24)"><foreignObject width="200" height="48"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="edgeLabel "><p>modify: SNAT (ipSpaceName)</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1134.546875, 406)"><g class="label" data-id="L_VDC_VAPP_0" transform="translate(-24.453125, -12)"><foreignObject width="48.90625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>vdcUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1414.546875, 220)"><g class="label" data-id="L_EDGE_VAPP_0" transform="translate(-26.6796875, -12)"><foreignObject width="53.359375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>nsxtUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1929.578125, 285)"><g class="label" data-id="L_VAPP_VM_0" transform="translate(-29.3515625, -12)"><foreignObject width="58.703125" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>vappUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1929.578125, 123)"><g class="label" data-id="L_IP_VM_0" transform="translate(-42.671875, -12)"><foreignObject width="85.34375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>внешний IP</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1414.546875, 456)"><g class="label" data-id="L_VDC_SHTURVAL_0" transform="translate(-24.453125, -12)"><foreignObject width="48.90625" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>vdcUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1689.015625, 391)"><g class="label" data-id="L_EDGE_SHTURVAL_0" transform="translate(-26.6796875, -12)"><foreignObject width="53.359375" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>nsxtUid</p></span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(1929.578125, 334)"><g class="label" data-id="L_IP_SHTURVAL_0" transform="translate(-66.09375, -12)"><foreignObject width="132.1875" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="labelBkg" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="edgeLabel "><p>внешний IP (&gt;= 3)</p></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default base " id="mermaid-svg-flowchart-ORG-0" data-look="classic" transform="translate(158.921875, 226)"><rect class="basic label-container" style="fill:#e1f5fe !important;stroke:#0288d1 !important;stroke-width:2px !important" x="-125.921875" y="-39" width="251.84375" height="78"/><g class="label" style="" transform="translate(-95.921875, -24)"><rect/><foreignObject width="191.84375" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel "><p><b>Организация (vcOrg: 19)</b><br />• Тип: iaas / saas</p></span></div></foreignObject></g></g><g class="node default compute " id="mermaid-svg-flowchart-VDC-1" data-look="classic" transform="translate(626.03125, 364)"><rect class="basic label-container" style="fill:#fff3e0 !important;stroke:#f57c00 !important;stroke-width:2px !important" x="-130" y="-87" width="260" height="174"/><g class="label" style="" transform="translate(-100, -72)"><rect/><foreignObject width="200" height="144"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel "><p><b>Виртуальный ДЦ (vcVdc: 21)</b><br />• cpuGuaranteed: 0% / 50% / 80%<br />• Пул: cpuAllocated, memAllocated, storage</p></span></div></foreignObject></g></g><g class="node default net " id="mermaid-svg-flowchart-EDGE-2" data-look="classic" transform="translate(1134.546875, 246)"><rect class="basic label-container" style="fill:#ede7f6 !important;stroke:#5e35b1 !important;stroke-width:2px !important" x="-130" y="-63" width="260" height="126"/><g class="label" style="" transform="translate(-100, -48)"><rect/><foreignObject width="200" height="96"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel "><p><b>Шлюз Edge (vcNsxt: 22)</b><br />• AVI Load Balancer<br />• routed-сеть 10.10.102.0/24</p></span></div></foreignObject></g></g><g class="node default compute " id="mermaid-svg-flowchart-VAPP-3" data-look="classic" transform="translate(1689.015625, 285)"><rect class="basic label-container" style="fill:#fff3e0 !important;stroke:#f57c00 !important;stroke-width:2px !important" x="-103.7109375" y="-39" width="207.421875" height="78"/><g class="label" style="" transform="translate(-73.7109375, -24)"><rect/><foreignObject width="147.421875" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel "><p><b>Каталог vApp (26)</b><br />(контейнер для ВМ)</p></span></div></foreignObject></g></g><g class="node default note " id="mermaid-svg-flowchart-IP-4" data-look="classic" transform="translate(1689.015625, 123)"><rect class="basic label-container" style="fill:#fafafa !important;stroke:#9e9e9e !important;stroke-width:1px !important;stroke-dasharray:4 4 !important" x="-124.46875" y="-51" width="248.9375" height="102"/><g class="label" style="" transform="translate(-94.46875, -36)"><rect/><foreignObject width="188.9375" height="72"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;"><span class="nodeLabel "><p><b>Внешние IP</b><br />• vcOrg modify / 25<br />• квота адресов (ipSpace)</p></span></div></foreignObject></g></g><g class="node default target " id="mermaid-svg-flowchart-VM-5" data-look="classic" transform="translate(2175.671875, 167)"><rect class="basic label-container" style="fill:#e8f5e9 !important;stroke:#2e7d32 !important;stroke-width:2px !important" x="-130" y="-51" width="260" height="102"/><g class="label" style="" transform="translate(-100, -36)"><rect/><foreignObject width="200" height="72"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel "><p><b>Виртуальная машина (28)</b><br />• vmCpu, vmRam, vmDisk</p></span></div></foreignObject></g></g><g class="node default target " id="mermaid-svg-flowchart-SHTURVAL-6" data-look="classic" transform="translate(2175.671875, 388)"><rect class="basic label-container" style="fill:#e8f5e9 !important;stroke:#2e7d32 !important;stroke-width:2px !important" x="-130" y="-51" width="260" height="102"/><g class="label" style="" transform="translate(-100, -36)"><rect/><foreignObject width="200" height="72"><div xmlns="http://www.w3.org/1999/xhtml" style="display: table; white-space: break-spaces; line-height: 1.5; max-width: 200px; text-align: center; width: 200px;"><span class="nodeLabel "><p><b>Кластер Штурвал (150)</b><br />• sizingPolicy, sizingDisk, count</p></span></div></foreignObject></g></g></g></g></g><defs><filter id="mermaid-svg-drop-shadow" height="130%" width="130%"><feDropShadow dx="4" dy="4" stdDeviation="0" flood-opacity="0.06" flood-color="#000000"/></filter></defs><defs><filter id="mermaid-svg-drop-shadow-small" height="150%" width="150%"><feDropShadow dx="2" dy="2" stdDeviation="0" flood-opacity="0.06" flood-color="#000000"/></filter></defs></svg>

After

Width:  |  Height:  |  Size: 32 KiB

+303
View File
@@ -0,0 +1,303 @@
#!/usr/bin/env python3
"""Генератор схемы зависимостей облачных сервисов (PNG + SVG).
Содержание подтверждено YAML-контрактами generated/dev/resources_yaml.
PNG рисуется в 2x и уменьшается (сглаживание), SVG — вектор.
"""
import os
from PIL import Image, ImageDraw, ImageFont
W, H = 1600, 720
SS = 2 # supersample
FONT_R = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
FONT_B = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
DARK = "#1A1A1A"
GRAY = "#5F6B76"
MUTED = "#7A8794"
TEXT = "#2E3A45"
RED = "#C62828"
LINE = "#455A64"
COLS = [
(16, "Шаг 1. Тенант", "базовый уровень изоляции", "#1565C0", "#E8F3FB"),
(340, "Шаг 2. Пул ресурсов", "CPU / RAM / Storage", "#E65100", "#FDF1E3"),
(664, "Шаг 3. Сетевой периметр", "маршрутизация и балансировка", "#4527A0", "#EEEAF8"),
(988, "Шаг 4. Среда запуска", "контейнер и внешние адреса", "#EF6C00", "#FDF4E3"),
(1312, "Шаг 5. Потребители", "что разворачивается в итоге", "#2E7D32", "#E9F5EA"),
]
COL_W = 270
# блоки: (x, y, w, h, заголовок-строки, подпись, строки, бейдж, рамка, фон, пунктир)
BLOCKS = [
(31, 150, 240, 150, ["Организация", "в Cloud Director"], "Сервис 19 · vcOrg",
["Типы: iaas / saas"], None, "#0288D1", "#FFFFFF", False),
(355, 130, 240, 230, ["Виртуальный", "датацентр (vDC)"], "Сервис 21 · vcVdc",
["cpuGuaranteed: 0% / 50% / 80%", "cpuAllocated, memAllocated", "storageConfig"],
"здесь выделяются CPU / RAM / Диск", "#F57C00", "#FFF3E0", False),
(679, 120, 240, 200, ["Сетевой шлюз", "периметра (Edge)"], "Сервис 22 · vcNsxt",
["AVI Load Balancer", "routed-сеть 10.10.102.0/24"],
"SNAT настраивается операцией modify", "#5E35B1", "#EDE7F6", False),
(1003, 100, 240, 110, ["Каталог ВМ (vApp)"], "Сервис 26 · vapp",
["логический контейнер для ВМ"], None, "#F57C00", "#FFF3E0", False),
(1003, 245, 180, 150, ["Внешние IP"], "Сервис 25",
["выделение: vcOrg", "NAT: сервис 25", "квота IP (ipSpace)"],
"здесь выделяются IP", "#9E9E9E", "#FAFAFA", True),
(1327, 100, 240, 120, ["Виртуальная машина"], "Сервис 28 · vc_vm_v3",
["vmCpu · vmRam · vmDisk"], None, "#2E7D32", "#E8F5E9", False),
(1327, 255, 240, 170, ["Kubernetes кластер", "Штурвал"], "Сервис 150",
["sizingPolicy · sizingDisk", "· count"], "требует ALB + VS ≥ 3",
"#2E7D32", "#E8F5E9", False),
]
# связи: (точки, цвет, толщина, пунктир, подпись, позиция подписи, вертикальная)
EDGES = [
([(271, 225), (355, 225)], LINE, 2, False, "organizationUid", (313, 262), True),
([(595, 200), (679, 200)], LINE, 2, False, "vdcUid", (637, 186), False),
([(500, 130), (500, 92), (961, 92), (961, 140), (1003, 140)], LINE, 2, False,
"vdcUid", (706, 96), False),
([(919, 180), (1003, 180)], LINE, 2, False, "nsxtUid", (961, 166), False),
([(1243, 140), (1327, 140)], LINE, 2, False, "vappUid", (1285, 126), False),
([(500, 360), (500, 470), (1447, 470), (1447, 425)], LINE, 2, False,
"vdcUid", (1180, 456), False),
([(799, 320), (799, 505), (1467, 505), (1467, 425)], LINE, 2, False,
"nsxtUid", (1180, 491), False),
([(151, 300), (151, 540), (1050, 540), (1050, 395)], MUTED, 2, True,
"modify: vIPConfigure", (161, 526), False),
([(870, 320), (870, 400), (1140, 400), (1140, 395)], MUTED, 2, True,
"modify: SNAT", (880, 386), False),
([(1050, 210), (1050, 245)], MUTED, 2, True, "требует Каталог", (1060, 218), False),
([(1183, 270), (1255, 270), (1255, 160), (1327, 160)], MUTED, 2, True,
"внешний IP", (1187, 222), False),
([(1183, 350), (1327, 350)], RED, 2, True, "≥ 3 адреса", (1187, 330), False),
]
CAPTION = [
"Порядок создания: Организация → vDC → Edge → (vApp + внешние IP) → ВМ / Штурвал.",
"SNAT — не отдельный сервис, а операция modify у Edge.",
"vDC и Edge могут быть переиспользованы существующие.",
]
def op_rect(x, y, w, h, fill, outline, lw=2, r=10, dash=False):
return ("rect", (x, y, w, h, fill, outline, lw, r, dash))
def op_text(x, y, s, size, color, bold=False, anchor="lt", vertical=False):
return ("text", (x, y, s, size, color, bold, anchor, vertical))
def op_path(pts, color, lw, dash):
return ("path", (pts, color, lw, dash))
def op_dot(x, y, r, color):
return ("dot", (x, y, r, color))
def build():
ops = []
for cx, title, sub, accent, tint in COLS:
ops.append(op_rect(cx, 8, COL_W, 552, tint, "#FFFFFF", 0, 12))
ops.append(op_rect(cx, 14, COL_W, 44, accent, accent, 0, 8))
ops.append(op_text(cx + COL_W / 2, 25, title, 15, "#FFFFFF", True, "mt"))
ops.append(op_text(cx + COL_W / 2, 68, sub, 12, MUTED, False, "mt"))
for x, y, w, h, titles, sub, lines, badge, border, fill, dashed in BLOCKS:
ops.append(op_rect(x, y, w, h, fill, border, 2, 10, dashed))
ops.append(op_rect(x + 12, y + 12, 22, 22, border, border, 0, 5))
tx = x + 42
ty = y + 12
for i, t in enumerate(titles):
ops.append(op_text(tx, ty + i * 20, t, 15, DARK, True))
sy = ty + len(titles) * 20
ops.append(op_text(tx, sy, sub, 11, GRAY))
ly = sy + 22
for ln in lines:
ops.append(op_dot(x + 20, ly + 7, 2.4, border))
ops.append(op_text(x + 30, ly, ln, 10, TEXT))
ly += 20
if badge:
ops.append(op_rect(x + 12, y + h - 42, w - 24, 30, "#FFFFFF", border, 1, 6))
ops.append(op_text(x + w / 2, y + h - 32, badge, 10, border, False, "mt"))
for pts, color, lw, dash, label, pos, vertical in EDGES:
ops.append(op_path(pts, color, lw, dash))
ops.append(op_text(pos[0], pos[1], label, 10, color, False,
"lt" if not vertical else "lt", vertical))
for i, s in enumerate(CAPTION):
ops.append(op_text(24, 590 + i * 26, "> " + s, 14, TEXT))
ops.append(op_path([(980, 606), (1040, 606)], LINE, 2, False))
ops.append(op_text(1052, 597, "Сплошная линия — обязательная зависимость", 11, TEXT))
ops.append(op_text(1052, 613, "(ссылка на UUID существующего инстанса)", 10, MUTED))
ops.append(op_path([(980, 650), (1040, 650)], MUTED, 2, True))
ops.append(op_text(1052, 641, "Пунктир — выделение ресурса / modify / предусловие", 11, TEXT))
swatches = [("#1565C0", "Тенант"), ("#F57C00", "Ресурсы"), ("#5E35B1", "Сеть"),
("#2E7D32", "Потребители"), ("#9E9E9E", "Адреса")]
sx = 980
for color, name in swatches:
ops.append(op_rect(sx, 684, 12, 12, color, color, 0, 3))
ops.append(op_text(sx + 16, 682, name, 10, TEXT))
sx += 20 + int(len(name) * 6.4)
return ops
def accent_of(border):
return border
def dash_segments(p1, p2, on=9, off=6):
(x1, y1), (x2, y2) = p1, p2
segs, total = [], ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
if total == 0:
return segs
ux, uy = (x2 - x1) / total, (y2 - y1) / total
pos = 0.0
while pos < total:
end = min(pos + on, total)
segs.append(((x1 + ux * pos, y1 + uy * pos), (x1 + ux * end, y1 + uy * end)))
pos = end + off
return segs
def arrow_head(d, tip, tail, color, size=9):
(tx, ty), (px, py) = tip, tail
dx, dy = tx - px, ty - py
n = (dx * dx + dy * dy) ** 0.5 or 1
ux, uy = dx / n, dy / n
bx, by = tx - ux * size, ty - uy * size
ox, oy = -uy * size * 0.42, ux * size * 0.42
d.polygon([(tx, ty), (bx + ox, by + oy), (bx - ox, by - oy)], fill=color)
def draw_polyline(d, pts, color, lw, dash):
for a, b in zip(pts, pts[1:]):
segs = dash_segments(a, b) if dash else [(a, b)]
for s in segs:
d.line([s[0], s[1]], fill=color, width=lw)
if len(pts) >= 2:
tail = pts[-2]
tip = pts[-1]
dx, dy = tip[0] - tail[0], tip[1] - tail[1]
n = (dx * dx + dy * dy) ** 0.5 or 1
shrink = 9
tip2 = (tip[0] - dx / n * shrink, tip[1] - dy / n * shrink)
d.line([tail, tip2], fill=color, width=lw)
arrow_head(d, tip, tail, color)
def render_png(ops, path):
img = Image.new("RGB", (W * SS, H * SS), "#FFFFFF")
d = ImageDraw.Draw(img)
fonts = {}
def fnt(size, bold):
key = (size, bold)
if key not in fonts:
fonts[key] = ImageFont.truetype(FONT_B if bold else FONT_R, size * SS)
return fonts[key]
for kind, a in ops:
if kind == "rect":
x, y, w, h, fill, outline, lw, r, dashed = a
box = [x * SS, y * SS, (x + w) * SS, (y + h) * SS]
if dashed:
d.rounded_rectangle(box, radius=r * SS, fill=fill)
for a1, b1 in [((x, y), (x + w, y)), ((x + w, y), (x + w, y + h)),
((x + w, y + h), (x, y + h)), ((x, y + h), (x, y))]:
for s in dash_segments((a1[0] * SS, a1[1] * SS), (b1[0] * SS, b1[1] * SS),
on=8 * SS, off=6 * SS):
d.line([s[0], s[1]], fill=outline, width=max(1, lw * SS))
else:
d.rounded_rectangle(box, radius=r * SS, fill=fill,
outline=outline if lw else None,
width=max(1, lw * SS) if lw else 0)
elif kind == "text":
x, y, s, size, color, bold, anchor, vertical = a
f = fnt(size, bold)
if vertical:
bbox = f.getbbox(s)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
tmp = Image.new("RGBA", (tw + 4, th + 4), (0, 0, 0, 0))
ImageDraw.Draw(tmp).text((-bbox[0] + 2, -bbox[1] + 2), s, font=f, fill=color)
tmp = tmp.rotate(90, expand=True)
img.paste(tmp, (int(x * SS - tmp.width / 2), int(y * SS - tmp.height / 2)), tmp)
else:
d.text((x * SS, y * SS), s, font=f, fill=color, anchor=anchor)
elif kind == "path":
pts, color, lw, dash = a
draw_polyline(d, [(px * SS, py * SS) for px, py in pts], color,
max(1, lw * SS), dash)
elif kind == "dot":
x, y, r, color = a
d.ellipse([(x - r) * SS, (y - r) * SS, (x + r) * SS, (y + r) * SS], fill=color)
img = img.resize((W, H), Image.LANCZOS)
img.save(path)
return path
def render_svg(ops, path):
out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" '
f'viewBox="0 0 {W} {H}" font-family="DejaVu Sans, Verdana, sans-serif">',
f'<rect width="{W}" height="{H}" fill="#FFFFFF"/>']
def esc(s):
return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
for kind, a in ops:
if kind == "rect":
x, y, w, h, fill, outline, lw, r, dashed = a
stroke = f' stroke="{outline}" stroke-width="{lw}"' if lw else ""
dash = ' stroke-dasharray="9 6"' if dashed else ""
out.append(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{r}" '
f'fill="{fill}"{stroke}{dash}/>')
elif kind == "text":
x, y, s, size, color, bold, anchor, vertical = a
weight = ' font-weight="bold"' if bold else ""
if vertical:
ta = ' text-anchor="middle" dominant-baseline="middle"'
transform = f' transform="rotate(-90 {x} {y})"'
else:
ta = (' text-anchor="middle" dominant-baseline="hanging"' if anchor == "mt"
else ' dominant-baseline="hanging"')
transform = ""
out.append(f'<text x="{x}" y="{y}" font-size="{size}" fill="{color}"'
f'{weight}{ta}{transform}>{esc(s)}</text>')
elif kind == "path":
pts, color, lw, dash = a
d = " ".join(("M" if i == 0 else "L") + f"{p[0]} {p[1]}"
for i, p in enumerate(pts))
dash = ' stroke-dasharray="9 6"' if dash else ""
out.append(f'<path d="{d}" fill="none" stroke="{color}" '
f'stroke-width="{lw}"{dash} stroke-linejoin="round"/>')
(tx, ty), (px, py) = pts[-1], pts[-2]
dx, dy = tx - px, ty - py
n = (dx * dx + dy * dy) ** 0.5 or 1
ux, uy = dx / n, dy / n
s_ = 9
bx, by = tx - ux * s_, ty - uy * s_
ox, oy = -uy * s_ * 0.42, ux * s_ * 0.42
out.append(f'<polygon points="{tx},{ty} {bx + ox},{by + oy} '
f'{bx - ox},{by - oy}" fill="{color}"/>')
elif kind == "dot":
x, y, r, color = a
out.append(f'<circle cx="{x}" cy="{y}" r="{r}" fill="{color}"/>')
out.append("</svg>")
with open(path, "w", encoding="utf-8") as fh:
fh.write("\n".join(out))
return path
if __name__ == "__main__":
# Файлы кладём рядом со скриптом: docs/diagrams/
base = os.path.dirname(os.path.abspath(__file__))
ops = build()
print(render_png(ops, os.path.join(base, "infra_services_diagram.png")))
print(render_svg(ops, os.path.join(base, "infra_services_diagram.svg")))
+2 -1
View File
@@ -56,9 +56,10 @@ nav:
- Руководства:
- Быстрый старт: 30_registry/guides/getting-started.md
- Основы Terraform: 30_registry/guides/terraform-basics.md
- Как работает провайдер (отличия от Terraform): 30_registry/guides/provider-behavior.md
- Структура манифестов: 30_registry/guides/terraform-structure.md
- Глоссарий: 30_registry/guides/glossary.md
- Проверенные примеры:
- PostgreSQL: curated/postgres/pg_user_db.md
- Пайплайн vDC → Edge → IP → SNAT: curated/pipeline/vdc_edge_ip_snat.md
- Пайплайн vDC → Edge → IP → SNAT → Штурвал: curated/pipeline/vdc_edge_ip_snat.md
- Ресурсы-модификаторы (IP организации, SNAT): curated/modifiers/org_ip_and_snat.md