feat: добавлен детальный отчет о состоянии кластера

- Создан скрипт scripts/k8s_status.py для детального отчета
- Показывает: узлы, namespaces, pods, deployments, daemonsets,
  statefulsets, services, ingress, PVC, события, Helm релизы
- Исправлена проблема с подключением kubectl (используется
  прямой адрес control-plane)
- Команда make k8s status теперь показывает полный отчет
This commit is contained in:
Сергей Антропов
2025-10-26 10:16:24 +03:00
parent 6ef4090fb2
commit fcf3f33e80
2 changed files with 302 additions and 3 deletions

View File

@@ -1117,7 +1117,7 @@ k8s:
docker exec $$CONTAINER_NAME bash -c "kind get clusters | xargs -I {} kind start cluster --name {}" 2>/dev/null || true; \ docker exec $$CONTAINER_NAME bash -c "kind get clusters | xargs -I {} kind start cluster --name {}" 2>/dev/null || true; \
echo "✅ Kind кластер запущен";; \ echo "✅ Kind кластер запущен";; \
status) \ status) \
echo "📊 Статус Kind кластеров:"; \ echo "📊 Детальный отчет о состоянии кластера..."; \
PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \ PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \
if [ -z "$$PRESET_ARG" ]; then \ if [ -z "$$PRESET_ARG" ]; then \
echo "❌ Ошибка: Укажите пресет"; \ echo "❌ Ошибка: Укажите пресет"; \
@@ -1126,8 +1126,7 @@ k8s:
fi; \ fi; \
CONTAINER_NAME=k8s-controller; \ CONTAINER_NAME=k8s-controller; \
if docker ps | grep -q $$CONTAINER_NAME; then \ if docker ps | grep -q $$CONTAINER_NAME; then \
docker exec $$CONTAINER_NAME bash -c "kind get clusters" 2>/dev/null || echo " Нет кластеров"; \ python3 scripts/k8s_status.py; \
docker exec $$CONTAINER_NAME bash -c "kind get clusters | while read cluster; do echo \"Кластер: \$$cluster\"; kubectl --context kind-\$$cluster get nodes 2>/dev/null || true; done" 2>/dev/null || true; \
else \ else \
echo "⚠️ Контейнер $$CONTAINER_NAME не запущен"; \ echo "⚠️ Контейнер $$CONTAINER_NAME не запущен"; \
echo "💡 Запустите: make k8s create $$PRESET_ARG"; \ echo "💡 Запустите: make k8s create $$PRESET_ARG"; \

300
scripts/k8s_status.py Executable file
View File

