#!/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 (тот же файл, что пишет create / UI).") 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 в PATH.", file=sys.stderr) print( " Запустите скрипт внутри контейнера приложения (там kubectl в образе), например:", file=sys.stderr, ) print( " docker compose exec kind-k8s-web python3 /opt/kind-k8s/app/cluster_status.py <имя>", file=sys.stderr, ) 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()