diff --git a/Makefile b/Makefile index e3ec67d..aa4b2f9 100644 --- a/Makefile +++ b/Makefile @@ -1117,7 +1117,7 @@ k8s: docker exec $$CONTAINER_NAME bash -c "kind get clusters | xargs -I {} kind start cluster --name {}" 2>/dev/null || true; \ echo "✅ Kind кластер запущен";; \ status) \ - echo "📊 Статус Kind кластеров:"; \ + echo "📊 Детальный отчет о состоянии кластера..."; \ PRESET_ARG="$(word 3, $(MAKECMDGOALS))"; \ if [ -z "$$PRESET_ARG" ]; then \ echo "❌ Ошибка: Укажите пресет"; \ @@ -1126,8 +1126,7 @@ k8s: fi; \ CONTAINER_NAME=k8s-controller; \ if docker ps | grep -q $$CONTAINER_NAME; then \ - docker exec $$CONTAINER_NAME bash -c "kind get clusters" 2>/dev/null || echo " Нет кластеров"; \ - 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; \ + python3 scripts/k8s_status.py; \ else \ echo "⚠️ Контейнер $$CONTAINER_NAME не запущен"; \ echo "💡 Запустите: make k8s create $$PRESET_ARG"; \ diff --git a/scripts/k8s_status.py b/scripts/k8s_status.py new file mode 100755 index 0000000..5c76c51 --- /dev/null +++ b/scripts/k8s_status.py @@ -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()