Files
KindClustersDashboard/README.md

379 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kind Clusters Dashboard — локальные кластеры Kubernetes (kind)
Проект сделан для тех, кому нужен **локальный Kubernetes без облака и без ручной возни в терминале**: **kind** остаётся движком, а поверх него — **единая панель в браузере** и **REST API**. В одном месте можно **создавать и удалять кластеры**, смотреть **узлы и поды**, ставить типовые вещи через **Helm** (ingress, мониторинг, metrics-server и др.), вести **журнал операций** и скачивать **kubeconfig** для работы с кластером с хоста или из других контейнеров. Удобно для **обучения DevOps/Kubernetes**, **быстрой проверки манифестов и Helm-чартов**, **демо** и **песочниц**, когда не хочется поднимать управляемый кластер и платить за API control plane.
Технически панель — это **FastAPI** в Docker-образе **`kind-k8s-tools:local`**, запуск через **`Makefile`** и **Compose** (**Docker** или **Podman**). На хосте по умолчанию интерфейс открывается на порту **8080** (переменная **`KIND_K8S_WEB_PORT`** в **`.env`**); **внутри контейнера** приложение слушает **6000**. Порт **6000 наружу по умолчанию не пробрасываем**: в Chrome и других браузерах на Chromium он считается небезопасным (**ERR_UNSAFE_PORT**). **kind**, **kubectl** и **Helm** ставятся в образ — на машине достаточно контейнерной среды и **make**. Данные кластеров и **kubeconfig** лежат на хосте в **`clusters/<имя>/`**.
## Документация
| Ресурс | Описание |
|--------|----------|
| **[app/docs/api_routes.md](app/docs/api_routes.md)** | Описание REST API `/api/v1/*` с примерами JSON (для фронтенда и интеграций) |
| **[app/docs/screenshots.md](app/docs/screenshots.md)** | Скриншоты веб-интерфейса (**светлая** и **тёмная** тема); файлы в **`app/docs/images/`** |
| **`/docs`** (Swagger), **`/redoc`**, **`/api/v1/health`** | На панели открываются в **отдельном окне** браузера (`window.open`); прямой URL — тот же порт, что и UI (по умолчанию **8080**) |
### Скриншоты (превью)
Полная галерея и подписи — в **[app/docs/screenshots.md](app/docs/screenshots.md)**. Ниже — главная панель в обеих темах.
| Светлая тема | Тёмная тема |
| :------------: | :-----------: |
| ![](app/docs/images/dashboard-light.png) | ![](app/docs/images/dashboard-dark.png) |
В корне репозитория — файл **`env.example`**: перечислены **только имена** переменных (без значений), для ориентира при ручной настройке **`.env`**. Полноценно создать **`.env`** можно интерактивно скриптом **`scripts/setup_env_interactive.py`** (`make setup`; в начале — выбор **docker** или **podman**, путь **`CONTAINER_SOCKET`** подставляется автоматически).
## Зачем это нужно
- Быстро получить Kubernetes **локально** (интеграционные тесты, манифесты, обучение) — см. ввод о назначении проекта выше.
- Версия кластера и число worker-нод задаются **в веб-UI** (или через REST API / скрипты в контейнере).
- Количество кластеров **не ограничено** кодом (ограничения — ресурсы хоста и Docker).
- Артефакты на хосте: `clusters/<имя>/` — том для `kubeconfig` (доступен в контейнере как `/work/clusters/<имя>/`).
## Веб-интерфейс
- Верхняя **единая карточка**: заголовок, краткое описание и строка **состояния среды** (`kind` / `kubectl` / Docker или Podman API).
- **Статистика**: число кластеров в kind, локальных каталогов, сумма workers из `meta.json`, счётчики фоновых заданий.
- **Создание кластера** (`/cluster-create`): форма с подсказкой тегов `kindest/node` (`GET /api/v1/versions`), фоновое задание и опрос статуса (JSON в сворачиваемом блоке); блок **«Последние задания»** (журнал с диска) обновляется без полной перерисовки таблицы — новые записи добавляются сверху, раскрытый лог не сбрасывается при автообновлении.
- **Кластеры** (`/clusters`): сводка ресурсов узлов (донаты), таблица кластеров — **старт**/**стоп**, скачивание kubeconfig, модалки узлов/подов, ссылка на страницу кластера; с **панели** (`/`) — кнопка **«Перейти к созданию кластера»** (размер кнопки плавно уменьшается на узком экране).
- **Аддоны** (`/cluster-addons`): выбор кластера и установка/удаление через **Helm** в контейнере — **ingress-nginx**, **kube-prometheus-stack** (логин/пароль Grafana), **metrics-server**, **Istio + Kiali** (Kiali по умолчанию без формы входа, `auth` при необходимости в YAML values); журнал операции на странице (прогресс + вывод как при создании кластера), история в **`clusters/<имя>/helm_addon_log.json`**. Нужна **пересборка образа** после обновления Dockerfile (бинарник `helm`). Таймаут операций: **`KIND_K8S_HELM_TIMEOUT_SEC`** (по умолчанию 900 с).
- **Последние задания**: история в памяти процесса (до **200** записей; после перезапуска контейнера сбрасывается); кнопка **«Очистить завершённые»** вызывает **`DELETE /api/v1/jobs`** (из памяти удаляются только завершённые задания).
- **Журнал** (`/journal`): три режима (по кластеру, развёртывание, Helm-аддоны), пагинация; на узком экране таблица записей превращается в **карточки** (как «Последние задания» на странице создания).
- **Документация** (`/documentation`): README и `app/docs/*.md` в браузере (Markdown); при загрузке и при переходе между файлами — полноэкранный **спиннер** (как на панели).
- **Автообновление** таблиц и плашки среды каждые ~3,5 с (fetch к API без перезагрузки страницы).
- При активном задании — **прогресс-бар**, **журнал** (в т.ч. скачивание образа; для **docker** при поддержке CLI — **`pull --progress=plain`**, см. **`KIND_K8S_DOCKER_PULL_PLAIN`**), опрос статуса чаще, чем общие таблицы; кнопка **«Отменить»** — прерывание с завершением текущей дочерней команды.
- Уведомления (toast) при успехе/ошибке; в подвале — копирайт и ссылка на **devops.org.ru**.
**Шапка:** навигация в виде **пилюль** (стили `.nav-pill`); при ширине окна **меньше ~920px** — кнопка **«гамбургер»** и выезжающая панель (`app/static/js/nav-mobile.js`). Пункты **Swagger**, **ReDoc** и **Health** открывают страницу в **отдельном именованном окне** (~1240×840), чтобы не уходить с панели (см. скрипт в `base.html`).
**Адаптивная вёрстка (кратко):** донаты «Ресурсы узлов» переносятся на новые строки при ширине окна **меньше ~710px**; блок **«Статистика»** на главной — мини-карточки в **две колонки** при **меньше ~520px**; таблицы **кластеров** и **«Последние задания»** на `/cluster-create` при ширине **меньше ~920px** оформляются **карточками**; на **/journal** карточки записей — при **меньше ~620px**; в журнале и таблицах заданий колонка времени (UTC) — дата и время **в две строки** при **меньше ~920px**. При первой загрузке полноэкранный спиннер: **главная**, **/clusters**, **страница кластера**, **/cluster-create**, **/documentation** (`app/static/style.css``.page-loading-overlay`).
**Структура фронтенда:** `app/templates/base.html` (шапка и меню), `app/templates/dashboard.html`, `app/templates/journal.html`, `app/templates/documentation.html`, `app/static/style.css`, `app/static/js/dashboard.js`, `app/static/js/journal.js`, `app/static/js/documentation.js`, `app/static/js/nav-mobile.js` (префикс API: `data-api-base` на `<body>`, по умолчанию `/api/v1`).
## Требования на хосте
| Компонент | Назначение |
|-----------|------------|
| **Docker** + **Compose v2** (или **Podman** + compose) | Сборка образа и запуск веб-сервиса |
| **make** | `make docker up` / `make podman up` и вспомогательные цели |
| **python3** | Только для **`make setup`** (создание `.env`) |
**На хост не нужны:** **kind**, **kubectl**, Python приложения — всё это в образе `kind-k8s-tools:local` и выполняется в контейнере **`kind-k8s-web`**. Проверка API и узлов: **веб-интерфейс** (кластер → узлы/поды) или **`make docker kubectl CLUSTER=<имя>`** / **`make podman kubectl …`** (см. ниже) — `kubectl` вызывается через **`docker compose exec`** / **`podman compose exec`** внутри уже запущенного сервиса.
Файл **`clusters/<имя>/kubeconfig.host`** (или скачивание из веб-UI — кнопка со стрелкой вниз) для kubectl на хосте: **`https://<KIND_K8S_KUBECONFIG_CLIENT_HOST или localhost>:<порт>`** — см. `app/kubeconfig_patch.py`. Переменная позволяет задать IP/имя хоста, если `localhost` недоступен с вашей машины. Рядом в UI — вторая кнопка (иконка «пакет»): **`GET …/kubeconfig/docker`** — kubeconfig для kubectl **из любого контейнера**: **`https://host.docker.internal:<порт>`** (порт с проброса 6443 control plane на хост) и **`tls-server-name: localhost`**, как у процесса веб-приложения. На **Linux** в вашем контейнере добавьте **`extra_hosts: ["host.docker.internal:host-gateway"]`** (в compose — как у сервиса `kind-k8s-web`). Если **`docker port`** недоступен при генерации файла, подставляется запасной **`https://<имя>-control-plane:6443`** (тогда нужна общая сеть с kind).
Смонтированы **сокет** Docker/Podman и каталог **`./clusters`** → в контейнере **`/work/clusters`**. Каталог **`./app`** монтируется в **`/opt/kind-k8s/app`** для разработки без пересборки образа. Файл **`./README.md`** монтируется в **`/opt/kind-k8s/README.md`** (страница **«Документация»** и **`GET /api/v1/docs/readme`** без пересборки образа).
После создания кластера при **`KIND_K8S_PATCH_KUBECONFIG`** дополнительно пишется **`kubeconfig.host`**; скачивание через API каждый раз пересобирает файл с актуальным портом и хостом из **`KIND_K8S_KUBECONFIG_CLIENT_HOST`**.
### kubectl из другого контейнера (ошибка `localhost:<порт>` / `dev-control-plane: Name or service not known`)
Файл **`kubeconfig.host`** рассчитан на запуск **kubectl с хоста**. Внутри **другого** контейнера **`127.0.0.1` — не хост Docker**, поэтому **`https://localhost:<порт>`** из `kubeconfig.host` там не работает. Имя **`…-control-plane`** резолвится только в **той же сети**, что и узел kind — из «чужой» сети контейнера DNS его не видит.
**Вариант 1 (предпочтительно): скачать kubeconfig из UI** (кнопка с иконкой пакета) или **`GET /api/v1/clusters/{name}/kubeconfig/docker`**. В файле уже **`https://host.docker.internal:<порт>`** и **`tls-server-name: localhost`** (как у kubectl внутри `kind-k8s-web`): трафик идёт на **хост**, затем в проброшенный порт API kind — **без** общей сети с кластером. В **вашем** контейнере на **Linux** добавьте в compose/run: **`extra_hosts: ["host.docker.internal:host-gateway"]`** (как в `docker-compose.yml` у `kind-k8s-web`). Шлюз переопределяется **`KIND_K8S_APISERVER_GATEWAY_HOST`**; SNI — **`KIND_K8S_KUBECONFIG_TLS_SERVER_NAME`**.
**Вариант 2: общая сеть Docker с kind.** Подключите контейнер к сети kind и задайте **`server: https://<имя_кластера>-control-plane:6443`** (см. `docker network ls` / `docker inspect …-control-plane`).
Подробности — **`app/kubeconfig_patch.py`**.
## Быстрый старт
### Готовый образ с Docker Hub (без сборки, без файла `.env`)
**Опубликованный образ:** **`inecs/kind-cluster-dashboard:v1.0.0`** (соответствует значениям по умолчанию в `Makefile`: `DOCKERHUB_REPO=inecs/kind-cluster-dashboard`, `RELEASE_TAG=v1.0.0`). Все теги: [hub.docker.com/r/inecs/kind-cluster-dashboard/tags](https://hub.docker.com/r/inecs/kind-cluster-dashboard/tags).
Сборка и отправка в Docker Hub (для сопровождения образа): **linux/amd64** (ПК **x86_64**) и **linux/arm64** (**Apple Silicon** и прочий ARM). Образ **linux/386** (32-bit x86) для **kind** официальными бинарниками не поставляется, поэтому в релиз не входит.
```bash
docker login
# По умолчанию пушит inecs/kind-cluster-dashboard:v1.0.0 (см. Makefile):
make release
# Или явно: make release DOCKERHUB_REPO=inecs/kind-cluster-dashboard RELEASE_TAG=v1.0.0
# Необязательно: KIND_VERSION=0.24.0 HELM_VERSION=v3.16.3 KUBECTL_VERSION=v1.32.0
```
Другой образ/тег задайте в shell: **`KIND_K8S_HUB_IMAGE=логин/репозиторий:тег`** при вызове compose (в YAML ниже то же через подстановку).
```bash
mkdir -p clusters
# Docker (файл в репозитории: compose/docker-compose.hub.docker.yml; образ по умолчанию — v1.0.0):
docker compose -f compose/docker-compose.hub.docker.yml up -d
# Другой тег: KIND_K8S_HUB_IMAGE=inecs/kind-cluster-dashboard:latest docker compose -f compose/docker-compose.hub.docker.yml up -d
# Podman — путь к API-сокету только через переменные окружения (не через .env):
export CONTAINER_SOCKET="${XDG_RUNTIME_DIR}/podman/podman.sock"
podman compose -f compose/docker-compose.hub.podman.yml up -d
```
Браузер: **http://127.0.0.1:8080** (в примерах ниже публикация **8080:6000**; при необходимости поменяйте в YAML).
Ниже — **полные** примеры `docker-compose`: все переменные **`environment`** заданы явно теми же значениями, что использует приложение по умолчанию (см. `docker-compose.yml`, `app/core/*.py`, `app/kubeconfig_patch.py`). **`.env` не нужен**. Актуальные копии — в [`compose/docker-compose.hub.docker.yml`](compose/docker-compose.hub.docker.yml) и [`compose/docker-compose.hub.podman.yml`](compose/docker-compose.hub.podman.yml).
**Docker** — вставьте в `docker-compose.yml` (или сохраните как отдельный файл):
```yaml
services:
kind-k8s-web:
image: ${KIND_K8S_HUB_IMAGE:-inecs/kind-cluster-dashboard:v1.0.0}
container_name: kind-clusters-dashboard
user: "0:0"
volumes:
- ${KIND_K8S_CLUSTERS_DIR:-./clusters}:/work/clusters
- ${CONTAINER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
ports:
- "8080:6000"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
HOME: /root
DOCKER_HOST: unix:///var/run/docker.sock
KIND_K8S_IN_CONTAINER: "1"
KIND_K8S_WORKDIR: /work
KIND_K8S_PATCH_KUBECONFIG: "1"
KIND_K8S_KUBECONFIG_CLIENT_HOST: localhost
KIND_K8S_KUBECONFIG_TLS_SERVER_NAME: localhost
KIND_K8S_APISERVER_GATEWAY_HOST: host.docker.internal
CONTAINER_CLI: docker
KIND_K8S_SKIP_VERSION_LIST: "0"
KIND_K8S_VERSION_LIST_DISPLAY: "50"
KIND_K8S_HUB_TAGS_MAX_PAGES: "120"
KIND_K8S_DEBUG: "0"
KIND_K8S_JOB_LOG_MAX_LINES: "2500"
KIND_K8S_STREAM_PTY: "1"
KIND_K8S_DOCKER_PULL_PLAIN: "1"
KIND_K8S_JOB_API_LOG_MAX_LINES: "5000"
KIND_K8S_JOBS_JSON: /work/clusters/kind_k8s_jobs.json
KIND_K8S_README_PATH: /opt/kind-k8s/README.md
KIND_K8S_WAIT_NODES: "1"
KIND_K8S_WAIT_NODES_TIMEOUT_SEC: "300"
KIND_K8S_HELM_TIMEOUT_SEC: "900"
KIND_K8S_HELM_VERSIONS_CACHE_SEC: "600"
KIND_K8S_HELM_VERSIONS_MAX: "80"
KIND_K8S_CLUSTER_JOURNAL_MAX_ENTRIES: "500"
KIND_K8S_CLUSTER_JOURNAL_MAX_LOG_LINES: "2000"
KIND_K8S_HELM_ADDON_LOG_MAX_ENTRIES: "500"
KIND_K8S_APP_TITLE: "Kind Clusters Dashboard"
KIND_K8S_UVICORN_RELOAD: "0"
working_dir: /opt/kind-k8s/app
command: ["/opt/kind-k8s/run_uvicorn.sh"]
```
**Podman** — перед запуском: **`export CONTAINER_SOCKET="$XDG_RUNTIME_DIR/podman/podman.sock"`** (или фактический путь к `podman.sock`). При **EACCES** на сокет подберите **`user`** / **UID:GID** как в основном [`docker-compose.yml`](docker-compose.yml) и [`Makefile`](Makefile) для `make podman up`.
```yaml
services:
kind-k8s-web:
image: ${KIND_K8S_HUB_IMAGE:-inecs/kind-cluster-dashboard:v1.0.0}
container_name: kind-clusters-dashboard
userns_mode: keep-id
user: "0:0"
volumes:
- ${KIND_K8S_CLUSTERS_DIR:-./clusters}:/work/clusters
- ${CONTAINER_SOCKET}:/run/podman/podman.sock
ports:
- "8080:6000"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
HOME: /tmp
DOCKER_HOST: unix:///run/podman/podman.sock
KIND_K8S_IN_CONTAINER: "1"
KIND_K8S_WORKDIR: /work
KIND_K8S_PATCH_KUBECONFIG: "1"
KIND_K8S_KUBECONFIG_CLIENT_HOST: localhost
KIND_K8S_KUBECONFIG_TLS_SERVER_NAME: localhost
KIND_K8S_APISERVER_GATEWAY_HOST: host.docker.internal
CONTAINER_CLI: podman
KIND_K8S_SKIP_VERSION_LIST: "0"
KIND_K8S_VERSION_LIST_DISPLAY: "50"
KIND_K8S_HUB_TAGS_MAX_PAGES: "120"
KIND_K8S_DEBUG: "0"
KIND_K8S_JOB_LOG_MAX_LINES: "2500"
KIND_K8S_STREAM_PTY: "1"
KIND_K8S_DOCKER_PULL_PLAIN: "1"
KIND_K8S_JOB_API_LOG_MAX_LINES: "5000"
KIND_K8S_JOBS_JSON: /work/clusters/kind_k8s_jobs.json
KIND_K8S_README_PATH: /opt/kind-k8s/README.md
KIND_K8S_WAIT_NODES: "1"
KIND_K8S_WAIT_NODES_TIMEOUT_SEC: "300"
KIND_K8S_HELM_TIMEOUT_SEC: "900"
KIND_K8S_HELM_VERSIONS_CACHE_SEC: "600"
KIND_K8S_HELM_VERSIONS_MAX: "80"
KIND_K8S_CLUSTER_JOURNAL_MAX_ENTRIES: "500"
KIND_K8S_CLUSTER_JOURNAL_MAX_LOG_LINES: "2000"
KIND_K8S_HELM_ADDON_LOG_MAX_ENTRIES: "500"
KIND_K8S_APP_TITLE: "Kind Clusters Dashboard"
KIND_K8S_UVICORN_RELOAD: "0"
working_dir: /opt/kind-k8s/app
command: ["/opt/kind-k8s/run_uvicorn.sh"]
```
### Запуск из репозитория (локальная сборка образа)
```bash
# Из корня клонированного репозитория Kind Clusters Dashboard (рядом с Makefile):
make setup # опционально: интерактивно создать .env (Enter — дефолты из скрипта)
make docker check-docker # или: make podman check-docker
make docker up # или: make podman up
# Браузер: http://127.0.0.1:8080 (порт: KIND_K8S_WEB_PORT в .env; не 6000 на хосте — Chrome ERR_UNSAFE_PORT)
```
Из родительского каталога: `make -C <каталог-корня-репозитория> docker up` (подставьте путь к каталогу с `Makefile`).
**Логи, статус, перезапуск и остановка:** `make docker logs` / `make podman logs` (follow), `make docker ps` / `make podman ps`, **`make docker restart` / `make podman restart`** (перезапуск сервиса `kind-k8s-web`), `make docker down` / `make podman down`.
### Разработка UI и API без пересборки образа
В **`docker-compose.yml`** смонтированы **`./app`** → **`/opt/kind-k8s/app`** и **`./README.md`** → **`/opt/kind-k8s/README.md`** (только чтение).
По умолчанию (**`KIND_K8S_UVICORN_RELOAD=1`**) uvicorn запускается с **`--reload`** (см. **`scripts/run_uvicorn.sh`**) и перезапускает процесс при изменении `*.py`, `*.html`, `*.css`, `*.js` в `app/`. Пересобирать образ нужно после изменений **Dockerfile**, **`requirements.txt`** или **`scripts/run_uvicorn.sh`**.
Отключить reload: в **`.env`** задать **`KIND_K8S_UVICORN_RELOAD=0`**.
### Дополнительно: CLI в одноразовом контейнере
Если нужен сценарий без UI (CI, скрипты):
```bash
docker compose run --rm --entrypoint python3 kind-k8s-web \
/opt/kind-k8s/app/create_cluster.py --non-interactive --name dev --kubernetes-version 1.29.4 --workers 2
docker compose run --rm --entrypoint python3 kind-k8s-web \
/opt/kind-k8s/app/delete_cluster.py --non-interactive --name dev --yes
```
Рабочий каталог сервиса в образе — `/opt/kind-k8s/app`; том `clusters/` и сокет те же, что у `docker compose up`.
### kubectl без установки на хост
Пока запущен веб-сервис (`make docker up` или `make podman up`), **kubectl** из образа:
```bash
# По умолчанию: get nodes (kubeconfig: /work/clusters/<имя>/kubeconfig внутри контейнера)
make docker kubectl CLUSTER=<имя_кластера>
# или: make podman kubectl CLUSTER=<имя_кластера>
make docker kubectl CLUSTER=<имя> KUBECTL_ARGS="get pods -A"
make docker kubectl CLUSTER=<имя> KUBECTL_ARGS="config view --minify"
```
Эквивалент вручную (из корня репозитория, **Docker**):
```bash
docker compose exec kind-k8s-web kubectl --kubeconfig=/work/clusters/<имя>/kubeconfig get nodes
```
После успешного `kind create` по умолчанию выполняется **`kubectl wait`** готовности нод (`KIND_K8S_WAIT_NODES`, `KIND_K8S_WAIT_NODES_TIMEOUT_SEC` в `.env`) — тоже **внутри** контейнера приложения.
## Команды Makefile
| Цель | Описание |
|------|----------|
| `make help` | Краткая справка |
| `make docker up` / `make podman up` | Поднять веб-UI (`kind-k8s-web`) |
| `make docker down` / `make podman down` | Остановить compose в каталоге репозитория |
| `make docker restart` / `make podman restart` | Перезапустить контейнер сервиса `kind-k8s-web` (`compose restart`) |
| `make docker logs` / `make podman logs` | Логи `kind-k8s-web` (stream, `-f`) |
| `make docker ps` / `make podman ps` | Статус контейнеров текущего compose-проекта |
| `make docker build` / `make podman build` | Собрать образ `kind-k8s-tools:local` |
| `make docker rebuild` / `make podman rebuild` | Пересборка образа **без кэша** (`build --no-cache`) и пересоздание контейнера (`up -d --force-recreate`) |
| `make docker check-docker` / `make podman check-docker` | Проверить выбранный CLI и `compose version` |
| `make docker kubectl CLUSTER=…` / `make podman kubectl CLUSTER=…` | **kubectl** в контейнере `kind-k8s-web` (опционально `KUBECTL_ARGS="…"`; по умолчанию `get nodes`). Сервис должен быть **up**. |
| `make setup` | Интерактивно создать `.env` (список переменных в `scripts/setup_env_interactive.py`) |
| `make clusters-dir` | Создать каталог `clusters/` |
| `make docker …` / `make podman …` | Префикс **обязателен** для целей `up`, `down`, `restart`, `logs`, `ps`, `build`, `rebuild`, `check-docker`, `kubectl` |
Цели `up`, `down`, `restart`, `logs`, `ps`, `build`, `rebuild`, `check-docker` и `kubectl` **без** `docker`/`podman` в той же команде завершатся с подсказкой.
## Переменные окружения
Файл **`.env`** в корне репозитория подхватывает Compose. Создать его: **`make setup`** или вручную по списку в **`scripts/setup_env_interactive.py`**. Файл **`.env`** в git не коммитится (см. `.gitignore`).
Переменные **`DOCKER_HOST`**, **`KIND_K8S_IN_CONTAINER`**, **`KIND_K8S_WORKDIR`** в контейнере задаются **литералами в `docker-compose.yml`**, а не из `.env`.
| Переменная | Где используется | Назначение |
|------------|------------------|------------|
| **`KIND_VERSION`** | build-arg | Версия бинарника kind при сборке образа |
| **`KUBECTL_VERSION`** | build-arg | Версия kubectl в образе; пусто в compose → в Dockerfile подставляется `stable.txt` при сборке; `make setup` предлагает закреплённый тег |
| **`KIND_K8S_WEB_PORT`** | ports | Порт **на хосте** для веб-UI (по умолчанию **8080**; в контейнере публикация идёт на процесс на **6000**) |
| **`KIND_K8S_WEB_HOST`** | локальный uvicorn / Settings | Хост привязки при запуске вне compose (в контейнере задаётся entrypoint) |
| **`KIND_K8S_UVICORN_RELOAD`** | контейнер | `1` (по умолчанию) — hot-reload при правках в `./app`; `0` — без reload |
| **`KIND_K8S_APP_TITLE`** | контейнер / Settings | Заголовок OpenAPI и HTML; пустое значение из compose не ломает приложение (`env_ignore_empty`, fallback) |
| **`KIND_K8S_WAIT_NODES`** | контейнер | `0` — не ждать Ready нод после create |
| **`KIND_K8S_WAIT_NODES_TIMEOUT_SEC`** | контейнер | Таймаут `kubectl wait` (секунды) |
| **`CONTAINER_SOCKET`** | volume | Путь к сокету **на хосте**; при **`make podman …`** перед compose пишется **`.env.podman.override`** (в **`.gitignore`**) с актуальным путём из **`scripts/detect_podman_socket.py`**, чтобы перекрыть **`.env`**: иначе старый **`docker-compose`** часто подставляет том из **`.env`** (в т.ч. неверный сокет **`/var/folders/…/podman-machine-default-api.sock`**) и падает с **operation not supported** |
| **`CONTAINER_SOCKET_MOUNT_TARGET`** | volume | Путь **внутри** контейнера: Docker — `/var/run/docker.sock`; Podman — **`/run/podman/podman.sock`** |
| **`KIND_K8S_REMOTE_SOCKET_URI`** | контейнер `DOCKER_HOST` | URI API (совпадает с точкой монтирования), например **`unix:///run/podman/podman.sock`** для Podman |
| **`KIND_K8S_PATCH_KUBECONFIG`** | контейнер | Патч `server` в kubeconfig для хоста; по умолчанию **включено** (`1` в compose и в `make setup`) |
| **`CONTAINER_CLI`** | контейнер | CLI для `docker port` / `podman port` (`docker` или `podman`) |
| **`KIND_K8S_CONTAINER_UIDGID`** | compose `user` | **`uid:gid`** процесса в контейнере; для Docker обычно **`0:0`**; для rootless Podman — **`$(id -u):$(id -g)`** (пишет **`make setup`**) |
| **`KIND_K8S_CONTAINER_HOME`** | контейнер `HOME` | Для не-root в образе без `/home/<uid>`: **`/tmp`** (Podman); для root — **`/root`** |
| **`KIND_K8S_SKIP_VERSION_LIST`** | контейнер | Не ходить в Docker Hub за тегами |
| **`KIND_K8S_VERSION_LIST_DISPLAY`** | контейнер | Сколько строк показывать в **интерактивном CLI** при выборе версии (веб-UI выводит полный список из API) |
| **`KIND_K8S_HUB_TAGS_MAX_PAGES`** | контейнер | Сколько страниц Docker Hub обходить при сборе тегов (старые 1.19.x часто на поздних страницах; в коде по умолчанию **120**, максимум **500**) |
| **`KIND_K8S_DEBUG`** | контейнер | `1`/`true`/`yes`/`да` — уровень DEBUG в логах |
| **`KIND_K8S_JOB_LOG_MAX_LINES`** | приложение | Сколько строк журнала хранить в памяти на задание (старые вытесняются); по умолчанию **2500** |
| **`KIND_K8S_JOB_API_LOG_MAX_LINES`** | приложение | Сколько строк отдавать в **GET /api/v1/jobs/{id}** (хвост); по умолчанию **5000**, максимум **20000** |
| **`KIND_K8S_JOBS_JSON`** | приложение | Путь к JSON с историей заданий; пусто — **`clusters/kind_k8s_jobs.json`** под **`KIND_K8S_WORKDIR`** |
| **`KIND_K8S_STREAM_PTY`** | приложение | **`1`** (по умолчанию) — для `kind` и **podman pull** псевдо-TTY; **`0`** — только pipe |
| **`KIND_K8S_DOCKER_PULL_PLAIN`** | приложение | **`1`** (по умолчанию) — если в выводе **`docker pull --help`** есть **`--progress`**, используется **`docker pull --progress=plain`** без PTY; иначе обычный pull. **`0`** — никогда не добавлять флаг |
| **`KIND_K8S_README_PATH`** | контейнер / приложение | Абсолютный путь к **README.md** для страницы **`/documentation`**; если пусто — используется `README.md` рядом с каталогом `app/` (в образе: `/opt/kind-k8s/README.md`) |
| **`KIND_K8S_WORKDIR`** | локальный запуск | Корень данных на машине разработчика без compose |
| **`COMPOSE_BUILD_FLAGS`** | Makefile | Например `make docker build COMPOSE_BUILD_FLAGS=--platform linux/arm64` (то же для **`make docker rebuild`**) |
## Podman (пример rootless)
```bash
export CONTAINER_SOCKET="$XDG_RUNTIME_DIR/podman/podman.sock"
make podman up
```
**Доступ к сокету (`permission denied` на `/var/run/docker.sock` внутри контейнера):** у rootless Podman сокет обычно принадлежит вашему пользователю, а не «root» из контейнера. В **`.env`** для Podman нужны **`KIND_K8S_CONTAINER_UIDGID=$(id -u):$(id -g)`** и **`KIND_K8S_CONTAINER_HOME=/tmp`** — при **`make setup`** с выбором **Podman** скрипт записывает их сам. Команды **`make podman …`** подмешивают **`docker-compose.podman.yml`** (`userns_mode: keep-id`) и дублируют uid/gid в окружении: без **keep-id** rootless Podman часто даёт **permission denied** на смонтированный сокет даже при верном `user:`. Вручную: **`podman compose -f docker-compose.yml -f docker-compose.podman.yml up -d`** и те же переменные в **`.env`** или в shell. На системах с SELinux при отказе доступа к сокету попробуйте **`CONTAINER_SOCKET_VOLUME_OPTS=:Z`** в `.env`.
## Структура репозитория (основное)
| Путь | Назначение |
|------|------------|
| `Makefile` | Запуск веб-UI; префикс `docker` или `podman` обязателен; цели `up`, `down`, `restart`, `logs`, `rebuild`, `build` и др. Для Podman: **`--env-file .env.podman.override`** (нужен compose с поддержкой **`--env-file`**, обычно ≥ 1.28). |
| `.env.podman.override` | Создаётся целью **`make podman …`**: `CONTAINER_SOCKET`, `KIND_K8S_*` для сокета; перекрывает устаревшие значения в **`.env`**. |
| `scripts/setup_env_interactive.py` | Интерактивное создание `.env` (все ключи и дефолты внутри скрипта) |
| `scripts/run_uvicorn.sh` | Точка входа контейнера: uvicorn с опциональным `--reload` |
| `Dockerfile` | Образ: kind, kubectl, docker-cli, FastAPI |
| `requirements.txt` | pip-зависимости веб-приложения |
| `docker-compose.yml` | Сервис `kind-k8s-web`, тома `./clusters`, `./app`, `./README.md`, сокет |
| `docker-compose.podman.yml` | Только для **Podman**: `userns_mode: keep-id` (подмешивается в **make podman …**) |
| `app/main.py` | FastAPI: главная `/`, создание кластера `/cluster-create`, `/documentation`, редирект `/ui`, монтирование `/static` |
| `app/api/v1/` | REST API: `router.py`, `endpoints/` (`health`, `versions`, `docs_readme`, `clusters`) |
| `app/core/` | Жизненный цикл кластеров, задания, настройки, блокировки (`kind_guard`), пути |
| `app/models/schemas.py` | Pydantic-схемы запросов/ответов API |
| `app/templates/` | Jinja2: `base.html`, `dashboard.html`, `clusters.html`, `cluster_create.html`, `cluster_detail.html`, `cluster_edit.html`, `cluster_addons.html`, `journal.html`, `documentation.html` |
| `app/static/` | `style.css`, `js/dashboard.js`, `js/documentation.js`, `js/vendor/` (marked, DOMPurify для README в UI) |
| `app/docs/` | `api_routes.md` (описание REST API) |
| `app/create_cluster.py`, `delete_cluster.py`, `cluster_status.py` | CLI и переиспользование из API / `compose run` |
В UI и API список версий **kindest/node** по умолчанию тянется с Docker Hub (нужна сеть). В изолированной среде: **`KIND_K8S_SKIP_VERSION_LIST=1`** — версию вводят вручную.
## Где лежат данные на хосте
- `clusters/<имя>/kind-config.yaml`
- `clusters/<имя>/kubeconfig`
- `clusters/<имя>/meta.json`
Содержимое `clusters/*/` не коммитится; в репозитории есть `clusters/.gitkeep`.
## Git и артефакты
В **`.gitignore`**: `.env`, каталоги в `clusters/` (кроме `.gitkeep`), **`__pycache__/`** и `*.pyc`, `.DS_Store`.
## Ограничения
- Образ `kindest/node:v…` должен быть доступен для pull.
- На **Windows** без WSL удобнее WSL2 + Docker Desktop.
- **kubectl** на хосте **не обязателен**: используйте веб-UI или **`make docker kubectl`** / **`make podman kubectl`** (см. выше).
- История заданий в UI/API хранится в памяти (до **200** записей); после перезапуска контейнера очищается. Завершённые записи можно удалить из памяти кнопкой на панели или **`DELETE /api/v1/jobs`**.
- При **`exec format error`** у kind пересоберите образ: `make docker rebuild COMPOSE_BUILD_FLAGS=--platform linux/arm64` (или `make podman …`, или **`make docker build`** без `--no-cache`, или `linux/amd64`).
---
**Автор:** Сергей Антропов — [devops.org.ru](https://devops.org.ru)