Merge k8s в main: добавлена поддержка Kubernetes Kind кластеров
This commit is contained in:
244
scripts/create_k8s_cluster.py
Executable file
244
scripts/create_k8s_cluster.py
Executable file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт для создания Kind кластеров
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
import sys
|
||||
import yaml
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
def run_cmd(cmd):
|
||||
"""Выполнить команду"""
|
||||
print(f"[run] {cmd}")
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"[error] {result.stderr}")
|
||||
sys.exit(1)
|
||||
print(result.stdout)
|
||||
return result.stdout
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: create_k8s_cluster.py <preset_file> <container_name>")
|
||||
sys.exit(1)
|
||||
|
||||
preset_file = sys.argv[1]
|
||||
container_name = sys.argv[2]
|
||||
|
||||
print(f"📋 Читаю пресет: {preset_file}")
|
||||
with open(preset_file, 'r') as f:
|
||||
preset = yaml.safe_load(f)
|
||||
|
||||
# Создаем Docker сеть если её нет
|
||||
docker_network = preset.get('docker_network', 'labnet')
|
||||
print(f"\n🌐 Проверка Docker сети: {docker_network}")
|
||||
result = subprocess.run(f"docker network ls --format '{{{{.Name}}}}' | grep -x {docker_network}",
|
||||
shell=True, capture_output=True, text=True)
|
||||
if not result.stdout.strip():
|
||||
print(f"📡 Создание Docker сети: {docker_network}")
|
||||
run_cmd(f"docker network create {docker_network}")
|
||||
else:
|
||||
print(f"✅ Сеть {docker_network} уже существует")
|
||||
|
||||
# Получаем конфигурацию для hosts
|
||||
hosts = preset.get('hosts', [])
|
||||
images = preset.get('images', {})
|
||||
systemd_defaults = preset.get('systemd_defaults', {})
|
||||
|
||||
# Создаем контейнеры если определены hosts
|
||||
if hosts:
|
||||
print(f"\n🐳 Создание контейнеров (всего: {len(hosts)})")
|
||||
for host in hosts:
|
||||
host_name = host['name']
|
||||
family = host['family']
|
||||
|
||||
# Проверяем существование контейнера
|
||||
result = subprocess.run(f"docker ps -a --format '{{{{.Names}}}}' | grep -x {host_name}",
|
||||
shell=True, capture_output=True, text=True)
|
||||
if result.stdout.strip():
|
||||
print(f"⚠️ Контейнер '{host_name}' уже существует, удаляем старый")
|
||||
run_cmd(f"docker rm -f {host_name}")
|
||||
|
||||
# Получаем образ
|
||||
image = images.get(family, f"inecs/ansible-lab:{family}-latest")
|
||||
|
||||
# Формируем команду docker run
|
||||
cmd_parts = [
|
||||
"docker run -d",
|
||||
f"--name {host_name}",
|
||||
f"--network {docker_network}",
|
||||
"--restart=unless-stopped"
|
||||
]
|
||||
|
||||
# Добавляем systemd настройки
|
||||
if systemd_defaults.get('privileged'):
|
||||
cmd_parts.append("--privileged")
|
||||
|
||||
for vol in systemd_defaults.get('volumes', []):
|
||||
cmd_parts.append(f"-v {vol}")
|
||||
|
||||
for tmpfs in systemd_defaults.get('tmpfs', []):
|
||||
cmd_parts.append(f"--tmpfs {tmpfs}")
|
||||
|
||||
if systemd_defaults.get('capabilities'):
|
||||
for cap in systemd_defaults['capabilities']:
|
||||
cmd_parts.append(f"--cap-add {cap}")
|
||||
|
||||
cmd_parts.append(image)
|
||||
|
||||
# Добавляем command в конец если задан
|
||||
if systemd_defaults.get('command'):
|
||||
cmd_parts.append(systemd_defaults['command'])
|
||||
|
||||
cmd = " ".join(cmd_parts)
|
||||
print(f"🚀 Создание контейнера: {host_name}")
|
||||
run_cmd(cmd)
|
||||
print(f"✅ Контейнер '{host_name}' создан")
|
||||
|
||||
kind_clusters = preset.get('kind_clusters', [])
|
||||
if not kind_clusters:
|
||||
print("\n⚠️ В пресете не определены kind кластеры")
|
||||
print("✅ Создание контейнеров завершено")
|
||||
sys.exit(0)
|
||||
|
||||
os.makedirs("/ansible/.kind", exist_ok=True)
|
||||
|
||||
for cluster in kind_clusters:
|
||||
name = cluster['name']
|
||||
config_file = f"/ansible/.kind/{name}.yaml"
|
||||
|
||||
print(f"\n☸️ Создание конфигурации для кластера: {name}")
|
||||
|
||||
# Создаем конфигурацию Kind
|
||||
config = {
|
||||
'kind': 'Cluster',
|
||||
'apiVersion': 'kind.x-k8s.io/v1alpha4',
|
||||
'nodes': [
|
||||
{'role': 'control-plane'}
|
||||
],
|
||||
'networking': {
|
||||
'apiServerAddress': '0.0.0.0',
|
||||
'apiServerPort': cluster.get('api_port', 0)
|
||||
}
|
||||
}
|
||||
|
||||
# Добавляем extraPortMappings для всех портов из addon_ports
|
||||
addon_ports = cluster.get('addon_ports', {})
|
||||
|
||||
# Ingress порты для проброса на host
|
||||
if addon_ports.get('ingress_http') or addon_ports.get('ingress_https'):
|
||||
# Добавляем extraPortMappings к control-plane узлу
|
||||
if 'extraPortMappings' not in config['nodes'][0]:
|
||||
config['nodes'][0]['extraPortMappings'] = []
|
||||
|
||||
if addon_ports.get('ingress_http'):
|
||||
config['nodes'][0]['extraPortMappings'].append({
|
||||
'containerPort': 80,
|
||||
'hostPort': addon_ports['ingress_http'],
|
||||
'protocol': 'TCP'
|
||||
})
|
||||
if addon_ports.get('ingress_https'):
|
||||
config['nodes'][0]['extraPortMappings'].append({
|
||||
'containerPort': 443,
|
||||
'hostPort': addon_ports['ingress_https'],
|
||||
'protocol': 'TCP'
|
||||
})
|
||||
|
||||
# Не добавляем extraPortMappings для портов аддонов - используем port-forward
|
||||
|
||||
# Добавляем worker nodes
|
||||
workers = cluster.get('workers', 0)
|
||||
for i in range(workers):
|
||||
config['nodes'].append({'role': 'worker'})
|
||||
|
||||
# Записываем конфигурацию
|
||||
with open(config_file, 'w') as f:
|
||||
yaml.dump(config, f)
|
||||
|
||||
print(f"✅ Конфигурация сохранена: {config_file}")
|
||||
|
||||
# Проверяем существование кластера
|
||||
result = subprocess.run(f"kind get clusters", shell=True, capture_output=True, text=True)
|
||||
existing = result.stdout.strip().split('\n') if result.returncode == 0 else []
|
||||
|
||||
if name in existing:
|
||||
print(f"⚠️ Кластер '{name}' уже существует, пропускаю")
|
||||
else:
|
||||
print(f"🚀 Создание кластера: {name}")
|
||||
run_cmd(f"kind create cluster --name {name} --config {config_file}")
|
||||
|
||||
# Подключаем контейнер k8s-controller к сети kind
|
||||
print(f"🔗 Подключение контейнера к сети kind...")
|
||||
result = subprocess.run(f"docker network inspect kind", shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
# Получаем имя контейнера из аргументов (второй аргумент)
|
||||
controller_name = sys.argv[2] if len(sys.argv) > 2 else "k8s-controller"
|
||||
result = subprocess.run(f"docker network connect kind {controller_name}", shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
print(f"✅ Контейнер {controller_name} подключен к сети kind")
|
||||
else:
|
||||
print(f"⚠️ Не удалось подключить контейнер к сети kind: {result.stderr}")
|
||||
else:
|
||||
print(f"⚠️ Сеть kind не найдена")
|
||||
|
||||
# Устанавливаем аддоны
|
||||
addons = cluster.get('addons', {})
|
||||
if not addons:
|
||||
continue
|
||||
|
||||
print(f"\n📦 Установка аддонов для кластера: {name}")
|
||||
|
||||
if addons.get('ingress_nginx'):
|
||||
print(" - Installing ingress-nginx")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify apply --validate=false -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=180s")
|
||||
|
||||
if addons.get('metrics_server'):
|
||||
print(" - Installing metrics-server")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify apply --validate=false -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml")
|
||||
patch_json = '{"spec":{"template":{"spec":{"containers":[{"name":"metrics-server","args":["--kubelet-insecure-tls","--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname"]}]}}}}'
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify -n kube-system patch deploy metrics-server -p '{patch_json}'")
|
||||
|
||||
if addons.get('istio'):
|
||||
print(" - Installing Istio")
|
||||
# Генерируем kubeconfig и заменяем 0.0.0.0 на IP control-plane узла
|
||||
run_cmd(f"kind get kubeconfig --name {name} > /tmp/istio-kubeconfig-{name}.yaml")
|
||||
# Получаем IP control-plane узла и заменяем в kubeconfig
|
||||
result = subprocess.run(f"docker inspect {name}-control-plane --format='{{{{.NetworkSettings.Networks.kind.IPAddress}}}}'",
|
||||
shell=True, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
control_plane_ip = result.stdout.strip()
|
||||
# Заменяем 0.0.0.0 на IP control-plane
|
||||
subprocess.run(f"sed -i 's/0\\.0\\.0\\.0:6443/{control_plane_ip}:6443/g' /tmp/istio-kubeconfig-{name}.yaml", shell=True)
|
||||
# Устанавливаем Istio используя kubeconfig
|
||||
run_cmd(f"KUBECONFIG=/tmp/istio-kubeconfig-{name}.yaml istioctl install -y --set profile=demo")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify -n istio-system rollout status deploy/istiod --timeout=180s")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify -n istio-system rollout status deploy/istio-ingressgateway --timeout=180s")
|
||||
|
||||
if addons.get('kiali'):
|
||||
print(" - Installing Kiali")
|
||||
subprocess.run(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify create ns istio-system", shell=True, capture_output=True)
|
||||
# Добавляем Helm репозиторий Kiali
|
||||
run_cmd(f"helm repo add kiali https://kiali.org/helm-charts")
|
||||
run_cmd(f"helm repo update")
|
||||
# Используем исправленный kubeconfig
|
||||
run_cmd(f"KUBECONFIG=/tmp/istio-kubeconfig-{name}.yaml helm upgrade --install kiali-server kiali/kiali-server --namespace istio-system --set auth.strategy=anonymous --wait --timeout 180s")
|
||||
|
||||
if addons.get('prometheus_stack'):
|
||||
print(" - Installing Prometheus Stack")
|
||||
# Добавляем Helm репозиторий Prometheus
|
||||
subprocess.run(f"helm repo add prometheus-community https://prometheus-community.github.io/helm-charts", shell=True, capture_output=True)
|
||||
subprocess.run(f"helm repo update", shell=True, capture_output=True)
|
||||
subprocess.run(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify create ns monitoring", shell=True, capture_output=True)
|
||||
# Используем исправленный kubeconfig
|
||||
run_cmd(f"KUBECONFIG=/tmp/istio-kubeconfig-{name}.yaml helm upgrade --install monitoring prometheus-community/kube-prometheus-stack --namespace monitoring --set grafana.adminPassword=admin --set grafana.defaultDashboardsTimezone=browser --wait --timeout 600s")
|
||||
run_cmd(f"kubectl --server=https://{name}-control-plane:6443 --insecure-skip-tls-verify -n monitoring rollout status deploy/monitoring-grafana --timeout=300s")
|
||||
|
||||
print(f"✅ Кластер '{name}' готов!")
|
||||
|
||||
print("\n🎉 Все кластеры созданы!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
44
scripts/delete_hosts.py
Normal file
44
scripts/delete_hosts.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт для удаления контейнеров из секции hosts пресета
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
import sys
|
||||
import yaml
|
||||
import subprocess
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: delete_hosts.py <preset_file>")
|
||||
sys.exit(1)
|
||||
|
||||
preset_file = sys.argv[1]
|
||||
|
||||
print(f"📋 Читаю пресет: {preset_file}")
|
||||
with open(preset_file, 'r') as f:
|
||||
preset = yaml.safe_load(f)
|
||||
|
||||
hosts = preset.get('hosts', [])
|
||||
if not hosts:
|
||||
print("⚠️ В пресете нет контейнеров для удаления")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"🗑️ Удаление контейнеров (всего: {len(hosts)})")
|
||||
for host in hosts:
|
||||
host_name = host['name']
|
||||
|
||||
# Проверяем существование контейнера
|
||||
result = subprocess.run(f"docker ps -a --format '{{{{.Names}}}}' | grep -x {host_name}",
|
||||
shell=True, capture_output=True, text=True)
|
||||
if result.stdout.strip():
|
||||
print(f"🗑️ Удаление контейнера: {host_name}")
|
||||
subprocess.run(f"docker rm -f {host_name}", shell=True, capture_output=True, text=True)
|
||||
print(f"✅ Контейнер '{host_name}' удален")
|
||||
else:
|
||||
print(f"⚠️ Контейнер '{host_name}' не найден")
|
||||
|
||||
print("✅ Удаление завершено")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
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()
|
||||
253
scripts/portforward.py
Executable file
253
scripts/portforward.py
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Скрипт для управления port-forward для Kubernetes сервисов
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
import sys
|
||||
import yaml
|
||||
import subprocess
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
|
||||
def get_cluster_name():
|
||||
"""Получаем имя кластера из preset файла"""
|
||||
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
||||
with open(preset_file, 'r') as f:
|
||||
preset = yaml.safe_load(f)
|
||||
return preset['kind_clusters'][0]['name']
|
||||
|
||||
def run_cmd(cmd):
|
||||
"""Выполняет команду и возвращает результат"""
|
||||
print(f"[run] {cmd}")
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"[error] {result.stderr}")
|
||||
else:
|
||||
print(result.stdout)
|
||||
return result
|
||||
|
||||
def get_portforward_pids():
|
||||
"""Получает PID процессов port-forward"""
|
||||
result = subprocess.run("ps aux | grep 'kubectl.*port-forward' | grep -v grep", shell=True, capture_output=True, text=True)
|
||||
pids = []
|
||||
for line in result.stdout.split('\n'):
|
||||
if line.strip():
|
||||
pids.append(int(line.split()[1]))
|
||||
return pids
|
||||
|
||||
def list_portforwards():
|
||||
"""Показывает список всех активных port-forward"""
|
||||
pids = get_portforward_pids()
|
||||
if not pids:
|
||||
print("❌ Нет активных port-forward")
|
||||
return
|
||||
|
||||
print("📋 Активные port-forward:")
|
||||
result = subprocess.run("ps aux | grep 'kubectl.*port-forward' | grep -v grep", shell=True, capture_output=True, text=True)
|
||||
for line in result.stdout.split('\n'):
|
||||
if line.strip():
|
||||
print(f" {line}")
|
||||
|
||||
def clear_portforwards():
|
||||
"""Завершает все процессы port-forward"""
|
||||
pids = get_portforward_pids()
|
||||
if not pids:
|
||||
print("❌ Нет активных port-forward")
|
||||
return
|
||||
|
||||
print(f"🗑️ Завершение {len(pids)} процессов port-forward...")
|
||||
for pid in pids:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
print(f"✅ Процесс {pid} завершен")
|
||||
except ProcessLookupError:
|
||||
print(f"⚠️ Процесс {pid} уже не существует")
|
||||
|
||||
# Ждем завершения процессов
|
||||
time.sleep(2)
|
||||
|
||||
# Принудительно убиваем оставшиеся
|
||||
remaining_pids = get_portforward_pids()
|
||||
if remaining_pids:
|
||||
print("⚠️ Принудительное завершение оставшихся процессов...")
|
||||
for pid in remaining_pids:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
print(f"✅ Процесс {pid} принудительно завершен")
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
def create_portforwards():
|
||||
"""Создает port-forward для всех сервисов из preset на локальном компьютере"""
|
||||
# Загружаем preset
|
||||
preset_file = "molecule/presets/k8s/kubernetes.yml"
|
||||
with open(preset_file, 'r') as f:
|
||||
preset = yaml.safe_load(f)
|
||||
|
||||
cluster_name = preset['kind_clusters'][0]['name']
|
||||
addon_ports = preset['kind_clusters'][0].get('addon_ports', {})
|
||||
|
||||
# Получаем kubeconfig из контейнера k8s-controller
|
||||
print(f"🔌 Создание port-forward для кластера: {cluster_name}")
|
||||
print("📋 Получение kubeconfig из контейнера k8s-controller...")
|
||||
|
||||
# Копируем kubeconfig из контейнера
|
||||
result = subprocess.run(
|
||||
f"docker exec k8s-controller kind get kubeconfig --name {cluster_name}",
|
||||
shell=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"❌ Ошибка получения kubeconfig: {result.stderr}")
|
||||
return
|
||||
|
||||
# Сохраняем kubeconfig во временный файл
|
||||
kubeconfig_file = "/tmp/kubeconfig-lab.yaml"
|
||||
with open(kubeconfig_file, 'w') as f:
|
||||
f.write(result.stdout)
|
||||
|
||||
# Меняем server с 0.0.0.0 на localhost для локального доступа
|
||||
subprocess.run(f"sed -i.bak 's|server: https://0.0.0.0:6443|server: https://localhost:6443|g' {kubeconfig_file}", shell=True)
|
||||
|
||||
print("✅ Kubeconfig подготовлен")
|
||||
|
||||
# Ingress HTTP (80)
|
||||
if addon_ports.get('ingress_http'):
|
||||
port = addon_ports['ingress_http']
|
||||
print(f" - Ingress HTTP: localhost:{port} -> ingress-nginx-controller:80")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "ingress-nginx",
|
||||
"svc/ingress-nginx-controller",
|
||||
f"{port}:80"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
# Ingress HTTPS (443)
|
||||
if addon_ports.get('ingress_https'):
|
||||
port = addon_ports['ingress_https']
|
||||
print(f" - Ingress HTTPS: localhost:{port} -> ingress-nginx-controller:443")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "ingress-nginx",
|
||||
"svc/ingress-nginx-controller",
|
||||
f"{port}:443"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
# Prometheus
|
||||
if addon_ports.get('prometheus'):
|
||||
port = addon_ports['prometheus']
|
||||
print(f" - Prometheus: localhost:{port} -> monitoring/monitoring-kube-prometheus-prometheus:9090")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "monitoring",
|
||||
"svc/monitoring-kube-prometheus-prometheus",
|
||||
f"{port}:9090"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
# Grafana
|
||||
if addon_ports.get('grafana'):
|
||||
port = addon_ports['grafana']
|
||||
print(f" - Grafana: localhost:{port} -> monitoring/monitoring-grafana:80")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "monitoring",
|
||||
"svc/monitoring-grafana",
|
||||
f"{port}:80"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
# Kiali
|
||||
if addon_ports.get('kiali'):
|
||||
port = addon_ports['kiali']
|
||||
print(f" - Kiali: localhost:{port} -> istio-system/kiali:20001")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "istio-system",
|
||||
"svc/kiali",
|
||||
f"{port}:20001"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
# Metrics Server
|
||||
if addon_ports.get('metrics_server'):
|
||||
port = addon_ports['metrics_server']
|
||||
print(f" - Metrics Server: localhost:{port} -> kube-system/metrics-server:4443")
|
||||
subprocess.Popen([
|
||||
"kubectl",
|
||||
f"--kubeconfig={kubeconfig_file}",
|
||||
"port-forward",
|
||||
"-n", "kube-system",
|
||||
"svc/metrics-server",
|
||||
f"{port}:4443"
|
||||
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
time.sleep(2)
|
||||
print("✅ Port-forward создан")
|
||||
list_portforwards()
|
||||
|
||||
def delete_portforward(port):
|
||||
"""Удаляет port-forward для конкретного порта"""
|
||||
pids = get_portforward_pids()
|
||||
if not pids:
|
||||
print(f"❌ Нет активных port-forward на порту {port}")
|
||||
return
|
||||
|
||||
# Находим процесс с нужным портом
|
||||
result = subprocess.run(f"ps aux | grep 'kubectl.*port-forward.*:{port}' | grep -v grep", shell=True, capture_output=True, text=True)
|
||||
if not result.stdout.strip():
|
||||
print(f"❌ Не найден port-forward на порту {port}")
|
||||
return
|
||||
|
||||
# Извлекаем PID
|
||||
pid = int(result.stdout.split()[1])
|
||||
print(f"🗑️ Завершение port-forward на порту {port} (PID: {pid})...")
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
print(f"✅ Port-forward на порту {port} завершен")
|
||||
except ProcessLookupError:
|
||||
print(f"⚠️ Процесс {pid} уже не существует")
|
||||
|
||||
def recreate_portforwards():
|
||||
"""Пересоздает port-forward: удаляет существующие и создает заново"""
|
||||
print("🔄 Пересоздание port-forward...")
|
||||
clear_portforwards()
|
||||
time.sleep(1)
|
||||
create_portforwards()
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: portforward.py <create|list|delete|clear|recreate> [port]")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "create":
|
||||
create_portforwards()
|
||||
elif command == "list":
|
||||
list_portforwards()
|
||||
elif command == "clear":
|
||||
clear_portforwards()
|
||||
elif command == "recreate":
|
||||
recreate_portforwards()
|
||||
elif command == "delete":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: portforward.py delete <port>")
|
||||
sys.exit(1)
|
||||
port = sys.argv[2]
|
||||
delete_portforward(port)
|
||||
else:
|
||||
print(f"❌ Неизвестная команда: {command}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Автоматическая настройка CI/CD для AnsibleLab
|
||||
# Автоматическая настройка CI/CD для DevOpsLab
|
||||
# Автор: Сергей Антропов
|
||||
# Сайт: https://devops.org.ru
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "🔧 Настройка CI/CD для AnsibleLab..."
|
||||
echo "🔧 Настройка CI/CD для DevOpsLab..."
|
||||
|
||||
# Создание директории .github/workflows
|
||||
mkdir -p .github/workflows
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Скрипт для тестирования собственных образов AnsibleLab
|
||||
# Скрипт для тестирования собственных образов DevOpsLab
|
||||
# Автор: Сергей Антропов
|
||||
# Сайт: https://devops.org.ru
|
||||
|
||||
@@ -190,7 +190,7 @@ cleanup() {
|
||||
|
||||
# Основная функция
|
||||
main() {
|
||||
log "🚀 Тестирование собственных образов AnsibleLab"
|
||||
log "🚀 Тестирование собственных образов DevOpsLab"
|
||||
echo "=========================================="
|
||||
|
||||
# Проверки
|
||||
|
||||
Reference in New Issue
Block a user