Files
KindClustersDashboard/app/docs/api_routes.md
Sergey Antropoff 6d4bc65c8a UI/документация: крошки, метрики узлов в stats, правки навигации и подвала
- Документация: хлебные крошки; секции H2 в одной карточке; заголовок вкладки от H1
- Навигация: активна только текущая пилюля (Панель без постоянного home-стиля)
- GET /api/v1/stats: cluster_resources (docker stats CPU/RAM/I/O по узлам kind)
- Панель: блок ресурсов в карточке статистики; убраны строки подвала про api_routes/clusters
- Удалён app/docs/README.md; крошки app/docs → api_routes.md; README корня обновлён
2026-04-04 06:37:36 +03:00

20 KiB
Raw Blame History

Описание 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: без pathGET /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. Подробности — 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 Запуск: 200docker 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 (0100) обновляются во время создания.
  • Поле 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

Сводная статистика для дашборда и метрики запущенных узлов kind (один вызов docker stats / podman stats на набор имён контейнеров имя-control-plane, имя-worker…).

Пример ответа 200:

{
  "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
}
  • total_workers_from_meta может быть null, если ни в одном 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 может быть пустым.

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 (1200, по умолчанию 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

Запуск кластера двумя сценариями:

  1. Кластер есть в kind get clusters (узлы когда-либо создавались) — выполняется docker start / podman start для всех контейнеров с именами вида <имя>-control-plane, <имя>-worker, … Ответ 200.
  2. В 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): см. раздел «Веб-интерфейс и статика» выше.