- Навигация: выезжающее меню при узком экране (nav-mobile.js) - Журнал: карточки <620px, компактная пагинация, время в две строки <920px - Создание кластера: оверлей загрузки, инкрементальное обновление таблицы заданий - Документация: полноэкранный спиннер при загрузке и навигации - Главная: масштабирование CTA, статистика 2 колонки <520px, донаты перенос <710px - README: env.example, новые фичи UI, автор в конце файла - api_routes: маршрут /cluster-create, спиннеры, шаблоны; автор в конце - env.example: автор перенесён в конец файла
58 KiB
Описание REST API веб-интерфейса Kind Clusters Dashboard
Базовый префикс: /api/v1
Как смотреть документацию
| Способ | 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 Панель: CTA «Перейти к созданию кластера», карточка Статистика (среда kind/kubectl, счётчики), отдельная карточка Ресурсы узлов (сводка) (донаты по GET /api/v1/stats); полная таблица кластеров — на GET /clusters. Полноэкранный спиннер первой загрузки — как у /cluster-create и /documentation. |
GET /cluster-create |
HTML Создание кластера: форма, прогресс и журнал задания, таблица «Последние задания» (журнал с диска, инкрементальное обновление без сброса раскрытого лога); dashboard.js. Спиннер первой загрузки. |
GET /clusters |
HTML Кластеры: шапка с кнопкой Создать кластер (/cluster-create), сводка Ресурсы узлов, таблица кластеров (старт/стоп, ссылка на GET /cluster/<имя>, модалки как на панели); скрипт dashboard.js. Спиннер первой загрузки. |
GET /cluster/{name} |
HTML страница кластера: донаты «Ресурсы узлов (сводка)», карточки Ресурсы узлов, затем отдельная карточка Установленные аддоны (мини-карточки Helm, GET /api/v1/clusters/{name}/addons/status, ссылки на /cluster-addons), таблицы Kubernetes (данные API кластера в JSON), кнопка Рестарт у подов (POST …/pods/restart), те же кнопки действий, что в таблице на главной; данные — GET /api/v1/clusters/{name}/overview (автообновление с интервалом панели). В шапке активна пилюля Кластеры (nav_active: clusters). |
GET /cluster/{name}/edit |
HTML редактирование сохранённого kind-config.yaml и полей meta.json (простой режим: тег/workers; расширенный: полный YAML kind Cluster). Сохранение — PUT /api/v1/clusters/{name}/config. В шапке активна Кластеры. |
GET /cluster-addons |
HTML Аддоны: GET /helm/chart-versions + GET …/addons/status; для установленных релизов — GET …/addons/installed-values: в селекте версия с пометкой «(текущая установленная версия)», в форме — values из кластера. По кнопке «Загрузить values» — шаблон из POST /helm/addons/compose-values. Установка/удаление релизов. |
GET /journal |
HTML Журналы: переключатель (как «Простой/Расширенный» в редактировании кластера) — по кластеру (journal/recent?cluster=), развёртывание (/journal/provision), Helm-аддоны (/journal/helm-addons); пагинация по 30 записей. |
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). Полноэкранный спиннер при первой загрузке и при каждом переходе по внутренним ссылкам / popstate. Каждая секция по 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 (шапка, навигация; меню «гамбургер» при узком viewport — nav-mobile.js), app/templates/dashboard.html (панель), app/templates/clusters.html (список кластеров и донаты узлов), app/templates/cluster_create.html (создание кластера и последние задания), app/templates/cluster_detail.html (страница кластера), app/templates/cluster_edit.html (редактирование конфигурации), app/templates/cluster_addons.html (Helm-аддоны), app/templates/journal.html (журнал заданий), app/templates/documentation.html (README и app/docs/*.md).
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 или kind_config_reapply); журнал — 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}/config |
Текущие meta.json и текст kind-config.yaml; подсказка kind_note про пересоздание |
| PUT | /api/v1/clusters/{name}/config |
Сохранить YAML и/или meta (простой режим или полный YAML); ответ включает обновлённый summary |
| GET | /api/v1/clusters/{name}/kubeconfig |
Скачать файл kubeconfig |
| GET | /api/v1/clusters/{name}/kubeconfig/docker |
Скачать kubeconfig для kubectl из любого контейнера: host.docker.internal:<порт> + tls-server-name (запасной вариант — *-control-plane:6443) |
| GET | /api/v1/clusters/{name}/provision-log |
Полный журнал последнего create_cluster / start_cluster / start_cluster_reapply (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/clusters/{name}/addons/status |
Статус Helm-релизов + версии чартов из helm list (ingress_nginx_chart_version, kube_prometheus_stack_chart_version, metrics_server_chart_version, istiod_chart_version, istio_base_chart_version, kiali_server_chart_version), если релиз установлен; нужен helm |
| GET | /api/v1/clusters/{name}/addons/installed-values |
Для установленных аддонов: эффективный YAML (helm get values --all -o yaml) и версии; поля ingress_nginx, kube_prometheus_stack, metrics_server, istio_kiali (или null); лимит размера YAML в ответе |
| GET | /api/v1/helm/chart-versions |
Версии чартов для UI (после helm repo update); кэш KIND_K8S_HELM_VERSIONS_CACHE_SEC; поля: ingress_nginx, kube_prometheus_stack, metrics_server, istio, kiali_server |
| POST | /api/v1/clusters/{name}/addons/ingress-nginx |
Установить ingress-nginx; тело: опционально chart_version, опционально values_yaml (полный YAML чарта, см. compose-values и раздел ниже) |
| DELETE | /api/v1/clusters/{name}/addons/ingress-nginx |
Удалить ingress-nginx |
| POST | /api/v1/clusters/{name}/addons/kube-prometheus-stack |
Установить kube-prometheus-stack; тело: grafana_admin_user, grafana_admin_password (≥8), опционально chart_version, опционально values_yaml (полный YAML чарта; пусто — автосборка) |
| DELETE | /api/v1/clusters/{name}/addons/kube-prometheus-stack |
Удалить стек |
| POST | /api/v1/clusters/{name}/addons/metrics-server |
Установить metrics-server; тело опционально: chart_version, values_yaml (полный YAML; пусто — автосборка с args для kind) |
| DELETE | /api/v1/clusters/{name}/addons/metrics-server |
Удалить metrics-server |
| POST | /api/v1/clusters/{name}/addons/istio-kiali |
Установить istio-base, istiod, секрет и kiali-server; тело: учётные данные Kiali, версии, опционально values_yaml (kiali-server), istio_base_values_yaml, istio_istiod_values_yaml |
| DELETE | /api/v1/clusters/{name}/addons/istio-kiali |
Удалить kiali-server, istiod, istio-base |
| POST | /api/v1/helm/addons/compose-values |
Собрать YAML для одного аддона (тело: addon, версии, поля Grafana при необходимости) |
| POST | /api/v1/helm/addons/compose-values-batch |
Собрать все шесть YAML для страницы аддонов одним запросом (один helm repo update); предпочтительно для UI |
| GET | /api/v1/journal/recent |
Журнал заданий из journal/jobs_history.json: query limit, offset, опционально cluster (только выбранный каталог); ответ total, total_pages, page |
| GET | /api/v1/journal/provision |
Сводка provision_log.json по кластерам (по одному последнему файлу на каталог), пагинация |
| GET | /api/v1/journal/helm-addons |
Сводка helm_addon_log.json: все записи из entries по кластерам (история), тот же формат строки, что у provision |
| GET | /api/v1/clusters/{name}/journal |
Полный файл jobs_history.json кластера (массив entries или пусто) |
| 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 |
Прервать задание: активная команда завершается принудительно (скачивание образа, создание кластера, старт/стоп узла) |
После каждого POST/DELETE …/addons/… в каталоге кластера в helm_addon_log.json добавляется новая запись в массив entries (история сохраняется, лимит KIND_K8S_HELM_ADDON_LOG_MAX_ENTRIES). Формат записи как у provision_log.json: lines, kind, status, message (краткая подпись на русском при успехе), result; в result поля addon и action: install | upgrade (повторный POST при уже установленном релизе) | delete; пароли в файл не пишутся. Поле kind: helm_addon_install_*, helm_addon_upgrade_*, helm_addon_delete_* (суффикс — аддон в snake_case).
POST /api/v1/helm/addons/compose-values-batch
Тело HelmAddonComposeBatchRequest: версии чартов из селектов (ingress_chart_version, kube_prometheus_chart_version, metrics_server_chart_version, istio_chart_version, kiali_chart_version) и опционально grafana_admin_user / grafana_admin_password.
Ответ HelmAddonComposeBatchResponse: поля ingress_nginx_values_yaml, kube_prometheus_values_yaml, metrics_server_values_yaml, kiali_values_yaml, istio_base_values_yaml, istio_istiod_values_yaml.
POST /api/v1/helm/addons/compose-values
Тело JSON (HelmAddonComposeValuesRequest):
addon:ingress-nginx|kube-prometheus-stack|metrics-server|istio-kialichart_version— для ingress / kube-prometheus-stack / metrics-server (пусто = последняя версия вhelm search)grafana_admin_user,grafana_admin_password— дляkube-prometheus-stack(если пароль пуст, в preview подставляется временная строкаChangeMe12345— замените в YAML или в форме перед установкой)istio_chart_version,kiali_chart_version— дляistio-kiali
Ответ (HelmAddonComposeValuesResponse): поле values_yaml — полный текст для основного чарта; для istio-kiali дополнительно istio_base_values_yaml и istio_istiod_values_yaml. Выполняется helm repo update (внутри цепочки) и helm show values.
Поля YAML в POST …/addons/… (установка)
Строки values_yaml / istio_base_values_yaml / istio_istiod_values_yaml: корень YAML — object. Пустая строка или отсутствие поля — сервер подставляет значения как при compose-values (кроме Istio: пустой блок = без файла -f для соответствующего чарта). Максимум 131072 символа на поле.
- ingress-nginx — один файл
-fс полным values; затем--setNodePort (сильнее файла). - kube-prometheus-stack — один файл: либо из
values_yaml, либо автосборка из версии и полей Grafana. - metrics-server — аналогично.
- istio-kiali — до трёх опциональных файлов: istio/base, istio/istiod, kiali-server. Если в YAML kiali-server нет
auth.strategy, сервер задаётauth.strategy=anonymous(стратегия login в актуальных версиях Kiali не поддерживается). Логин/пароль Kiali в теле запроса не передаются.
Пример POST compose-values (kube-prometheus-stack):
{
"addon": "kube-prometheus-stack",
"chart_version": "65.1.1",
"grafana_admin_user": "admin",
"grafana_admin_password": "MySecurePwd12"
}
Пример ответа 200 (фрагмент): поле values_yaml — многострочная строка с полным YAML чарта.
Пример POST установки istio-kiali с тремя YAML (Kiali с token, если задано в values_yaml; иначе будет anonymous):
{
"istio_chart_version": "1.22.0",
"kiali_chart_version": "1.89.0",
"istio_base_values_yaml": "defaultRevision: default\n",
"istio_istiod_values_yaml": "pilot:\\n resources: {}\\n",
"values_yaml": "auth:\\n strategy: token\\n"
}
Фоновые задания (jobs)
- Хранятся только в памяти процесса uvicorn; после перезапуска контейнера история обнуляется.
- В памяти держится не более 200 записей; при превышении старые задания вытесняются (
app/core/job_store.py). - Снимок заданий сохраняется в JSON в каталоге
clusters/(файлkind_k8s_jobs.jsonна томе с хоста) — после перезапуска контейнера список восстанавливается. Записи в статусе queued/running при старте помечаются как failed (процесс уже не выполняется). Путь переопределяется переменнойKIND_K8S_JOBS_JSON. - При завершении задания (успех / ошибка / отмена), если указано
cluster_nameи существует каталогclusters/<имя>/, вclusters/<имя>/journal/jobs_history.jsonдозаписывается запись с хвостом лога иresult(без секретов). Лимиты:KIND_K8S_CLUSTER_JOURNAL_MAX_ENTRIES,KIND_K8S_CLUSTER_JOURNAL_MAX_LOG_LINES. Чтение:GET /api/v1/journal/recent?cluster=…,GET /api/v1/clusters/{name}/journal, страницаGET /journal(режим «По кластеру»). - Операции Helm-аддонов (первая установка, обновление через тот же POST при уже установленном релизе, удаление) дописывают запись в
clusters/<имя>/helm_addon_log.json(массивentries, новые сверху). Сводка по всем кластерам:GET /api/v1/journal/helm-addons; на странице/journalрежим Helm-аддоны показывает тип операции и аддон в человекочитаемом виде. - Создание кластера:
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 и start_cluster_reapply полный журнал без обрезки дополнительно сохраняется в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_cluster_reapply(после правкиkind-config.yaml:kind deleteбез удаления каталога +kind create),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.
- Первым элементом всегда идёт
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:
{
"tags": ["latest", "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,
"helm_addons_installable_count": 4,
"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», а счётчик по всему снимку).helm_addons_installable_count— число типовых Helm-аддонов в каталоге UI (Аддоны,HELM_INSTALLABLE_ADDON_IDSвcore/helm_addons.py); совпадает с кнопками установки на/cluster-addons.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,
"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/helm/chart-versions
Список версий чартов для выпадающих списков на /cluster-addons. Выполняется helm repo update и для каждого чарта — helm search repo <chart> --versions -o json. Результаты кэшируются на KIND_K8S_HELM_VERSIONS_CACHE_SEC (по умолчанию 600 с) отдельно для каждого рефа чарта. Длина списка ограничена KIND_K8S_HELM_VERSIONS_MAX (по умолчанию 80).
Пример ответа 200:
{
"ingress_nginx": ["4.14.0", "4.13.3", "4.12.1"],
"kube_prometheus_stack": ["69.0.0", "68.4.3"],
"metrics_server": ["3.12.2", "3.12.1"],
"istio": ["1.24.3", "1.24.2"],
"kiali_server": ["2.1.0", "2.0.0"]
}
При недоступном helm — 503 с текстом ошибки.
GET /api/v1/journal/recent
Записи из journal/jobs_history.json. Без параметра cluster — объединение по всем кластерам, сортировка по времени (новые первыми). С cluster=<имя> — только файл выбранного каталога (порядок как в файле, новые сверху). В каждой записи есть source_cluster.
Query:
| Параметр | Описание |
|---|---|
limit |
Записей на страницу, 1–100, по умолчанию 30 |
offset |
Смещение, ≥ 0, по умолчанию 0 |
cluster |
Необязательно: DNS-имя кластера — только его jobs_history.json |
Поля ответа: limit, offset, total, page, total_pages, entries (элементы JournalEntryModel — в т.ч. log_lines).
Пример ответа 200 (первая страница, до 30 записей):
{
"limit": 30,
"offset": 0,
"total": 75,
"page": 1,
"total_pages": 3,
"entries": [
{
"job_id": "a1b2c3",
"kind": "create_cluster",
"cluster_name": "dev",
"source_cluster": "dev",
"status": "success",
"message": null,
"created_at_utc": "2026-04-04T11:58:00+00:00",
"finished_at_utc": "2026-04-04T12:05:00+00:00",
"log_lines": ["[1] …", "[2] …"],
"result": { "cluster_name": "dev" }
}
]
}
GET /api/v1/journal/provision
Для каждого каталога clusters/<имя>/, где есть provision_log.json, читается один объект; список сортируется по finished_at_utc (новые первыми). Пагинация: limit, offset (те же ограничения, что у /journal/recent).
Ответ: JournalPagedDirLogsResponse — в каждом элементе entries поля как у файла provision: kind, lines, status, message, result, плюс source_cluster.
GET /api/v1/journal/helm-addons
Аналогично /journal/provision, но источник — helm_addon_log.json: для каждого кластера разворачиваются все элементы массива entries (история операций Helm), строки сортируются по finished_at_utc (новые первыми). Формат одной записи совпадает с provision_log.json.
GET /api/v1/clusters/{name}/journal
Содержимое файла clusters/<имя>/journal/jobs_history.json. Если файла нет — entries: [].
Пример ответа 200:
{
"cluster_name": "dev",
"file_version": 1,
"entries": [
{
"version": 1,
"job_id": "a1b2c3",
"kind": "stop_containers",
"cluster_name": "dev",
"status": "success",
"message": null,
"created_at_utc": "2026-04-04T10:00:00+00:00",
"finished_at_utc": "2026-04-04T10:00:15+00:00",
"log_lines": [],
"result": null
}
]
}
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_cluster_reapply, start_containers, stop_containers). Для длительной команды завершается связанный дочерний процесс; между шагами запуска/остановки отдельных узлов также проверяется флаг отмены.
Пример ответа 200:
{
"job_id": "a1b2…",
"cancel_requested": true,
"message": "Запрошено прерывание; текущая команда будет остановлена, задание перейдёт в отменено"
}
Ошибка 400: задание уже завершено.
Ошибка 404: неизвестный job_id.
GET /api/v1/clusters/{name}/config
Чтение clusters/<имя>/meta.json и текста kind-config.yaml. Поле kind_note напоминает: kind не меняет топологию и образ уже созданного кластера — новый YAML на диске применится при следующем kind create (после kind delete и «Старт» / пересоздания).
Ошибка 404: каталога кластера нет.
Ошибка 400: некорректное имя.
Пример ответа 200 (JSON):
{
"cluster_name": "dev",
"meta": {
"cluster_name": "dev",
"kubernetes_version_tag": "v1.29.4",
"worker_nodes": 2,
"node_image": "kindest/node:v1.29.4",
"description": "Тестовый стенд"
},
"kind_config_yaml": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n image: kindest/node:v1.29.4\n- role: worker\n image: kindest/node:v1.29.4\n",
"has_kind_config": true,
"registered_in_kind": true,
"kind_note": "Файлы сохранены на диске. Кластер уже зарегистрирован в kind: действующие узлы и образ не меняются до удаления кластера из kind и повторного создания по обновлённому kind-config.yaml (или «Старт», если запись в kind удалена, а каталог данных остался)."
}
PUT /api/v1/clusters/{name}/config
Сохранение на диск. Нужно хотя бы одно поле тела:
| Поле | Назначение |
|---|---|
kubernetes_version |
Тег kindest/node для пересборки простого YAML (control-plane + N worker) |
workers |
Число worker-нод 0–20 (вместе с версией из запроса или из meta) |
kind_config_yaml |
Полная замена kind-config.yaml (проверка: kind: Cluster, apiVersion с kind.x-k8s.io, nodes, есть control-plane) |
description |
Строка в meta (пустая строка сбрасывает поле) |
Если передан непустой kind_config_yaml, поля kubernetes_version и workers для генерации YAML не используются (можно не отправлять).
Пример тела (только описание):
{
"description": "Обновлённая заметка"
}
Пример тела (простой режим):
{
"kubernetes_version": "1.30.0",
"workers": 3,
"description": "Три воркера"
}
Пример тела (расширенный YAML):
{
"kind_config_yaml": "kind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nnodes:\n- role: control-plane\n image: kindest/node:v1.29.4\n",
"description": ""
}
Пример ответа 200 (JSON):
{
"cluster_name": "dev",
"updated_kind_config_yaml": true,
"message": "Сохранено.",
"registered_in_kind": true,
"kind_note": "Файлы сохранены на диске. Кластер уже зарегистрирован в kind: действующие узлы и образ не меняются до удаления кластера из kind и повторного создания по обновлённому kind-config.yaml (или «Старт», если запись в kind удалена, а каталог данных остался).",
"summary": {
"name": "dev",
"registered_in_kind": true,
"kind_nodes_running": true,
"has_local_kubeconfig": true,
"has_provision_log": true,
"meta": {
"kubernetes_version_tag": "v1.30.0",
"worker_nodes": 3
}
}
}
Ошибка 404: каталога кластера нет.
Ошибка 400: некорректное имя, пустое тело, ошибка разбора или структуры YAML.
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}/kubeconfig/docker
Скачать kubeconfig для kubectl из произвольного контейнера (Content-Disposition: kubeconfig-docker-{name}.yaml).
Логика как у kubectl внутри веб-контейнера (kubeconfig_patch.apply_apiserver_endpoint_to_kubeconfig_file): при успешном docker port <имя>-control-plane 6443/tcp — server=https://<KIND_K8S_APISERVER_GATEWAY_HOST>:<порт> (по умолчанию host.docker.internal) и tls-server-name из KIND_K8S_KUBECONFIG_TLS_SERVER_NAME (по умолчанию localhost). Иначе запасной server=https://<имя>-control-plane:6443 без SNI. На Linux у клиентского контейнера обычно нужен extra_hosts: host.docker.internal:host-gateway.
Ошибка 404: нет файла kubeconfig в clusters/{name}/.
Ошибка 500: не удалось выполнить kubectl config set-cluster / задать SNI.
Ошибка 400: некорректное имя кластера.
GET /api/v1/clusters/{name}/provision-log
Содержимое clusters/<имя>/provision_log.json: полный журнал строк последнего завершённого задания create_cluster, start_cluster или start_cluster_reapply (включая успех, ошибку и отмену), плюс метаданные (job_id, kind, status, message, result, finished_at_utc).
Ошибка 404: файла нет (кластер ещё не создавали/не стартовали с сохранением журнала, или каталог без записи).
Ошибка 400: некорректное имя кластера.
Пример ответа 200 (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:
{
"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.
Если кластер есть в kind, но узлы остановлены (docker stop / «Стоп» в UI): error — краткое сообщение без вызова kubectl (вместо сырого stderr про DNS *-control-plane).
Ошибка 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_*недоступны (см. ниже). - Если кластер зарегистрирован в kind, но узлы не запущены (
kind_nodes_running: false):kubectlне вызывается; вkubeconfig_errorи вmessageкаждого блокаk8s_*— одно и то же дружелюбное пояснение (без stderr про недоступный API). - Поля
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 (фрагмент):
{
"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):
{
"namespace": "default",
"pod": "my-app-7d4f8b9-xk2cp"
}
Пример ответа 200:
{
"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:
{
"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 и в
meta.jsonнет флагаapply_kind_config_on_next_start— заданиеstart_containers(поочерёдныйdocker startузлов,mode:containers). - Кластер зарегистрирован в kind и после PUT …/config с изменением
kind-config.yamlвmeta.jsonвыставленapply_kind_config_on_next_start: true — заданиеstart_cluster_reapply(mode:kind_config_reapply):kind delete clusterбез удаленияclusters/<имя>/, затемkind createпо сохранённому YAML (как при создании; флаг в новомmeta.jsonне сохраняется). - В kind кластера нет, но есть
clusters/<имя>/kind-config.yaml— заданиеstart_cluster(mode:kind_config).
Остановка (POST …/stop) флаг пересоздания не сбрасывает; достаточно после правки конфига нажать «Старт» (после стопа или сразу — выполнится сценарий 2, если кластер всё ещё в kind).
Пример ответа 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}"
}
Пример ответа 202 (пересоздание после правки YAML):
{
"job_id": "c0ffee...",
"status": "queued",
"mode": "kind_config_reapply",
"message": "Удаление записи в kind и создание кластера по обновлённому kind-config.yaml; GET /api/v1/jobs/{job_id}"
}
Ошибка 400: некорректное имя; или нет ни кластера в kind, ни kind-config.yaml; или для kind_config_reapply отсутствует kind-config.yaml.
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): см. раздел «Веб-интерфейс и статика» выше.
Автор: Сергей Антропов — devops.org.ru