--- - hosts: localhost gather_facts: false vars: # Получаем preset из переменной окружения или используем default preset_name: "{{ lookup('env', 'MOLECULE_PRESET') | default('default') }}" # Проверяем сначала в папке k8s, затем в основной папке presets preset_file: "{{ '/workspace/molecule/presets/k8s/' + preset_name + '.yml' if (preset_name in ['k8s-minimal', 'kubernetes', 'k8s-full'] or preset_name.startswith('k8s-')) else '/workspace/molecule/presets/' + preset_name + '.yml' }}" # Fallback значения если preset файл не найден docker_network: labnet generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini" images: alt: "inecs/ansible-lab:alt-linux-latest" astra: "inecs/ansible-lab:astra-linux-latest" rhel: "inecs/ansible-lab:rhel-latest" centos: "inecs/ansible-lab:centos-latest" alma: "inecs/ansible-lab:alma-latest" rocky: "inecs/ansible-lab:rocky-latest" redos: "inecs/ansible-lab:redos-latest" ubuntu: "inecs/ansible-lab:ubuntu-latest" debian: "inecs/ansible-lab:debian-latest" systemd_defaults: privileged: true command: "/sbin/init" volumes: - "/sys/fs/cgroup:/sys/fs/cgroup:rw" tmpfs: ["/run", "/run/lock"] capabilities: ["SYS_ADMIN"] hosts: - name: u1 family: debian groups: [test] kind_clusters: [] tasks: # - name: Install required collections # command: ansible-galaxy collection install -r /workspace/requirements.yml # delegate_to: localhost # ignore_errors: true # register: collections_install # changed_when: false # run_once: true # become: true # vars: # ansible_python_interpreter: /usr/bin/python3 # environment: # ANSIBLE_COLLECTIONS_PATH: /usr/share/ansible/collections # Определяем архитектуру системы для корректной загрузки образов - name: Detect system architecture shell: | arch=$(uname -m) case $arch in x86_64) echo "linux/amd64" ;; aarch64|arm64) echo "linux/arm64" ;; armv7l) echo "linux/arm/v7" ;; *) echo "linux/amd64" ;; esac register: detected_platform changed_when: false - name: Set ansible_architecture variable set_fact: ansible_architecture: "{{ detected_platform.stdout }}" - name: Load preset configuration include_vars: "{{ preset_file }}" when: preset_file is file ignore_errors: true # Фильтрация хостов по поддерживаемым платформам - name: Filter hosts by supported platforms set_fact: filtered_hosts: "{{ filtered_hosts | default([]) + [item] }}" loop: "{{ hosts }}" when: | item.supported_platforms is not defined or ansible_architecture in item.supported_platforms - name: Update hosts list with filtered results set_fact: hosts: "{{ filtered_hosts | default(hosts) }}" - name: Display filtered hosts debug: msg: "Platform {{ ansible_architecture }}: {{ hosts | length }} hosts will be deployed" - name: Ensure network exists community.docker.docker_network: name: "{{ docker_network }}" state: present # SYSTEMD nodes - name: Pull systemd images with correct platform command: "docker pull --platform {{ ansible_architecture }} {{ images[item.family] }}" loop: "{{ hosts | selectattr('type','undefined') | list }}" loop_control: { label: "{{ item.name }}" } when: item.family is defined and images[item.family] is defined register: pull_result ignore_errors: yes - name: Display pull results debug: msg: "Pulled {{ item.item.name }}: {{ 'OK' if (item.rc is defined and item.rc == 0) else 'SKIPPED (not available for this platform)' }}" loop: "{{ pull_result.results | default([]) }}" loop_control: label: "{{ item.item.name }}" - name: Start systemd nodes community.docker.docker_container: name: "{{ item.name }}" image: "{{ images[item.family] }}" networks: - name: "{{ docker_network }}" privileged: "{{ systemd_defaults.privileged }}" command: "{{ systemd_defaults.command }}" volumes: "{{ systemd_defaults.volumes | default([]) + (item.volumes | default([])) }}" tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}" capabilities: "{{ systemd_defaults.capabilities | default([]) }}" published_ports: "{{ item.publish | default([]) }}" env: "{{ item.env | default({}) }}" # Специальные настройки для Astra Linux и RedOS (для совместимости с amd64 базовыми образами) security_opts: "{{ ['seccomp=unconfined', 'apparmor=unconfined'] if item.family in ['astra', 'redos'] else [] }}" platform: "{{ 'linux/amd64' if item.family in ['astra', 'redos'] else omit }}" state: started restart_policy: unless-stopped loop: "{{ hosts | selectattr('type','undefined') | list }}" loop_control: { label: "{{ item.name }}" } when: item.family is defined and images[item.family] is defined # Ожидание стабилизации контейнеров - name: Wait for containers to be ready pause: seconds: 5 when: hosts | length > 0 # Создание tmp директории в контейнерах - name: Create Ansible tmp directory in containers community.docker.docker_container_exec: container: "{{ item.name }}" command: "mkdir -p /tmp/.ansible-tmp && chmod 755 /tmp/.ansible-tmp" loop: "{{ hosts | selectattr('type','undefined') | list }}" loop_control: { label: "{{ item.name }}" } when: item.family is defined and images[item.family] is defined ignore_errors: true retries: 3 delay: 2 # DinD nodes - name: Start DinD nodes (docker:27-dind) community.docker.docker_container: name: "{{ item.name }}" image: "docker:27-dind" networks: - name: "{{ docker_network }}" privileged: true env: DOCKER_TLS_CERTDIR: "" published_ports: "{{ item.publish | default([]) }}" volumes: "{{ (item.volumes | default([])) + [item.name + '-docker:/var/lib/docker'] }}" state: started restart_policy: unless-stopped loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}" loop_control: { label: "{{ item.name }}" } # DOoD nodes (mount docker.sock) - name: Start DOoD nodes (systemd + docker.sock mount) community.docker.docker_container: name: "{{ item.name }}" image: "{{ images[item.family] }}" networks: - name: "{{ docker_network }}" privileged: "{{ systemd_defaults.privileged }}" command: "{{ systemd_defaults.command }}" volumes: "{{ (systemd_defaults.volumes | default([])) + ['/var/run/docker.sock:/var/run/docker.sock'] + (item.volumes | default([])) }}" tmpfs: "{{ systemd_defaults.tmpfs | default([]) }}" capabilities: "{{ systemd_defaults.capabilities | default([]) }}" published_ports: "{{ item.publish | default([]) }}" env: "{{ item.env | default({}) }}" state: started restart_policy: unless-stopped loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}" loop_control: { label: "{{ item.name }}" } when: item.family is defined and images[item.family] is defined # Build groups map - name: Initialize groups map set_fact: groups_map: {} - name: Append hosts to groups set_fact: groups_map: "{{ groups_map | combine({ item_group: (groups_map[item_group] | default([])) + [item_name] }) }}" loop: "{{ hosts | subelements('groups', skip_missing=True) }}" loop_control: label: "{{ item.0.name }}" vars: item_name: "{{ item.0.name }}" item_group: "{{ item.1 }}" # Render inventory - name: Render inventory ini set_fact: inv_content: | [all:vars] ansible_connection=community.docker.docker ansible_remote_tmp=/tmp/.ansible-tmp {% for group, members in (groups_map | dictsort) %} [{{ group }}] {% for h in members %}{{ h }} {% endfor %} {% endfor %} [all] {% for h in hosts %}{{ h.name }} {% endfor %} {# Группа с Debian-based системами (Debian, Ubuntu, Alt) - используем /usr/bin/python3 #} {% set debian_hosts = [] %} {% for h in hosts %} {% if h.family in ['ubuntu', 'debian', 'alt'] %} {% set _ = debian_hosts.append(h.name) %} {% endif %} {% endfor %} {% if debian_hosts %} [debian_family:vars] ansible_python_interpreter=/usr/bin/python3 [debian_family] {% for h in debian_hosts %}{{ h }} {% endfor %} {% endif %} {# Группа с RHEL-based системами (RHEL, CentOS, Alma, Rocky, RedOS) #} {% set rhel_hosts = [] %} {% for h in hosts %} {% if h.family in ['rhel', 'centos', 'alma', 'rocky', 'redos'] %} {% set _ = rhel_hosts.append(h.name) %} {% endif %} {% endfor %} {% if rhel_hosts %} [rhel_family:vars] ansible_python_interpreter=/usr/bin/python3 [rhel_family] {% for h in rhel_hosts %}{{ h }} {% endfor %} {% endif %} {# Astra Linux - используем /usr/bin/python3 #} {% set astra_hosts = [] %} {% for h in hosts %} {% if h.family == 'astra' %} {% set _ = astra_hosts.append(h.name) %} {% endif %} {% endfor %} {% if astra_hosts %} [astra_family:vars] ansible_python_interpreter=/usr/bin/python3 [astra_family] {% for h in astra_hosts %}{{ h }} {% endfor %} {% endif %} {# Глобальный fallback для остальных хостов #} [unmatched_hosts:vars] ansible_python_interpreter=auto_silent - name: Write inventory file copy: dest: "{{ generated_inventory }}" content: "{{ inv_content }}" mode: "0644" - name: Display inventory summary debug: msg: | 📋 Inventory Summary: - Total hosts: {{ hosts | length }} - Groups: {{ groups_map.keys() | list | join(', ') }} - Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }} - DinD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }} - DOoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }} # ---------- Kind clusters (если определены) ---------- - name: Create kind cluster configs community.docker.docker_container_exec: container: ansible-controller command: > bash -lc ' mkdir -p /ansible/.kind; cat > /ansible/.kind/{{ item.name }}.yaml < 0 - name: Create kind clusters community.docker.docker_container_exec: container: ansible-controller command: > bash -lc ' set -e; for n in {{ (kind_clusters | default([]) | map(attribute="name") | list) | map('quote') | join(' ') }}; do if kind get clusters | grep -qx "$$n"; then echo "[kind] cluster $$n already exists"; else echo "[kind] creating $$n"; kind create cluster --name "$$n" --config "/ansible/.kind/$$n.yaml"; fi done ' when: (kind_clusters | default([])) | length > 0 - name: Install Ingress NGINX, Metrics Server, Istio, Kiali, Prometheus Stack (per cluster, if enabled) community.docker.docker_container_exec: container: ansible-controller command: > bash -lc ' set -e; helm repo add kiali https://kiali.org/helm-charts >/dev/null 2>&1 || true; helm repo add prometheus-community https://prometheus-community.github.io/helm-charts >/dev/null 2>&1 || true; helm repo update >/dev/null 2>&1 || true; for n in {{ (kind_clusters | default([]) | map(attribute="name") | list) | map('quote') | join(' ') }}; do # ingress-nginx if {{ (kind_clusters | items2dict(key_name="name", value_name="addons")).get(n, {}).get("ingress_nginx", False) | to_json }}; then echo "[addons] ingress-nginx on $$n"; kubectl --context kind-$$n apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml || true; kubectl --context kind-$$n -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=180s || true; fi # metrics-server if {{ (kind_clusters | items2dict(key_name="name", value_name="addons")).get(n, {}).get("metrics_server", False) | to_json }}; then echo "[addons] metrics-server on $$n"; kubectl --context kind-$$n apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml || true; kubectl --context kind-$$n -n kube-system patch deploy metrics-server -p \ "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"metrics-server\",\"args\":[\"--kubelet-insecure-tls\",\"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname\"]}]}}}}}" || true; fi # istio (demo profile) if {{ (kind_clusters | items2dict(key_name="name", value_name="addons")).get(n, {}).get("istio", False) | to_json }}; then echo "[addons] istio (demo profile) on $$n"; istioctl install -y --set profile=demo --context kind-$$n; kubectl --context kind-$$n -n istio-system rollout status deploy/istiod --timeout=180s || true; kubectl --context kind-$$n -n istio-system rollout status deploy/istio-ingressgateway --timeout=180s || true; fi # kiali (server chart, anonymous auth) — требует istio/metrics if {{ (kind_clusters | items2dict(key_name="name", value_name="addons")).get(n, {}).get("kiali", False) | to_json }}; then echo "[addons] kiali on $$n"; kubectl --context kind-$$n create ns istio-system >/dev/null 2>&1 || true; helm upgrade --install kiali-server kiali/kiali-server \ --namespace istio-system --kube-context kind-$$n \ --set auth.strategy=anonymous --wait --timeout 180s; fi # kube-prometheus-stack (Prometheus + Grafana) if {{ (kind_clusters | items2dict(key_name="name", value_name="addons")).get(n, {}).get("prometheus_stack", False) | to_json }}; then echo "[addons] kube-prometheus-stack on $$n"; kubectl --context kind-$$n create ns monitoring >/dev/null 2>&1 || true; helm upgrade --install monitoring prometheus-community/kube-prometheus-stack \ --namespace monitoring --kube-context kind-$$n \ --set grafana.adminPassword=admin \ --set grafana.defaultDashboardsTimezone=browser \ --wait --timeout 600s; # дождаться графаны kubectl --context kind-$$n -n monitoring rollout status deploy/monitoring-grafana --timeout=300s || true; fi done ' when: (kind_clusters | default([])) | length > 0 - name: Setup NodePort for addons community.docker.docker_container_exec: container: ansible-controller command: > bash -lc ' for n in {{ (kind_clusters | default([]) | map(attribute="name") | list) | map('quote') | join(' ') }}; do {% for cluster in kind_clusters | default([]) %} {% if cluster.addon_ports is defined %} if [ "$$n" = "{{ cluster.name }}" ]; then {% if cluster.addon_ports.prometheus is defined %} echo "[ports] Prometheus: {{ cluster.addon_ports.prometheus }}"; kubectl --context kind-{{ cluster.name }} patch svc -n monitoring monitoring-kube-prom-prometheus --type='json' -p='[{"op": "replace", "path": "/spec/type", "value":"NodePort"},{"op": "replace", "path": "/spec/ports/0/nodePort", "value":{{ cluster.addon_ports.prometheus }}}]' 2>/dev/null || true; {% endif %} {% if cluster.addon_ports.grafana is defined %} echo "[ports] Grafana: {{ cluster.addon_ports.grafana }}"; kubectl --context kind-{{ cluster.name }} patch svc -n monitoring monitoring-grafana --type='json' -p='[{"op": "replace", "path": "/spec/type", "value":"NodePort"},{"op": "replace", "path": "/spec/ports/0/nodePort", "value":{{ cluster.addon_ports.grafana }}}]' 2>/dev/null || true; {% endif %} {% if cluster.addon_ports.kiali is defined %} echo "[ports] Kiali: {{ cluster.addon_ports.kiali }}"; kubectl --context kind-{{ cluster.name }} patch svc -n istio-system kiali --type='json' -p='[{"op": "replace", "path": "/spec/type", "value":"NodePort"},{"op": "replace", "path": "/spec/ports/0/nodePort", "value":{{ cluster.addon_ports.kiali }}}]' 2>/dev/null || true; {% endif %} fi {% endif %} {% endfor %} done ' when: (kind_clusters | default([])) | length > 0