- Шапка: логотип Kubernetes, ссылка на главную, выпадающее меню API (Swagger/ReDoc/Health), переключатель светлой/тёмной темы (localStorage). - Светлая тема в синей гамме; выравнивание кнопки темы в ряду с пилюлями. - Дашборд: единая карточка ошибки health/stats, подсказка Docker/Podman, поле container_cli в GET /stats, total_workers_from_meta всегда число (0 без meta). - Правки кластеров, job_store, compose, документация и частичные шаблоны.
543 lines
24 KiB
Markdown
543 lines
24 KiB
Markdown
# Описание 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-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, **журнал** в реальном времени, **«Отменить»** прерывает текущую команду), **старт/стоп** кластера с тем же журналом (фоновые **POST …/start** и **…/stop**), таблица **последних заданий** с кнопкой **«Очистить завершённые»** (**DELETE /api/v1/jobs**), модалка узлов/подов; шапка — пилюли, Swagger / ReDoc / Health в отдельных окнах. |
|
||
| `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/documentation.html` (README).
|
||
|
||
**kubectl на хосте не обязателен:** бинарник есть в образе; узлы и поды доступны через API (**`GET /api/v1/clusters/{name}/workloads`**) и веб-UI. Для интерактивной консоли из корня репозитория при запущенном compose: **`make docker kubectl CLUSTER=<имя>`** (или **`make podman kubectl …`**), внутри контейнера kubeconfig — **`/work/clusters/<имя>/kubeconfig`**. Перезапуск только веб-сервиса без `down`/`up`: **`make docker restart`** / **`make podman 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` | Список кластеров |
|
||
| 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}/workloads` | Узлы и поды (`kubectl`) |
|
||
| 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**); при переполнении старые строки вытесняются.
|
||
- Для **`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).
|
||
При `KIND_K8S_SKIP_VERSION_LIST=1` список пустой.
|
||
|
||
**Пример ответа 200:**
|
||
|
||
```json
|
||
{
|
||
"tags": ["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,
|
||
"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` показывается действие «Стоп», иначе при необходимости подъёма — «Старт».
|
||
|
||
---
|
||
|
||
## 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` (ответ — тело файла, `Content-Disposition` с именем `kubeconfig-{name}.yaml`).
|
||
|
||
**Ошибка 404:** файла нет в `clusters/{name}/`.
|
||
|
||
**Ошибка 400:** некорректное имя кластера.
|
||
|
||
---
|
||
|
||
## 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}
|
||
|
||
Детали и попытка `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): см. раздел «Веб-интерфейс и статика» выше.
|