- Исправлена логика добавления extraPortMappings в конфигурацию Kind - Порты 80 и 443 теперь правильно пробрасываются на host (8081, 8443) - Создана документация k8s-ingress-fix.md с инструкциями
		
			
				
	
	
		
			245 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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()
 |