- Molecule: драйвер delegated, коллекция containers.podman, create/destroy/verify на Podman - Makefile: все вызовы docker заменены на podman, сокет /run/podman/podman.sock - Сборка образов: podman build (без buildx), buildall/buildall-image — только локально без push - Ansible-controller: Podman в образе, docker-compose на podman compose, сокет Podman - K8s: Kind заменён на Minikube (драйвер podman), скрипты и Makefile обновлены - Пресеты: проверка локальных образов, без podman pull (registry запрещён) - Документация: docs/podman.md, docs/quickstart-for-dummies.md (роли, плейбук, линт, тесты, пресеты, инвентори) - README: ссылка на quickstart-for-dummies Made-with: Cursor
296 lines
9.9 KiB
Python
Executable File
296 lines
9.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Скрипт для детального отчета о состоянии Kubernetes кластера
|
||
Автор: Сергей Антропов
|
||
Сайт: https://devops.org.ru
|
||
"""
|
||
import sys
|
||
import subprocess
|
||
import json
|
||
|
||
def get_cluster_name():
|
||
"""Получает имя текущего контекста (Minikube)."""
|
||
result = subprocess.run(
|
||
"kubectl config current-context 2>/dev/null", shell=True, capture_output=True, text=True
|
||
)
|
||
if result.returncode == 0:
|
||
return result.stdout.strip()
|
||
return None
|
||
|
||
|
||
def run_kubectl_cmd(cmd):
|
||
"""Выполняет команду kubectl на хосте (контекст Minikube)."""
|
||
full_cmd = f"kubectl {cmd}"
|
||
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()
|