@@ -0,0 +1,300 @@
#!/usr/bin/env python3
"""
Скрипт для детального отчета о состоянии Kubernetes кластера
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import sys
import subprocess
import json
def get_cluster_name():
"""Получает имя кластера"""
result = subprocess.run("docker exec k8s-controller kind get clusters | head -1", shell=True, capture_output=True, text=True)
if result.returncode == 0:
return result.stdout.strip()
return None
def run_kubectl_cmd(cmd):
"""Выполняет команду kubectl внутри контейнера k8s-controller"""
cluster_name = get_cluster_name()
if cluster_name:
# Используем прямой адрес control-plane
server = f"https://{cluster_name}-control-plane:6443"
cmd_with_server = f"--server={server} --insecure-skip-tls-verify {cmd}"
else:
cmd_with_server = cmd
full_cmd = f"docker exec k8s-controller kubectl {cmd_with_server}"
result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True)
return result.stdout
def print_section(title):
"""Выводит заголовок секции"""
print(f"\n{'='*70}")
print(f" {title}")
print(f"{'='*70}\n")
def print_subsection(title):
"""Выводит подзаголовок"""
print(f"\n{''*70}")
print(f" {title}")
print(f"{''*70}\n")
def get_cluster_info():
"""Получает информацию о кластере"""
print_section("📊 ОБЩАЯ ИНФОРМАЦИЯ О КЛАСТЕРЕ")
# Версия Kubernetes
version = run_kubectl_cmd("version --short")
print(version)
def get_nodes_status():
"""Получает статус узлов"""
print_section("🖥️ УЗЛЫ КЛАСТЕРА")
nodes = run_kubectl_cmd("get nodes -o wide")
print(nodes)
# Детальная информация о каждом узле
node_names = run_kubectl_cmd("get nodes -o jsonpath='{.items[*].metadata.name}'")
if node_names.strip():
for node in node_names.strip().split():
print_subsection(f"Узел: {node}")
node_info = run_kubectl_cmd(f"describe node {node}")
print(node_info)
def get_namespaces():
"""Получает список namespace"""
print_section("📁 NAMESPACES")
namespaces = run_kubectl_cmd("get namespaces")
print(namespaces)
def get_pods_by_namespace():
"""Получает поды по namespace"""
print_section("🪟 PODS")
# Получаем список namespace
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
for ns in namespaces:
print_subsection(f"Namespace: {ns}")
pods = run_kubectl_cmd(f"get pods -n {ns} -o wide")
if pods.strip():
print(pods)
else:
print(" (пусто)")
def get_services():
"""Получает сервисы"""
print_section("🔗 SERVICES")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
for ns in namespaces:
print_subsection(f"Namespace: {ns}")
services = run_kubectl_cmd(f"get services -n {ns}")
if services.strip():
print(services)
else:
print(" (пусто)")
def get_ingress():
"""Получает Ingress ресурсы"""
print_section("🌐 INGRESS")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
ingress_found = False
for ns in namespaces:
ingress = run_kubectl_cmd(f"get ingress -n {ns} 2>/dev/null")
if ingress.strip() and "No resources found" not in ingress:
ingress_found = True
print_subsection(f"Namespace: {ns}")
print(ingress)
if not ingress_found:
print(" Ingress ресурсы не найдены")
def get_pvcs():
"""Получает PersistentVolumeClaims"""
print_section("💾 VOLUMES (PVC)")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
pvc_found = False
for ns in namespaces:
pvcs = run_kubectl_cmd(f"get pvc -n {ns} 2>/dev/null")
if pvcs.strip() and "No resources found" not in pvcs:
pvc_found = True
print_subsection(f"Namespace: {ns}")
print(pvcs)
if not pvc_found:
print(" PVC не найдены")
def get_deployments():
"""Получает Deployments"""
print_section("🚀 DEPLOYMENTS")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
for ns in namespaces:
print_subsection(f"Namespace: {ns}")
deployments = run_kubectl_cmd(f"get deployments -n {ns}")
if deployments.strip():
print(deployments)
else:
print(" (пусто)")
def get_daemonsets():
"""Получает DaemonSets"""
print_section("🔧 DAEMONSETS")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
for ns in namespaces:
print_subsection(f"Namespace: {ns}")
daemonsets = run_kubectl_cmd(f"get daemonsets -n {ns}")
if daemonsets.strip():
print(daemonsets)
else:
print(" (пусто)")
def get_statefulsets():
"""Получает StatefulSets"""
print_section("🗄️ STATEFULSETS")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
statefulsets_found = False
for ns in namespaces:
statefulsets = run_kubectl_cmd(f"get statefulsets -n {ns} 2>/dev/null")
if statefulsets.strip() and "No resources found" not in statefulsets:
statefulsets_found = True
print_subsection(f"Namespace: {ns}")
print(statefulsets)
if not statefulsets_found:
print(" StatefulSets не найдены")
def get_events():
"""Получает события"""
print_section("📅 СОБЫТИЯ (EVENTS)")
namespaces_output = run_kubectl_cmd("get namespaces -o json")
try:
namespaces_data = json.loads(namespaces_output)
namespaces = [ns['metadata']['name'] for ns in namespaces_data['items']]
except:
namespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease']
for ns in namespaces:
print_subsection(f"Namespace: {ns}")
events = run_kubectl_cmd(f"get events -n {ns} --sort-by='.lastTimestamp' | tail -20")
if events.strip():
print(events)
else:
print(" (пусто)")
def get_helm_releases():
"""Получает Helm релизы"""
print_section("📦 HELM RELEASES")
helm_output = run_kubectl_cmd("helm list --all-namespaces 2>/dev/null")
if helm_output.strip():
print(helm_output)
else:
print(" Helm релизы не найдены")
def get_resource_usage():
"""Получает использование ресурсов"""
print_section("📈 ИСПОЛЬЗОВАНИЕ РЕСУРСОВ")
try:
# Проверяем наличие metrics-server
top_nodes = run_kubectl_cmd("top nodes 2>/dev/null")
if top_nodes.strip():
print_subsection("Узлы:")
print(top_nodes)
except:
print(" metrics-server не установлен или недоступен")
try:
top_pods = run_kubectl_cmd("top pods --all-namespaces 2>/dev/null")
if top_pods.strip():
print_subsection("Pods (топ-20):")
# Берем только первые 20 строк
lines = top_pods.strip().split('\n')
print('\n'.join(lines[:21])) # + заголовок
except:
pass
def main():
"""Главная функция"""
# Проверяем доступность контейнера
result = subprocess.run("docker ps | grep k8s-controller", shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("❌ Контейнер k8s-controller не запущен")
sys.exit(1)
print("="*70)
print(" ДЕТАЛЬНЫЙ ОТЧЕТ О СОСТОЯНИИ KUBERNETES КЛАСТЕРА")
print("="*70)
get_cluster_info()
get_nodes_status()
get_namespaces()
get_resource_usage()
get_pods_by_namespace()
get_deployments()
get_daemonsets()
get_statefulsets()
get_services()
get_ingress()
get_pvcs()
get_events()
get_helm_releases()
print("\n" + "="*70)
print(" ОТЧЕТ ЗАВЕРШЕН")
print("="*70 + "\n")
if __name__ == '__main__':
main()