Files
KindClustersDashboard/app/docs/api_routes.md
T
inecs 909129d8ed Веб-UI кластера: страница деталей, kubectl по карточкам, мета 3 колонки
- Страница /cluster/<имя>: сводка, ресурсы узлов полосами, отдельные карточки на каждый kubectl get … -o json, рестарт пода.
- API: overview с блоками k8s_*, POST pods/restart, расширенный набор ресурсов (-A).
- Панель: спиннер загрузки, правки дашборда и стилей; документация api_routes, compose и прочие сопутствующие изменения.
2026-04-04 09:13:08 +03:00

698 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание REST API веб-интерфейса Kind Clusters Dashboard
**Базовый префикс:** `/api/v1`
**Автор:** Сергей Антропов — [devops.org.ru](https://devops.org.ru)
## Как смотреть документацию
| Способ | URL / путь |
|--------|------------|
| Swagger UI (OpenAPI) | `http://127.0.0.1:<порт>/docs` (порт на хосте по умолчанию **8080**, см. `KIND_K8S_WEB_PORT`; 6000 на хосте блокируется Chrome) |
| ReDoc | `http://127.0.0.1:<порт>/redoc` |
| Health (JSON) | `http://127.0.0.1:<порт>/api/v1/health` |
| Документация проекта | `http://127.0.0.1:<порт>/documentation` — по умолчанию **README** (`GET /api/v1/docs/readme`); ссылки на `app/docs/*.md` открываются в той же странице (`?path=...` + `GET /api/v1/docs/file`); рендер **Markdown** в браузере (**marked** + **DOMPurify** из `app/static/js/vendor/`, без CDN) |
| Этот файл | `app/docs/api_routes.md` в репозитории |
С **веб-панели** (`GET /`) пункты меню **Swagger**, **ReDoc** и **Health** вызывают `window.open` с именами окон `kind_swagger`, `kind_redoc`, `kind_health` (отдельное окно, повторный клик переиспользует то же окно). Пункт **Документация** открывает `GET /documentation` в той же вкладке.
## Веб-интерфейс и статика (не JSON)
| Маршрут | Описание |
|---------|----------|
| `GET /` | HTML-панель: единая карточка «панель + среда», статистика, **ссылка с имени кластера на** `GET /cluster/<имя>` (сводка ресурсов, kubectl, действия), **старт/стоп** кластера с тем же журналом (фоновые **POST …/start** и **…/stop**), модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
| `GET /cluster/{name}` | HTML **страница кластера**: донаты «Ресурсы узлов (сводка)», карточки узлов, **таблицы Kubernetes** из JSON (`kubectl get … -o json`), кнопка **Рестарт** у подов (**`POST …/pods/restart`**), те же кнопки действий, что в таблице на главной; данные — **`GET /api/v1/clusters/{name}/overview`** (автообновление с интервалом панели). |
| `GET /documentation` | HTML-оболочка; **`documentation.js`**: без `path`**`GET /api/v1/docs/readme`**, с `?path=app/docs/…`**`GET /api/v1/docs/file`**; разбор Markdown из **`/static/js/vendor/`** (marked, DOMPurify). Каждая секция по **H2****одна карточка** (заголовок h2 и содержимое до следующего h2 вместе). Заголовок вкладки браузера: **«Документация — …»** + текст **первого H1** документа + имя приложения (`KIND_K8S_APP_TITLE` на `body`). В шапке на этой странице активна только **Документация**; **Панель** как обычная пилюля (на дашборде активна **Панель**). Путь к README: `KIND_K8S_README_PATH` или `README.md` рядом с `app/`; в образе — `/opt/kind-k8s/README.md`. |
| `GET /ui` | Редирект **307** на `/` (удобный ярлык). |
| `GET /static/…` | CSS (`style.css`), скрипты панели (`js/dashboard.js`) и документации (`js/documentation.js`); базовый URL API задаётся атрибутом `data-api-base` на `<body>` (по умолчанию `/api/v1`). |
Шаблоны: `app/templates/base.html` (шапка, навигация), `app/templates/dashboard.html` (контент панели), `app/templates/cluster_detail.html` (страница кластера), `app/templates/documentation.html` (README).
**kubectl на хосте не обязателен:** бинарник есть в образе; узлы и поды доступны через API и веб-UI. Внутри контейнера веб-приложения `kubectl` использует временный kubeconfig с `server` через **`host.docker.internal:<порт>`** (см. `kubeconfig_patch.py`, `extra_hosts` в compose). Скачивание для хоста — **`GET …/kubeconfig`** (файл **`kubeconfig.host`** при наличии). Для консоли: **`make docker kubectl CLUSTER=<имя>`** — **`/work/clusters/<имя>/kubeconfig`**; при сбое попробуйте kubectl **с хоста** с **`clusters/<имя>/kubeconfig.host`**. Перезапуск веб-сервиса: **`make docker restart`**. Подробности — **README.md**.
---
## Сводка маршрутов API
| Метод | Путь | Кратко |
|-------|------|--------|
| GET | `/api/v1/health` | Среда: kind, kubectl, движок контейнеров |
| GET | `/api/v1/docs/readme` | Текст **README.md** (`text/markdown`; страница `/documentation` без `path`) |
| GET | `/api/v1/docs/file` | Текст одного **`.md`** под `app/docs/` (query `path=app/docs/…`; для `/documentation?path=…`) |
| GET | `/api/v1/versions` | Теги `kindest/node` (Docker Hub) или пусто при `KIND_K8S_SKIP_VERSION_LIST` |
| GET | `/api/v1/stats` | Сводка для дашборда |
| GET | `/api/v1/clusters` | Список кластеров (поле `has_provision_log` — есть ли `clusters/<имя>/provision_log.json`) |
| POST | `/api/v1/clusters` | Создание в фоне (**202** + `job_id`) |
| POST | `/api/v1/clusters/{name}/start` | Запуск в фоне (**202** + `job_id`, поле `mode`: `containers` или `kind_config`); журнал — `GET /jobs/{job_id}` |
| POST | `/api/v1/clusters/{name}/stop` | Остановка узлов в фоне (**202** + `job_id`, `mode`: `stop`); журнал — `GET /jobs/{job_id}` |
| GET | `/api/v1/clusters/{name}` | Детали + `kubectl get nodes` при наличии kubeconfig |
| GET | `/api/v1/clusters/{name}/kubeconfig` | Скачать файл kubeconfig |
| GET | `/api/v1/clusters/{name}/provision-log` | Полный журнал последнего **create_cluster** / **start_cluster** (JSON с диска) |
| GET | `/api/v1/clusters/{name}/workloads` | Узлы и поды (`kubectl`) |
| GET | `/api/v1/clusters/{name}/overview` | Сводка для страницы UI: метрики узлов, агрегаты, блоки **`k8s_*`** (JSON из `kubectl get … -o json`) |
| POST | `/api/v1/clusters/{name}/pods/restart` | Удалить под (`kubectl delete pod`) для мягкого рестарта |
| DELETE | `/api/v1/clusters/{name}` | Удалить кластер и данные в `clusters/` |
| GET | `/api/v1/jobs` | Последние задания (`progress_log` в ответе пустой — полный журнал только в GET по `job_id`) |
| GET | `/api/v1/jobs/{job_id}` | Статус задания + полный хвост `progress_log` (лимит см. `KIND_K8S_JOB_API_LOG_MAX_LINES`) |
| DELETE | `/api/v1/jobs` | Удалить из памяти **завершённые** задания (`removed` — число записей) |
| POST | `/api/v1/jobs/{job_id}/cancel` | Прервать задание: активная команда завершается принудительно (скачивание образа, создание кластера, старт/стоп узла) |
### Фоновые задания (jobs)
- Хранятся **только в памяти** процесса uvicorn; после перезапуска контейнера история обнуляется.
- В памяти держится не более **200** записей; при превышении старые задания вытесняются (`app/core/job_store.py`).
- Снимок заданий сохраняется в JSON в каталоге **`clusters/`** (файл **`kind_k8s_jobs.json`** на томе с хоста) — после перезапуска контейнера список восстанавливается. Записи в статусе **queued**/**running** при старте помечаются как **failed** (процесс уже не выполняется). Путь переопределяется переменной **`KIND_K8S_JOBS_JSON`**.
- Создание кластера: `POST /api/v1/clusters` → опрос `GET /api/v1/jobs/{job_id}` (как в веб-UI).
- В ответе задания поля **`progress_stage`** (текст этапа) и **`progress_percent`** (0–100) обновляются во время создания.
- В **GET /api/v1/jobs** (список) поле **`progress_log`** всегда **пустой массив** — меньше трафика; полный хвост — в **GET /api/v1/jobs/{job_id}** (лимит строк: `KIND_K8S_JOB_API_LOG_MAX_LINES`, по умолчанию **5000**).
- Буфер строк в памяти на задание: `KIND_K8S_JOB_LOG_MAX_LINES` (по умолчанию **2500**); при переполнении старые строки вытесняются. Для **create_cluster** и **start_cluster** полный журнал без обрезки дополнительно сохраняется в **`clusters/<имя>/provision_log.json`** (перезапись при каждом завершении такого задания); чтение — **GET /api/v1/clusters/{name}/provision-log**.
- Для **`docker pull`**: если в справке **`docker pull --help`** объявлен флаг **`--progress`**, при **`KIND_K8S_DOCKER_PULL_PLAIN=1`** вызывается **`--progress=plain`** без PTY; на старых CLI флаг не передаётся (нет строки «unknown flag» в журнале). Для **podman** и **kind** — псевдо-TTY по `KIND_K8S_STREAM_PTY`, из строк убираются ANSI-коды.
- Тип задания **`kind`**: `create_cluster`, `start_cluster` (подъём по сохранённому конфигу), `start_containers` (запуск уже созданных узлов), `stop_containers` (остановка узлов).
- Статус **`cancelled`** — запрошена отмена (`POST .../cancel`); дочерний процесс текущей команды получает принудительное завершение.
---
## GET /api/v1/health
Проверка: `kind`/`kubectl` в PATH и ответ движка контейнеров (`docker info` / `podman info` по `CONTAINER_CLI`).
`status`: `ok` — всё готово к созданию кластеров; `degraded` — чего-то не хватает (см. поля ниже).
**Пример ответа 200 (JSON):**
```json
{
"status": "ok",
"kind_in_path": true,
"kubectl_in_path": true,
"container_cli": "docker",
"container_engine_ok": true,
"container_engine_detail": null
}
```
**Если сокет Docker недоступен:**
```json
{
"status": "degraded",
"kind_in_path": true,
"kubectl_in_path": true,
"container_cli": "docker",
"container_engine_ok": false,
"container_engine_detail": "Cannot connect to the Docker daemon..."
}
```
---
## GET /api/v1/docs/readme
Сырое содержимое **README.md** проекта в кодировке UTF-8, заголовок **`Content-Type: text/markdown; charset=utf-8`**.
Используется страницей **`GET /documentation`**: скрипт `documentation.js` загружает текст и превращает его в HTML через **marked** и **DOMPurify** (файлы лежат в репозитории: `app/static/js/vendor/`, без внешних CDN).
**Ошибка 404:** файл не найден. В Compose смонтируйте `./README.md:/opt/kind-k8s/README.md:ro`, задайте `KIND_K8S_README_PATH` или пересоберите образ (`COPY README.md`). См. `app/core/readme_doc.py`.
---
## GET /api/v1/docs/file
Параметр запроса **`path`** — относительный путь вида **`app/docs/<имя>.md`**. Допускаются только такие пути (префикс `app/docs/`, расширение `.md`, без `..`); иначе **404**.
Тело ответа — UTF-8 Markdown, **`Content-Type: text/markdown; charset=utf-8`**.
**Пример запроса:**
```http
GET /api/v1/docs/file?path=app%2Fdocs%2Fapi_routes.md HTTP/1.1
Host: 127.0.0.1:8080
Accept: text/markdown
```
**Пример начала тела ответа 200** (не JSON, текст Markdown):
```markdown
# Описание REST API веб-интерфейса Kind Clusters Dashboard
**Базовый префикс:** `/api/v1`
```
Логика проверки пути: `app/core/readme_doc.py` (`resolve_app_docs_markdown`).
---
## GET /api/v1/versions
Теги `kindest/node` с Docker Hub для выпадающего списка в UI.
- Первым элементом всегда идёт **`latest`** (плавающий тег «самый новый образ»).
- Далее стабильные семверы **`vX.Y.Z`** без суффиксов (`-rc` и т.п.), **от новых к старым**, минимум **1.19.0**.
- Полный список семверов зависит от числа обходимых страниц API Hub: переменная **`KIND_K8S_HUB_TAGS_MAX_PAGES`** (по умолчанию в коде — 120 страниц, максимум при явной настройке — 500). Раньше UI обрезал список до 100 пунктов — сейчас отображаются все пришедшие теги.
При `KIND_K8S_SKIP_VERSION_LIST=1` список пустой.
**Пример ответа 200:**
```json
{
"tags": ["latest", "v1.32.0", "v1.31.4"],
"skipped": false
}
```
**Пример при пропуске загрузки:**
```json
{
"tags": [],
"skipped": true,
"reason": "KIND_K8S_SKIP_VERSION_LIST"
}
```
---
## GET /api/v1/stats
Сводная статистика для дашборда и **метрики запущенных узлов kind** (один вызов `docker stats` / `podman stats` на набор имён контейнеров `имя-control-plane`, `имя-worker`…). Поле **`container_cli`** совпадает с **`CONTAINER_CLI`** в среде приложения (`docker` или `podman`) — по нему UI показывает, что **все** узлы kind и сбор метрик идут через выбранный CLI.
**Пример ответа 200:**
```json
{
"container_cli": "docker",
"kind_clusters_count": 1,
"local_cluster_dirs_count": 1,
"total_workers_from_meta": 2,
"jobs_total": 3,
"jobs_recent_failed": 0,
"cluster_resources": [
{
"cluster_name": "dev",
"nodes": [
{
"container_name": "dev-control-plane",
"cpu_percent": "2.50%",
"memory_usage": "512MiB / 7.7GiB",
"memory_percent": "6.50%",
"net_io": "1.2MB / 800kB",
"block_io": "2GB / 150MB",
"pids": 245
},
{
"container_name": "dev-worker",
"cpu_percent": "1.00%",
"memory_usage": "380MiB / 7.7GiB",
"memory_percent": "4.80%",
"net_io": "512kB / 400kB",
"block_io": "500MB / 50MB",
"pids": 198
}
],
"note": null
}
],
"cluster_resources_error": null,
"aggregate_cluster_resources": {
"nodes_count": 2,
"cpu_ring": 1.8,
"cpu_label": "ср. 1.8%",
"memory_percent_ring": 5.7,
"memory_percent_label": "ср. 5.7%",
"memory_used_ratio_ring": 5.8,
"memory_used_ratio_label": "ср. 5.8%",
"network_ring": 12.5,
"network_label": "всего 1.71 MiB",
"disk_ring": 45.0,
"disk_label": "всего 2.5 GiB"
}
}
```
- `container_cli` — активный движок для kind и `docker stats` / `podman stats` (как в GET /health).
- `total_workers_from_meta` — целое **≥ 0**; **0**, если ни в одном `meta.json` нет поля `worker_nodes` или оно не число.
- `jobs_total` — число заданий в текущей памяти процесса (не более 200).
- `jobs_recent_failed` — сколько заданий в этом хранилище сейчас в статусе `failed` (не «последние N», а счётчик по всему снимку).
- `cluster_resources` — по каждому имени из `kind get clusters`; если узлы остановлены, `nodes` пустой, в `note` пояснение.
- `cluster_resources_error` — если CLI (`CONTAINER_CLI`) не найден в PATH и т.п.; тогда `cluster_resources` может быть пустым, а `aggregate_cluster_resources` — нулевая сводка.
- `aggregate_cluster_resources` — агрегаты по **запущенным** узлам для донат-диаграмм на главной: средние проценты CPU/RAM, средняя доля RAM из строки `memory_usage`, кольца сети/диска по суммарному I/O (шкала 0–100 относительно порога 8 GiB на полное кольцо).
---
## GET /api/v1/clusters
Список: объединение `kind get clusters` и подкаталогов `clusters/*` (без дубликатов).
**Пример ответа 200 (массив):**
```json
[
{
"name": "dev",
"registered_in_kind": true,
"kind_nodes_running": true,
"has_local_kubeconfig": true,
"has_provision_log": true,
"meta": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"node_image": "kindest/node:v1.29.4",
"worker_nodes": 2,
"kubeconfig_patched_for_host": true
}
}
]
```
- `kind_nodes_running` — в списке процессов контейнерного движка есть узлы kind с префиксом имени (`имя-control-plane`, `имя-worker`…); для UI: при `true` показывается действие «Стоп», иначе при необходимости подъёма — «Старт».
- `has_provision_log` — в каталоге кластера есть файл **`provision_log.json`** (последнее завершённое задание создания или старта по сохранённому конфигу).
---
## GET /api/v1/jobs
Список последних фоновых заданий, от новых к старым. Поле **`progress_log`** в каждом элементе **пустое** — используйте **GET /api/v1/jobs/{job_id}** для журнала.
**Query:** `limit` (1–200, по умолчанию **30**).
**Пример ответа 200 (массив `JobView`):**
```json
[
{
"job_id": "abc123",
"kind": "create_cluster",
"status": "success",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": "Кластер создан",
"result": { "cluster_name": "dev", "kubernetes_version_tag": "v1.29.4" },
"progress_stage": null,
"progress_percent": null,
"progress_log": []
}
]
```
---
## DELETE /api/v1/jobs
Удаляет из памяти записи заданий со статусом **success**, **failed** или **cancelled**. Задания **queued** и **running** не удаляются.
**Пример ответа 200:**
```json
{
"removed": 4
}
```
---
## POST /api/v1/jobs/{job_id}/cancel
Прервать фоновое задание (`create_cluster`, `start_cluster`, `start_containers`, `stop_containers`). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
**Пример ответа 200:**
```json
{
"job_id": "a1b2…",
"cancel_requested": true,
"message": "Запрошено прерывание; текущая команда будет остановлена, задание перейдёт в отменено"
}
```
**Ошибка 400:** задание уже завершено.
**Ошибка 404:** неизвестный `job_id`.
---
## GET /api/v1/clusters/{name}/kubeconfig
Скачать kubeconfig для **kubectl на машине пользователя** (ответ — тело файла, `Content-Disposition`: `kubeconfig-{name}.yaml`).
При каждом запросе копируется **`clusters/{name}/kubeconfig`** и патчится `server=https://<KIND_K8S_KUBECONFIG_CLIENT_HOST или localhost>:<порт>` (порт из `docker port … 6443/tcp`). Если патч не удалился — отдаётся сохранённый **`kubeconfig.host`** или сырой **`kubeconfig`**.
**Ошибка 404:** нет ни `kubeconfig.host`, ни `kubeconfig` в `clusters/{name}/`.
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/clusters/{name}/provision-log
Содержимое **`clusters/<имя>/provision_log.json`**: полный журнал строк последнего завершённого задания **create_cluster** или **start_cluster** (включая успех, ошибку и отмену), плюс метаданные (`job_id`, `kind`, `status`, `message`, `result`, `finished_at_utc`).
**Ошибка 404:** файла нет (кластер ещё не создавали/не стартовали с сохранением журнала, или каталог без записи).
**Ошибка 400:** некорректное имя кластера.
**Пример ответа 200 (JSON):**
```json
{
"version": 1,
"job_id": "a1b2c3d4",
"kind": "create_cluster",
"cluster_name": "dev",
"finished_at_utc": "2026-04-04T14:30:00+00:00",
"status": "success",
"message": "Кластер создан",
"lines": [
"Creating cluster \"dev\" ...",
" • Ensuring node image (kindest/node:v1.29.4) 🖼"
],
"result": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"kubeconfig_path": "/work/clusters/dev/kubeconfig"
}
}
```
---
## GET /api/v1/clusters/{name}/workloads
`kubectl get nodes -o wide` и `kubectl get pods -A` по сохранённому kubeconfig.
**Пример ответа 200:**
```json
{
"cluster_name": "dev",
"nodes_rc": 0,
"nodes_output": "NAME STATUS ROLES ...",
"pods_rc": 0,
"pods_output": "NAMESPACE NAME READY STATUS ...",
"error": null
}
```
Если kubeconfig нет: `error` — строка вида **`Нет сохранённого kubeconfig в clusters/<имя>/`**, поля вывода kubectl могут быть `null`.
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/clusters/{name}/overview
Единый ответ для **страницы кластера** (`GET /cluster/{name}`): флаги и `meta` как в списке кластеров, **метрики узлов** (docker/podman), **`aggregate_resources`** (как в `GET /stats` для донатов), данные Kubernetes в виде **`kubectl get … -o json`** (разбор `items` на фронтенде в таблицы).
- При отсутствии kubeconfig: **`kubeconfig_error`** — строка-пояснение; блоки **`k8s_*`** недоступны (см. ниже).
- Поля **`nodes_rc` / `nodes_output`**, **`pods_rc` / `pods_output`**, … — устаревший текстовый вывод; в текущей версии API могут быть **`null`** (UI использует только JSON-блоки).
- Блоки **`k8s_nodes`**, **`k8s_namespaces`**, **`k8s_pods`**, **`k8s_deployments`**, **`k8s_statefulsets`**, **`k8s_daemonsets`**, **`k8s_replicasets`**, **`k8s_jobs`**, **`k8s_cronjobs`**, **`k8s_services`**, **`k8s_ingresses`** (ресурс **`ingresses.networking.k8s.io -A`**), **`k8s_pvcs`** — объекты вида **`K8sListJsonBlock`**; для ресурсов в namespace везде **`kubectl get … -A`** (все пространства имён).
- **`ok`**: `true` при успешном `kubectl`;
- **`rc`**: код возврата;
- **`items`**: массив объектов из `kubectl` (как в API Kubernetes);
- **`message`**: текст ошибки при `ok: false`.
- **`resources_error`** — ошибка сбора метрик контейнерного CLI (как `cluster_resources_error` в `/stats`).
- **`cluster_resources`** — один блок `KindClusterResources` (узлы с полями `cpu_percent`, `memory_usage`, …).
**Пример ответа 200 (фрагмент):**
```json
{
"cluster_name": "dev",
"registered_in_kind": true,
"kind_nodes_running": true,
"has_local_kubeconfig": true,
"has_provision_log": true,
"meta": { "worker_nodes": 2, "kubernetes_version_tag": "v1.29.4" },
"resources_error": null,
"cluster_resources": {
"cluster_name": "dev",
"nodes": [
{
"container_name": "dev-control-plane",
"cpu_percent": "2.10%",
"memory_usage": "800MiB / 7.7GiB",
"memory_percent": "10.14%",
"net_io": "1.2MB / 800kB",
"block_io": "0B / 0B",
"pids": 120
}
],
"note": null
},
"aggregate_resources": {
"nodes_count": 3,
"cpu_ring": 5.2,
"cpu_label": "5.2%",
"memory_percent_ring": 12.0,
"memory_percent_label": "12%",
"memory_used_ratio_ring": 45.0,
"memory_used_ratio_label": "2.1 / 4.6 GiB",
"network_ring": 8.0,
"network_label": "↓ 10 MB",
"disk_ring": 3.0,
"disk_label": "R/W 2 MB"
},
"kubeconfig_error": null,
"k8s_nodes": {
"ok": true,
"rc": 0,
"items": [{ "metadata": { "name": "dev-control-plane" }, "status": { "conditions": [] } }],
"message": null
},
"k8s_pods": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_deployments": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_statefulsets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_daemonsets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_services": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_ingresses": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_namespaces": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_replicasets": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_jobs": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_cronjobs": { "ok": true, "rc": 0, "items": [], "message": null },
"k8s_pvcs": { "ok": true, "rc": 0, "items": [], "message": null }
}
```
**Ошибка 400:** некорректное имя кластера.
---
## POST /api/v1/clusters/{name}/pods/restart
Мягкий **рестарт пода**: выполняется **`kubectl delete pod`** в указанном namespace (под пересоздаётся, если им управляет Deployment/ReplicaSet и т.д.).
**Тело запроса (JSON):**
```json
{
"namespace": "default",
"pod": "my-app-7d4f8b9-xk2cp"
}
```
**Пример ответа 200:**
```json
{
"ok": true,
"cluster_name": "dev",
"namespace": "default",
"pod": "my-app-7d4f8b9-xk2cp",
"message": null
}
```
**Ошибка 400:** неверное имя кластера, namespace или pod (валидация как в Kubernetes).
**Ошибка 502:** `kubectl delete` завершился с ненулевым кодом (текст stderr в теле ответа от сервера).
---
## GET /api/v1/clusters/{name}
Детали и попытка `kubectl get nodes -o wide` с **сохранённого** `clusters/{name}/kubeconfig` (если файл есть).
**Пример ответа 200:**
```json
{
"name": "dev",
"registered_in_kind": true,
"has_local_kubeconfig": true,
"kubeconfig_path": "/work/clusters/dev/kubeconfig",
"meta": { "worker_nodes": 2 },
"kubectl_get_nodes_rc": 0,
"kubectl_get_nodes": "NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME\n..."
}
```
---
## POST /api/v1/clusters
Создание кластера **в фоне** (ответ **202**).
**Тело запроса (JSON):**
```json
{
"name": "dev",
"kubernetes_version": "1.29.4",
"workers": 2
}
```
**Пример ответа 202:**
```json
{
"job_id": "a1b2c3d4e5f6...",
"status": "queued",
"message": "Создание кластера выполняется в фоне; опросите GET /api/v1/jobs/{job_id}"
}
```
**Ошибка 400:** невалидное имя кластера или тело не проходит валидацию Pydantic.
**Ошибка 409 (кластер уже есть в kind):**
```json
{
"detail": "Кластер с таким именем уже есть в kind"
}
```
---
## POST /api/v1/clusters/{name}/start
Запуск кластера двумя сценариями (оба с **202** и `job_id`, журнал в `GET /jobs/{job_id}`):
1. Кластер **зарегистрирован** в kind — задание **`start_containers`** (поочерёдный запуск узлов, `mode`: **`containers`**).
2. В kind кластера **нет**, но есть сохранённый **`clusters/<имя>/kind-config.yaml`** — задание **`start_cluster`** (`mode`: **`kind_config`**), логика как при создании (в т.ч. скачивание образа при необходимости).
**Пример ответа 202 (запуск узлов):**
```json
{
"job_id": "cafebabe...",
"status": "queued",
"mode": "containers",
"message": "Запуск узлов; опросите GET /api/v1/jobs/{job_id}"
}
```
**Пример ответа 202 (подъём по конфигу):**
```json
{
"job_id": "deadbeef...",
"status": "queued",
"mode": "kind_config",
"message": "Подъём кластера по сохранённому конфигу; опросите GET /api/v1/jobs/{job_id}"
}
```
**Ошибка 400:** некорректное имя или нет ни кластера в kind, ни `kind-config.yaml` в `clusters/<имя>/`.
---
## POST /api/v1/clusters/{name}/stop
Остановка узлов кластера **в фоне** (задание **`stop_containers`**). Запись кластера в kind **не удаляется**; позже снова **POST …/start**.
**Пример ответа 202:**
```json
{
"job_id": "baba...",
"status": "queued",
"mode": "stop",
"message": "Остановка узлов; опросите GET /api/v1/jobs/{job_id}"
}
```
**Ошибка 400:** некорректное имя кластера.
---
## GET /api/v1/jobs/{job_id}
Статус фонового задания создания.
**В процессе (пример 200):**
```json
{
"job_id": "a1b2...",
"kind": "create_cluster",
"status": "running",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": null,
"result": null,
"progress_stage": "Создание узлов кластера",
"progress_percent": 45,
"progress_log": [
"Подготовка конфигурации",
"Using default tag: latest",
"Status: Downloaded newer image for kindest/node:v1.29.4",
"Creating cluster \"dev\" ..."
]
}
```
**Успех (пример 200):**
```json
{
"job_id": "a1b2...",
"kind": "create_cluster",
"status": "success",
"cluster_name": "dev",
"created_at_utc": "2026-04-04T12:00:00+00:00",
"message": "Кластер создан",
"progress_log": ["Завершение", "Узлы готовы: condition met"],
"result": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"node_image": "kindest/node:v1.29.4",
"workers": 2,
"kubeconfig_path": "/work/clusters/dev/kubeconfig",
"kubeconfig_patched_for_host": true,
"nodes_ready": true,
"nodes_ready_message": "..."
}
}
```
**Ошибка 404:**
```json
{
"detail": "Задание не найдено"
}
```
---
## DELETE /api/v1/clusters/{name}
`kind delete cluster` и удаление каталога `clusters/{name}/`.
**Пример ответа 200:**
```json
{
"name": "dev",
"kind_delete_ok": true,
"summary": "kind delete: OK; удалена папка /work/clusters/dev"
}
```
**Ошибка 400:** некорректное имя кластера.
**Ошибка 500:** логическая ошибка удаления (тело с `detail`).
---
## GET /
HTML-дашборд (не JSON): см. раздел «Веб-интерфейс и статика» выше.