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:
5
Makefile
5
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; \
|
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
300
scripts/k8s_status.py
Executable 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()
|
||||||
Reference in New Issue
Block a user