- Шапка: логотип Kubernetes, ссылка на главную, выпадающее меню API (Swagger/ReDoc/Health), переключатель светлой/тёмной темы (localStorage). - Светлая тема в синей гамме; выравнивание кнопки темы в ряду с пилюлями. - Дашборд: единая карточка ошибки health/stats, подсказка Docker/Podman, поле container_cli в GET /stats, total_workers_from_meta всегда число (0 без meta). - Правки кластеров, job_store, compose, документация и частичные шаблоны.
24 KiB
Описание REST API веб-интерфейса Kind Clusters Dashboard
Базовый префикс: /api/v1
Автор: Сергей Антропов — 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):
{
"status": "ok",
"kind_in_path": true,
"kubectl_in_path": true,
"container_cli": "docker",
"container_engine_ok": true,
"container_engine_detail": null
}
Если сокет Docker недоступен:
{
"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.
Пример запроса:
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):
# Описание 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:
{
"tags": ["v1.32.0", "v1.31.4"],
"skipped": false
}
Пример при пропуске загрузки:
{
"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:
{
"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 (массив):
[
{
"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):
[
{
"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:
{
"removed": 4
}
POST /api/v1/jobs/{job_id}/cancel
Прервать фоновое задание (create_cluster, start_cluster, start_containers, stop_containers). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
Пример ответа 200:
{
"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:
{
"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:
{
"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):
{
"name": "dev",
"kubernetes_version": "1.29.4",
"workers": 2
}
Пример ответа 202:
{
"job_id": "a1b2c3d4e5f6...",
"status": "queued",
"message": "Создание кластера выполняется в фоне; опросите GET /api/v1/jobs/{job_id}"
}
Ошибка 400: невалидное имя кластера или тело не проходит валидацию Pydantic.
Ошибка 409 (кластер уже есть в kind):
{
"detail": "Кластер с таким именем уже есть в kind"
}
POST /api/v1/clusters/{name}/start
Запуск кластера двумя сценариями (оба с 202 и job_id, журнал в GET /jobs/{job_id}):
- Кластер зарегистрирован в kind — задание
start_containers(поочерёдный запуск узлов,mode:containers). - В kind кластера нет, но есть сохранённый
clusters/<имя>/kind-config.yaml— заданиеstart_cluster(mode:kind_config), логика как при создании (в т.ч. скачивание образа при необходимости).
Пример ответа 202 (запуск узлов):
{
"job_id": "cafebabe...",
"status": "queued",
"mode": "containers",
"message": "Запуск узлов; опросите GET /api/v1/jobs/{job_id}"
}
Пример ответа 202 (подъём по конфигу):
{
"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:
{
"job_id": "baba...",
"status": "queued",
"mode": "stop",
"message": "Остановка узлов; опросите GET /api/v1/jobs/{job_id}"
}
Ошибка 400: некорректное имя кластера.
GET /api/v1/jobs/{job_id}
Статус фонового задания создания.
В процессе (пример 200):
{
"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):
{
"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:
{
"detail": "Задание не найдено"
}
DELETE /api/v1/clusters/{name}
kind delete cluster и удаление каталога clusters/{name}/.
Пример ответа 200:
{
"name": "dev",
"kind_delete_ok": true,
"summary": "kind delete: OK; удалена папка /work/clusters/dev"
}
Ошибка 400: некорректное имя кластера.
Ошибка 500: логическая ошибка удаления (тело с detail).
GET /
HTML-дашборд (не JSON): см. раздел «Веб-интерфейс и статика» выше.