- Цель make docker|podman kubectl CLUSTER=… (KUBECTL_ARGS) — exec kubectl в kind-k8s-web - README: без kubectl на хосте; раздел про проверку API из контейнера - create_cluster/cluster_status: подсказки для UI, make kubectl и exec в контейнере - app/docs: api_routes.md и README.md про kubectl и API workloads - Прочее: переименование проекта, документация, UI документации (ранее в рабочем дереве)
19 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-панель: единая карточка «панель + среда», статистика, создание кластера (прогресс, журнал kind create, отмена), таблица кластеров с иконками действий и всплывающими подсказками, модалка узлов/подов; шапка — пилюли, 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 показываются отдельными карточками (заголовок и тело). В шапке активна пилюля Документация. Путь к 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. Подробности — README.md (раздел «kubectl без установки на хост»).
Сводка маршрутов 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 |
Запуск: 200 — docker start узлов (кластер в kind); 202 + job_id — фоновый kind create по сохранённому kind-config.yaml |
| POST | /api/v1/clusters/{name}/stop |
Остановка узлов (docker/podman stop), запись в kind сохраняется |
| 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 |
Последние задания создания |
| GET | /api/v1/jobs/{job_id} |
Статус одного задания (включая progress_stage, progress_percent) |
| POST | /api/v1/jobs/{job_id}/cancel |
Запросить отмену создания (между этапами; kind create до конца не прерывается) |
Фоновые задания (jobs)
- Хранятся только в памяти процесса uvicorn; после перезапуска контейнера история обнуляется.
- В памяти держится не более 200 записей; при превышении старые задания вытесняются (
app/core/job_store.py). - Создание кластера:
POST /api/v1/clusters→ опросGET /api/v1/jobs/{job_id}(как в веб-UI). - В ответе задания поля
progress_stage(текст этапа) иprogress_percent(0–100) обновляются во время создания. - Поле
progress_log— массив последних строк журнала (выводkind create: pull образов, подъём нод и т.д.); размер ограничен (см.KIND_K8S_JOB_LOG_MAX_LINESв кодеjob_store, по умолчанию до 500 строк в буфере, в JSON отдаётся хвост). - Тип задания
kind:create_clusterилиstart_cluster(повторный подъём поclusters/<имя>/kind-config.yaml). - Статус
cancelled— пользователь запросил отмену (POST .../cancel); этапkind create clusterдо завершения не прерывается.
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
Сводная статистика для дашборда.
Пример ответа 200:
{
"kind_clusters_count": 2,
"local_cluster_dirs_count": 2,
"total_workers_from_meta": 4,
"jobs_total": 5,
"jobs_recent_failed": 1
}
total_workers_from_metaможет бытьnull, если ни в одномmeta.jsonнетworker_nodes.jobs_total— число заданий в текущей памяти процесса (не более 200).jobs_recent_failed— сколько заданий в этом хранилище сейчас в статусеfailed(не «последние N», а счётчик по всему снимку).
GET /api/v1/clusters
Список: объединение kind get clusters и подкаталогов clusters/* (без дубликатов).
Пример ответа 200 (массив):
[
{
"name": "dev",
"registered_in_kind": 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
}
}
]
GET /api/v1/jobs
Список последних фоновых заданий (создание кластера), от новых к старым.
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
}
]
POST /api/v1/jobs/{job_id}/cancel
Запрос отмены создания кластера. Пока задание в статусе queued или running, между этапами выполняется проверка флага; после уже запущенного kind create cluster нужно дождаться окончания этого шага.
Пример ответа 200:
{
"job_id": "a1b2…",
"cancel_requested": true,
"message": "Отмена обрабатывается между этапами; во время kind create дождитесь окончания шага"
}
Ошибка 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
Запуск кластера двумя сценариями:
- Кластер есть в
kind get clusters(узлы когда-либо создавались) — выполняетсяdocker start/podman startдля всех контейнеров с именами вида<имя>-control-plane,<имя>-worker, … Ответ 200. - В kind кластера нет, но в
clusters/<имя>/kind-config.yamlфайл есть — ставится фоновое заданиеstart_cluster(как при создании:kind createпо сохранённому конфигу, журнал вGET /jobs/{job_id}). Ответ 202 +job_id.
Пример ответа 200 (контейнеры запущены):
{
"name": "dev",
"mode": "containers",
"containers_started_ok": true,
"summary": "dev-control-plane: OK; dev-worker: OK; dev-worker2: OK"
}
Пример ответа 202 (подъём по конфигу):
{
"job_id": "cafebabe...",
"status": "queued",
"message": "Подъём кластера по kind-config.yaml; опросите GET /api/v1/jobs/{job_id}"
}
Ошибка 400: некорректное имя или нет ни кластера в kind, ни kind-config.yaml в clusters/<имя>/.
POST /api/v1/clusters/{name}/stop
Остановка всех контейнеров узлов кластера (docker stop / podman stop по префиксу имени). Запись кластера в kind не удаляется; позже можно снова вызвать POST …/start (режим containers).
Пример ответа 200:
{
"name": "dev",
"containers_stopped_ok": true,
"summary": "dev-control-plane: OK; dev-worker: OK"
}
Ошибка 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": "kind create cluster (скачивание образов и подъём нод — может занять несколько минут)",
"progress_percent": 28,
"progress_log": [
"[12%] Подготовка каталога и kind-config",
"--- kind create cluster ---",
"Creating cluster \"dev\" ...",
" • Ensuring node image (kindest/node:v1.29.4) 🖼 ..."
]
}
Успех (пример 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": ["[95%] Финализация", "kubectl wait nodes: ..."],
"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): см. раздел «Веб-интерфейс и статика» выше.