Files
KindClustersDashboard/app/cluster_status.py
Sergey Antropoff e46a62cfdb Веб-UI FastAPI, REST API v1, интерактивный setup без env.example
- Дашборд (Jinja2 + static), управление кластерами kind, задания и kubeconfig.
- API: health, stats, clusters CRUD, versions, jobs; документация app/docs/api_routes.md.
- Docker Compose: том app, uvicorn reload, KIND_K8S_PATCH_KUBECONFIG по умолчанию 1.
- setup_env_interactive.py: список переменных в скрипте, удалён env.example.
- Makefile: явный префикс docker/podman; прочие правки CLI и ядра кластеров.
2026-04-04 05:39:53 +03:00

175 lines
5.7 KiB
Python
Executable File
Raw 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.

#!/usr/bin/env python3
"""Проверка статуса кластеров kind: регистрация, kubeconfig, узлы (kubectl).
Запуск без аргументов — все кластеры из `kind get clusters`.
Один аргумент — только указанное имя.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
Требования: kind и kubectl в PATH (в образе kind-k8s-tools уже есть).
"""
from __future__ import annotations
import argparse
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from kind_k8s_paths import clusters_dir
def _kind_cluster_names() -> list[str]:
p = subprocess.run(["kind", "get", "clusters"], capture_output=True, text=True)
if p.returncode != 0:
return []
lines = [x.strip() for x in (p.stdout or "").splitlines() if x.strip()]
return [x for x in lines if "no kind" not in x.lower()]
def _kubeconfig_for_cluster(name: str) -> tuple[str | None, str]:
"""Временный файл kubeconfig или None; второе значение — пояснение."""
p = subprocess.run(
["kind", "get", "kubeconfig", "--name", name],
capture_output=True,
text=True,
)
if p.returncode != 0:
err = (p.stderr or p.stdout or "").strip()
return None, err or "kind get kubeconfig не удался"
data = (p.stdout or "").strip()
if not data:
return None, "пустой kubeconfig"
with tempfile.NamedTemporaryFile(
mode="w",
suffix=".kubeconfig",
prefix=f"kind-{name}-",
delete=False,
encoding="utf-8",
) as f:
f.write(data)
return f.name, ""
def _kubectl_nodes(kubeconfig: str) -> tuple[int, str]:
p = subprocess.run(
[
"kubectl",
"--kubeconfig",
kubeconfig,
"get",
"nodes",
"-o",
"wide",
"--request-timeout=10s",
],
capture_output=True,
text=True,
)
out = (p.stdout or "").strip()
err = (p.stderr or "").strip()
msg = out if out else err
return p.returncode, msg
def _local_meta(name: str) -> dict[str, str] | None:
meta = CLUSTERS_DIR / name / "meta.json"
if not meta.is_file():
return None
try:
import json
raw = json.loads(meta.read_text(encoding="utf-8"))
if isinstance(raw, dict):
return {str(k): str(v) for k, v in raw.items() if isinstance(k, str)}
except Exception:
pass
return None
def _print_cluster(name: str, *, kube_path_saved: Path | None) -> None:
print(f"── Кластер: {name} ──")
if kube_path_saved and kube_path_saved.is_file():
print(f" Сохранённый kubeconfig: {kube_path_saved}")
meta = _local_meta(name)
if meta:
ver = meta.get("kubernetes_version_tag") or meta.get("node_image", "")
wn = meta.get("worker_nodes", "")
print(f" meta.json: версия={ver}, workers={wn}")
# С хоста удобнее тот же файл, что после create (в т.ч. с патчем 127.0.0.1:порт).
use_path: str | None = None
if kube_path_saved and kube_path_saved.is_file():
use_path = str(kube_path_saved)
print(" Проверка API: kubectl с сохранённым kubeconfig (как на хосте после создания кластера).")
tmp_kc: str | None = None
if not use_path:
tmp_kc, kerr = _kubeconfig_for_cluster(name)
if not tmp_kc:
print(f" Статус API: недоступен ({kerr})")
return
use_path = tmp_kc
try:
rc, msg = _kubectl_nodes(use_path)
if rc == 0:
print(" Узлы (kubectl get nodes -o wide):")
for line in msg.splitlines():
print(f" {line}")
else:
print(f" kubectl: код {rc}")
for line in msg.splitlines()[:20]:
print(f" {line}")
finally:
if tmp_kc:
Path(tmp_kc).unlink(missing_ok=True)
def main() -> None:
CLUSTERS_DIR = clusters_dir()
parser = argparse.ArgumentParser(description="Статус кластеров kind")
parser.add_argument(
"cluster",
nargs="?",
help="Имя кластера (если не указано — все из kind get clusters)",
)
args = parser.parse_args()
if not shutil.which("kind"):
print("Не найден kind.", file=sys.stderr)
print(" Статус узлов — в веб-интерфейсе (make docker up) или внутри контейнера kind-k8s-web.", file=sys.stderr)
print(
" Либо установите kind на хост: https://kind.sigs.k8s.io/docs/user/quick-start/#installation",
file=sys.stderr,
)
sys.exit(127)
if not shutil.which("kubectl"):
print("Не найден kubectl.", file=sys.stderr)
sys.exit(127)
names = _kind_cluster_names()
if args.cluster:
if args.cluster not in names:
print(f"Кластер «{args.cluster}» не найден в kind get clusters.", file=sys.stderr)
print("Известные:", ", ".join(names) if names else "(пусто)", file=sys.stderr)
sys.exit(1)
names = [args.cluster]
if not names:
print("Нет кластеров kind (kind get clusters).")
return
for name in names:
saved = CLUSTERS_DIR / name / "kubeconfig"
kube_saved = saved if saved.is_file() else None
_print_cluster(name, kube_path_saved=kube_saved)
print()
if __name__ == "__main__":
main()