Веб-интерфейс: страница /clusters, навигация и крошки для кластеров

- Выделена страница списка кластеров, панель упрощена; nav_active и крошки
  ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL.
- Доработки дашборда, аддонов, журнала, стилей и API-документации.
- Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
This commit is contained in:
Sergey Antropoff
2026-04-04 13:42:21 +03:00
parent 17f6233fd7
commit eb063aec20
38 changed files with 3990 additions and 734 deletions

102
Makefile
View File

@@ -10,17 +10,50 @@
#
# Автор: Сергей Антропов — https://devops.org.ru
ifneq (,$(filter podman,$(MAKECMDGOALS)))
COMPOSE := podman compose
else ifneq (,$(filter docker,$(MAKECMDGOALS)))
COMPOSE := docker compose
endif
.PHONY: help docker podman _require_runtime up down restart logs ps setup clusters-dir check-docker build rebuild kubectl
KIND_K8S_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
SETUP_ENV_SCRIPT := $(KIND_K8S_DIR)/scripts/setup_env_interactive.py
PYTHON ?= python3
ifneq (,$(filter podman,$(MAKECMDGOALS)))
COMPOSE := podman compose
# Сокет Podman на хосте (перекрывает CONTAINER_SOCKET из .env, если там путь Docker).
PODMAN_HOST_SOCK := $(shell $(PYTHON) "$(KIND_K8S_DIR)/scripts/detect_podman_socket.py")
# docker-compose из podman compose часто подставляет том из .env, игнорируя префикс VAR= в командной строке.
# Файл .env.podman.override (см. цель _podman_env_override) задаёт CONTAINER_SOCKET после .env.
PODMAN_COMPOSE_ENV_FILE := --env-file .env.podman.override
# user: в контейнере для доступа к смонтированному podman.sock:
# «podman compose» на macOS часто дергает docker-compose к API Podman — keep-id не срабатывает, uid с сокета даёт EACCES.
# По умолчанию 0:0 (как у типичного docker.sock); переопределение: KIND_K8S_PODMAN_CONTAINER_UIDGID=501:20
# или KIND_K8S_PODMAN_USE_SOCKET_OWNER=1 (владелец по stat, как detect_podman_socket.py --print-owner).
ifneq (,$(KIND_K8S_PODMAN_USE_SOCKET_OWNER))
_PODMAN_UIDGID := $(shell $(PYTHON) "$(KIND_K8S_DIR)/scripts/detect_podman_socket.py" --print-owner)
else
ifneq (,$(KIND_K8S_PODMAN_CONTAINER_UIDGID))
_PODMAN_UIDGID := $(KIND_K8S_PODMAN_CONTAINER_UIDGID)
else
_PODMAN_UIDGID := 0:0
endif
endif
_PODMAN_UID_FIRST := $(word 1,$(subst :, ,$(_PODMAN_UIDGID)))
_PODMAN_HOME := $(if $(filter 0,$(_PODMAN_UID_FIRST)),/root,/tmp)
ifneq (,$(findstring var/folders,$(PODMAN_HOST_SOCK)))
$(error PODMAN_HOST_SOCK содержит /var/folders/ — этот API-сокет нельзя смонтировать в compose. Обновите репозиторий (scripts/detect_podman_socket.py) и удалите такой путь из .env)
endif
# Внутри контейнера — путь podman, не docker.sock; DOCKER_HOST должен указывать на тот же путь (API совместим).
COMPOSE_PODMAN_ENV := KIND_K8S_CONTAINER_UIDGID=$(_PODMAN_UIDGID) KIND_K8S_CONTAINER_HOME=$(_PODMAN_HOME) CONTAINER_SOCKET=$(PODMAN_HOST_SOCK) CONTAINER_SOCKET_MOUNT_TARGET=/run/podman/podman.sock KIND_K8S_REMOTE_SOCKET_URI=unix:///run/podman/podman.sock
COMPOSE_FILE_ARGS := -f docker-compose.yml -f docker-compose.podman.yml
else ifneq (,$(filter docker,$(MAKECMDGOALS)))
COMPOSE := docker compose
# Сброс путей от Podman в .env: сокет в контейнере снова /var/run/docker.sock.
COMPOSE_DOCKER_OVERRIDES := CONTAINER_SOCKET_MOUNT_TARGET=/var/run/docker.sock KIND_K8S_REMOTE_SOCKET_URI=unix:///var/run/docker.sock
endif
COMPOSE_PODMAN_ENV ?=
COMPOSE_DOCKER_OVERRIDES ?=
COMPOSE_FILE_ARGS ?=
PODMAN_COMPOSE_ENV_FILE ?=
.PHONY: help docker podman _require_runtime _podman_env_override up down restart logs ps setup clusters-dir check-docker build rebuild kubectl probe-sockets
# При «exec format error» у kind: make docker build COMPOSE_BUILD_FLAGS=--platform linux/arm64
COMPOSE_BUILD_FLAGS ?=
# Для цели kubectl: имя кластера и аргументы kubectl после --kubeconfig (по умолчанию: get nodes).
@@ -57,20 +90,35 @@ _require_runtime:
exit 1; \
fi
up: _require_runtime clusters-dir build ## (с docker/podman) Поднять веб-UI в фоне
cd "$(KIND_K8S_DIR)" && $(COMPOSE) up -d kind-k8s-web
# Только для make podman …: перекрыть CONTAINER_SOCKET из .env (устаревший /var/folders/… ломает volume).
_podman_env_override:
@if [ -n "$(COMPOSE_PODMAN_ENV)" ]; then \
printf '%s\n' \
"CONTAINER_SOCKET=$(PODMAN_HOST_SOCK)" \
"KIND_K8S_CONTAINER_UIDGID=$(_PODMAN_UIDGID)" \
"KIND_K8S_CONTAINER_HOME=$(_PODMAN_HOME)" \
"CONTAINER_SOCKET_MOUNT_TARGET=/run/podman/podman.sock" \
"KIND_K8S_REMOTE_SOCKET_URI=unix:///run/podman/podman.sock" \
> "$(KIND_K8S_DIR)/.env.podman.override"; \
fi
down: _require_runtime ## (с docker/podman) Остановить compose в этом каталоге
cd "$(KIND_K8S_DIR)" && $(COMPOSE) down
up: _require_runtime _podman_env_override clusters-dir build ## (с docker/podman) Поднять веб-UI в фоне
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) up -d kind-k8s-web
restart: _require_runtime ## (с docker/podman) Перезапустить контейнер kind-k8s-web
cd "$(KIND_K8S_DIR)" && $(COMPOSE) restart kind-k8s-web
down: _require_runtime _podman_env_override ## (с docker/podman) Остановить compose в этом каталоге
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) down
logs: _require_runtime ## (с docker/podman) Логи kind-k8s-web (follow -f)
cd "$(KIND_K8S_DIR)" && $(COMPOSE) logs -f kind-k8s-web
restart: _require_runtime _podman_env_override ## (с docker/podman) Перезапустить контейнер kind-k8s-web
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) restart kind-k8s-web
ps: _require_runtime ## (с docker/podman) Статус контейнеров compose-проекта
cd "$(KIND_K8S_DIR)" && $(COMPOSE) ps
logs: _require_runtime _podman_env_override ## (с docker/podman) Логи kind-k8s-web (follow -f)
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) logs -f kind-k8s-web
ps: _require_runtime _podman_env_override ## (с docker/podman) Статус контейнеров compose-проекта
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) ps
probe-sockets: ## Проверить сокеты Docker/Podman (доступ без permission denied)
@$(PYTHON) "$(KIND_K8S_DIR)/scripts/probe_container_sockets.py"
setup: ## Интерактивно создать .env (scripts/setup_env_interactive.py; нужен python3 на хосте)
@$(PYTHON) "$(SETUP_ENV_SCRIPT)"
@@ -78,27 +126,27 @@ setup: ## Интерактивно создать .env (scripts/setup_env_intera
clusters-dir: ## Каталог clusters/ для тома (если ещё нет)
@mkdir -p "$(KIND_K8S_DIR)/clusters"
check-docker: _require_runtime ## (с docker/podman) Проверить CLI и compose
check-docker: _require_runtime _podman_env_override ## (с docker/podman) Проверить CLI и compose
@case "$(COMPOSE)" in \
docker*) command -v docker >/dev/null 2>&1 || { echo >&2 "docker не найден в PATH."; exit 1; } ;; \
podman*) command -v podman >/dev/null 2>&1 || { echo >&2 "podman не найден в PATH."; exit 1; } ;; \
esac
@$(COMPOSE) version >/dev/null 2>&1 || { echo >&2 "Команда «$(COMPOSE) version» недоступна."; exit 1; }
@$(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) version >/dev/null 2>&1 || { echo >&2 "Команда «$(COMPOSE) version» недоступна."; exit 1; }
@echo "$(COMPOSE): OK"
# kubectl в образе; путь к kubeconfig — как у веб-UI (подстановка server через host.docker.internal, см. kubeconfig_patch.py).
kubectl: _require_runtime ## (с docker/podman) kubectl в контейнере: CLUSTER=имя [KUBECTL_ARGS="get pods -A"]
kubectl: _require_runtime _podman_env_override ## (с docker/podman) kubectl в контейнере: CLUSTER=имя [KUBECTL_ARGS="get pods -A"]
@if [ -z "$(CLUSTER)" ]; then \
echo >&2 "Задайте CLUSTER=<имя_кластера> (каталог в ./clusters/)."; \
echo >&2 "Пример: make docker kubectl CLUSTER=dev"; \
echo >&2 "Свои подкоманды: make docker kubectl CLUSTER=dev KUBECTL_ARGS=\"get pods -A\""; \
exit 1; \
fi
cd "$(KIND_K8S_DIR)" && KC=$$($(COMPOSE) exec -T kind-k8s-web python3 scripts/effective_kubeconfig_path.py $(CLUSTER) | tr -d '\r') && \
$(COMPOSE) exec kind-k8s-web kubectl --kubeconfig="$$KC" $(KUBECTL_ARGS)
cd "$(KIND_K8S_DIR)" && KC=$$($(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) exec -T kind-k8s-web python3 scripts/effective_kubeconfig_path.py $(CLUSTER) | tr -d '\r') && \
$(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) exec kind-k8s-web kubectl --kubeconfig="$$KC" $(KUBECTL_ARGS)
build: _require_runtime clusters-dir ## (с docker/podman) Собрать образ kind-k8s-tools:local
cd "$(KIND_K8S_DIR)" && $(COMPOSE) build $(COMPOSE_BUILD_FLAGS)
build: _require_runtime _podman_env_override clusters-dir ## (с docker/podman) Собрать образ kind-k8s-tools:local
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) build $(COMPOSE_BUILD_FLAGS)
rebuild: _require_runtime clusters-dir ## (с docker/podman) Пересобрать образ без кэша и пересоздать контейнер kind-k8s-web
cd "$(KIND_K8S_DIR)" && $(COMPOSE) build --no-cache $(COMPOSE_BUILD_FLAGS) && $(COMPOSE) up -d --force-recreate kind-k8s-web
rebuild: _require_runtime _podman_env_override clusters-dir ## (с docker/podman) Пересобрать образ без кэша и пересоздать контейнер kind-k8s-web
cd "$(KIND_K8S_DIR)" && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) build --no-cache $(COMPOSE_BUILD_FLAGS) && $(COMPOSE_PODMAN_ENV) $(COMPOSE_DOCKER_OVERRIDES) $(COMPOSE) $(COMPOSE_FILE_ARGS) $(PODMAN_COMPOSE_ENV_FILE) up -d --force-recreate kind-k8s-web