first commit

This commit is contained in:
Sergey Antropoff
2026-04-04 05:15:54 +03:00
commit ae961ef5fe
25 changed files with 2022 additions and 0 deletions

174
app/cluster_status.py Executable file
View File

@@ -0,0 +1,174 @@
#!/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 (как на хосте после make create).")
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 -C kind-k8s-develop status (kind внутри образа).", 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()