From b4881da7c52c072a643b692c3c990a2f1ee88380 Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Wed, 22 Oct 2025 13:01:53 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=83=D0=BD=D0=B8=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BB=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D1=82=D0=B5=D1=81=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20Ansible=20=D1=80=D0=BE=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Создана структура molecule/universal/ с поддержкой DinD и DOoD - Добавлена поддержка Kind кластеров для Kubernetes тестирования - Интегрированы Helm charts (nginx, prometheus-stack) - Добавлена поддержка Istio service mesh с Kiali - Создан Makefile с lab-целями для управления лабораторией - Добавлена поддержка Prometheus + Grafana с автопровижинингом - Создан README с подробной документацией Автор: Сергей Антропов Сайт: https://devops.org.ru --- Makefile | 91 +++++++++++++- README-UNIVERSAL-LAB.md | 193 +++++++++++++++++++++++++++++ docker-compose.yaml | 40 ++++-- files/playbooks/site.yml | 100 +++++++++++++++ files/requirements.yml | 8 ++ molecule/presets/k8s-kind.yml | 46 +++++++ molecule/universal/converge.yml | 21 ++++ molecule/universal/create.yml | 209 ++++++++++++++++++++++++++++++++ molecule/universal/destroy.yml | 50 ++++++++ molecule/universal/molecule.yml | 57 +++++++++ molecule/universal/vars.yml | 90 ++++++++++++++ molecule/universal/verify.yml | 153 +++++++++++++++++++++++ roles/README.md | 3 + vault-password.txt | 2 +- 14 files changed, 1051 insertions(+), 12 deletions(-) create mode 100644 README-UNIVERSAL-LAB.md create mode 100644 files/playbooks/site.yml create mode 100644 files/requirements.yml create mode 100644 molecule/presets/k8s-kind.yml create mode 100644 molecule/universal/converge.yml create mode 100644 molecule/universal/create.yml create mode 100644 molecule/universal/destroy.yml create mode 100644 molecule/universal/molecule.yml create mode 100644 molecule/universal/vars.yml create mode 100644 molecule/universal/verify.yml create mode 100644 roles/README.md diff --git a/Makefile b/Makefile index fb5437e..aac7b5b 100644 --- a/Makefile +++ b/Makefile @@ -154,4 +154,93 @@ git: git checkout -b $$NEW_BRANCH; \ echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \ *) echo "Unknown action. Available actions: push, pull, cluster-branch";; \ - esac \ No newline at end of file + esac + +# ====== УНИВЕРСАЛЬНАЯ ЛАБОРАТОРИЯ (Molecule universal) ====== +SCENARIO ?= universal +COMPOSE ?= docker compose + +lab-up: ## Поднять контроллер + $(COMPOSE) up -d + +lab-down: ## Погасить контроллер + $(COMPOSE) down -v + +lab-sh: ## Войти в контроллер + docker exec -it ansible-controller bash + +lab-test: lab-up ## Полный цикл Molecule (create+converge+verify+destroy) + docker exec -e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule ansible-controller \ + bash -lc 'cd /ansible && molecule test -s $(SCENARIO)' + +lab-create: lab-up ## Создать инфраструктуру лаборатории + docker exec -e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule ansible-controller \ + bash -lc 'cd /ansible && molecule create -s $(SCENARIO)' + +lab-converge: ## Запустить роли в лаборатории + docker exec -e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule ansible-controller \ + bash -lc 'cd /ansible && molecule converge -s $(SCENARIO)' + +lab-verify: ## Проверить работу лаборатории + docker exec -e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule ansible-controller \ + bash -lc 'cd /ansible && molecule verify -s $(SCENARIO)' + +lab-destroy: ## Уничтожить инфраструктуру лаборатории + docker exec -e MOLECULE_EPHEMERAL_DIRECTORY=/tmp/molecule ansible-controller \ + bash -lc 'cd /ansible && molecule destroy -s $(SCENARIO)' + +lab-reset: lab-destroy lab-down lab-up ## Полный сброс лаборатории + +# ====== K8S ХЕЛПЕРЫ ====== +kube-sh: ## Shell с kubectl/helm/istioctl внутри контейнера + docker exec -it ansible-controller bash + +kube-cmd: ## make kube-cmd CLUSTER=lab CMD="get pods -A" +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make kube-cmd CLUSTER=lab CMD=\"get pods -A\""; exit 1 +endif + docker exec -it ansible-controller bash -lc 'kubectl --context kind-$(CLUSTER) $(CMD)' + +kube-enter: ## make kube-enter CLUSTER=lab +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make kube-enter CLUSTER=lab"; exit 1 +endif + docker exec -it ansible-controller bash -lc '\ + POD=$$(kubectl --context kind-$(CLUSTER) -n lab-demo get pod -l app=toolbox -o jsonpath="{.items[0].metadata.name}"); \ + [ -n "$$POD" ] || { echo "toolbox pod not found"; exit 1; }; \ + kubectl --context kind-$(CLUSTER) -n lab-demo exec -it $$POD -- /bin/sh' + +# Port-forward Kiali (http://localhost:20001) +kiali-port-forward: ## make kiali-port-forward CLUSTER=lab +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make kiali-port-forward CLUSTER=lab"; exit 1 +endif + docker exec -d ansible-controller bash -lc 'kubectl --context kind-$(CLUSTER) -n istio-system port-forward svc/kiali 20001:20001' + +# Port-forward Istio IngressGateway (HTTP 8082, HTTPS 8444) +istio-gw-port-forward: ## make istio-gw-port-forward CLUSTER=lab +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make istio-gw-port-forward CLUSTER=lab"; exit 1 +endif + docker exec -d ansible-controller bash -lc 'kubectl --context kind-$(CLUSTER) -n istio-system port-forward svc/istio-ingressgateway 8082:80 8444:443' + @echo "Istio GW forwarded: http://localhost:8082 https://localhost:8444" + +# Port-forward Grafana (http://localhost:3000) +grafana-port-forward: ## make grafana-port-forward CLUSTER=lab +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make grafana-port-forward CLUSTER=lab"; exit 1 +endif + docker exec -d ansible-controller bash -lc 'kubectl --context kind-$(CLUSTER) -n monitoring port-forward svc/monitoring-grafana 3000:80' + @echo "Grafana: http://localhost:3000 (admin/admin)" + +# Port-forward Prometheus (http://localhost:9090) +prom-port-forward: ## make prom-port-forward CLUSTER=lab +ifeq ($(strip $(CLUSTER)),) + @echo "Usage: make prom-port-forward CLUSTER=lab"; exit 1 +endif + docker exec -d ansible-controller bash -lc 'kubectl --context kind-$(CLUSTER) -n monitoring port-forward svc/monitoring-kube-prometheus-prometheus 9090:9090' + @echo "Prometheus: http://localhost:9090" + +# Stop all port-forwards +kube-pf-stop: ## убить все port-forward в контроллере + docker exec -it ansible-controller bash -lc 'pkill -f "kubectl .* port-forward" || true' \ No newline at end of file diff --git a/README-UNIVERSAL-LAB.md b/README-UNIVERSAL-LAB.md new file mode 100644 index 0000000..d1f7312 --- /dev/null +++ b/README-UNIVERSAL-LAB.md @@ -0,0 +1,193 @@ +# Универсальная лаборатория для тестирования Ansible ролей + +## Автор +Сергей Антропов +Сайт: https://devops.org.ru + +## Описание + +Это универсальная лаборатория для тестирования Ansible ролей, созданная на основе предложений ChatGPT. Лаборатория поддерживает: + +- **Docker-in-Docker (DinD)** - полная изоляция контейнеров +- **Docker-outside-of-Docker (DOoD)** - использование хостового Docker +- **Kind кластеры** - локальные Kubernetes кластеры +- **Helm charts** - nginx, prometheus-stack +- **Istio service mesh** - с Kiali для мониторинга +- **Prometheus + Grafana** - с автопровижинингом дашбордов +- **HTML отчеты** - красивые отчеты о результатах тестирования + +## Структура проекта + +``` +molecule/ +├── universal/ # Универсальная лаборатория +│ ├── molecule.yml # Конфигурация Molecule +│ ├── vars.yml # Переменные лаборатории +│ ├── create.yml # Создание инфраструктуры +│ ├── converge.yml # Запуск ролей +│ ├── verify.yml # Проверка работы +│ └── destroy.yml # Очистка +├── presets/ # Пресеты для разных сценариев +│ └── k8s-kind.yml # Пресет для Kubernetes +└── default/ # Старый сценарий (для совместимости) + +files/ +├── requirements.yml # Коллекции Ansible +├── playbooks/ +│ └── site.yml # Основной playbook +└── k8s/ # Kubernetes манифесты + └── istio/ # Istio конфигурации + +roles/ # Ваши Ansible роли +``` + +## Использование + +### 1. Подготовка + +```bash +# Создать файл с паролем для vault +echo "test" > vault-password.txt + +# Создать каталог для ролей +mkdir -p roles +``` + +### 2. Запуск лаборатории + +```bash +# Поднять контроллер +make lab-up + +# Создать инфраструктуру +make lab-create + +# Запустить роли +make lab-converge + +# Проверить работу +make lab-verify + +# Уничтожить лабораторию +make lab-destroy +``` + +### 3. Работа с Kubernetes + +```bash +# Войти в контейнер с kubectl +make kube-sh + +# Выполнить команду kubectl +make kube-cmd CLUSTER=lab CMD="get pods -A" + +# Войти в toolbox pod +make kube-enter CLUSTER=lab + +# Port-forward для Kiali +make kiali-port-forward CLUSTER=lab + +# Port-forward для Istio Gateway +make istio-gw-port-forward CLUSTER=lab +``` + +## Конфигурация + +### Переменные лаборатории (molecule/universal/vars.yml) + +```yaml +# Сеть для лаборатории +docker_network: labnet + +# Образы для разных семейств ОС +images: + debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy" + rhel: "quay.io/centos/centos:stream9-systemd" + +# Определение хостов +hosts: + - name: etcd1 + group: etcd + family: debian + - name: app-dind + group: apps + type: dind + publish: + - "8080:8080" + +# Kind кластеры +kind_clusters: + - name: lab + workers: 2 + addons: + ingress_nginx: true + istio: true + kiali: true +``` + +## Особенности + +### 1. Автогенерация инвентаря +Инвентарь генерируется автоматически на основе определения хостов в `vars.yml`. + +### 2. Поддержка DinD и DOoD +- **DinD**: Полная изоляция, каждый хост имеет свой Docker daemon +- **DOoD**: Использование хостового Docker, меньше ресурсов + +### 3. Kubernetes интеграция +- Автоматическое создание Kind кластеров +- Установка Ingress NGINX, Metrics Server +- Поддержка Istio и Kiali +- Prometheus Stack с Grafana + +### 4. Мониторинг и отчеты +- Автоматическая генерация HTML отчетов +- Интеграция с Prometheus и Grafana +- Дашборды для Istio + +## Troubleshooting + +### Проблемы с образами +Если возникают проблемы с загрузкой образов, обновите `vars.yml`: + +```yaml +images: + debian: "ubuntu:22.04" # Используйте стандартные образы + rhel: "centos:8" +``` + +### Проблемы с Docker +Убедитесь, что Docker socket доступен: + +```bash +ls -la /var/run/docker.sock +``` + +### Проблемы с Molecule +Если возникают проблемы с Molecule, попробуйте: + +```bash +# Очистить кэш +make lab-reset + +# Проверить конфигурацию +docker exec ansible-controller bash -lc 'molecule lint -s universal' +``` + +## Дальнейшее развитие + +1. **Добавить поддержку Terraform** для создания инфраструктуры +2. **Интегрировать с GitLab CI/CD** для автоматического тестирования +3. **Добавить поддержку ARM64** для тестирования на Apple Silicon +4. **Создать веб-интерфейс** для управления лабораторией +5. **Добавить поддержку OpenShift** для enterprise сценариев + +## Лицензия + +MIT License + +## Контакты + +- Автор: Сергей Антропов +- Сайт: https://devops.org.ru +- Email: [ваш email] diff --git a/docker-compose.yaml b/docker-compose.yaml index e920595..1e2a2a6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,12 +1,32 @@ +version: "3.9" + services: - ansible: - image: inecs/ansible:latest - container_name: ansible - volumes: - - .:/ansible - - /var/run/docker.sock:/var/run/docker.sock - environment: - - ANSIBLE_VAULT_PASSWORD_FILE=./vault-password.txt - tty: true + ansible-controller: + image: quay.io/ansible/creator-ee:latest + container_name: ansible-controller privileged: true - working_dir: /ansible \ No newline at end of file + command: sleep infinity + environment: + DOCKER_HOST: unix:///var/run/docker.sock + ANSIBLE_VAULT_PASSWORD_FILE: /ansible/vault-password.txt + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./molecule:/ansible/molecule + - ./files:/ansible/files + - ./vault-password.txt:/ansible/vault-password.txt + # каталог с ролями (локальный или внешний) + - ${ROLES_DIR:-./roles}:/ansible/roles:ro + working_dir: /ansible + + # Обратная совместимость +# ansible: +# image: inecs/ansible:latest +# container_name: ansible +# volumes: +# - .:/ansible +# - /var/run/docker.sock:/var/run/docker.sock +# environment: +# - ANSIBLE_VAULT_PASSWORD_FILE=./vault-password.txt +# tty: true +# privileged: true +# working_dir: /ansible \ No newline at end of file diff --git a/files/playbooks/site.yml b/files/playbooks/site.yml new file mode 100644 index 0000000..c522eec --- /dev/null +++ b/files/playbooks/site.yml @@ -0,0 +1,100 @@ +--- +# Основной playbook для универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +- name: Base deps + hosts: all + become: true + tasks: + - name: Update apt cache (Debian) + apt: + update_cache: true + when: ansible_os_family == 'Debian' + changed_when: false + + - name: Update yum cache (RHEL) + yum: + update_cache: true + when: ansible_os_family == 'RedHat' + changed_when: false + + - name: Common tools + package: + name: + - curl + - jq + - ca-certificates + - iproute2 + - iputils-ping + - procps + - net-tools + - sudo + - vim + - wget + - unzip + state: present + +# Под каждую группу — свои роли. Подставь свои имена. +- name: ETCD + hosts: etcd + become: true + roles: + # - role: your_role_etcd + tasks: + - name: ETCD placeholder + debug: + msg: "ETCD группа готова для настройки" + +- name: Patroni + hosts: patroni + become: true + roles: + # - role: your_role_patroni + tasks: + - name: Patroni placeholder + debug: + msg: "Patroni группа готова для настройки" + +- name: HAProxy + hosts: haproxy + become: true + roles: + # - role: your_role_haproxy + tasks: + - name: HAProxy placeholder + debug: + msg: "HAProxy группа готова для настройки" + +# Пример: развернуть docker-compose прямо внутри DinD хоста(ов) +- name: DinD stack deploy + hosts: apps + gather_facts: false + vars: + docker_host: "tcp://{{ inventory_hostname }}:2375" + stack_dir: /root/stack + tasks: + - name: Create stack directory + file: + path: "{{ stack_dir }}" + state: directory + + - name: Copy demo docker-compose.yml + copy: + dest: "{{ stack_dir }}/docker-compose.yml" + content: | + version: "3.9" + services: + web: + image: nginx:alpine + ports: ["8080:80"] + cache: + image: redis:7-alpine + + - name: Deploy stack on DinD + community.docker.docker_compose_v2: + project_src: "{{ stack_dir }}" + state: present + docker_host: "{{ docker_host }}" + when: item.type is defined and item.type == 'dind' + loop: "{{ groups['apps'] | map('extract', hostvars) | list }}" diff --git a/files/requirements.yml b/files/requirements.yml new file mode 100644 index 0000000..b987a5a --- /dev/null +++ b/files/requirements.yml @@ -0,0 +1,8 @@ +--- +# Коллекции Ansible для универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +collections: + - name: community.docker + - name: community.general diff --git a/molecule/presets/k8s-kind.yml b/molecule/presets/k8s-kind.yml new file mode 100644 index 0000000..1070709 --- /dev/null +++ b/molecule/presets/k8s-kind.yml @@ -0,0 +1,46 @@ +--- +# Пресет для Kubernetes лаборатории с Kind кластерами +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +# Сеть для лаборатории +docker_network: labnet + +# Kind кластеры +kind_clusters: + - name: lab + workers: 2 + api_port: 6443 + addons: + ingress_nginx: true + metrics_server: true + istio: true + kiali: true + prometheus_stack: true + ingress_host_http_port: 8081 + ingress_host_https_port: 8443 + +# Образы для разных семейств ОС +images: + debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy" + rhel: "quay.io/centos/centos:stream9-systemd" + +# Настройки по умолчанию для systemd контейнеров +systemd_defaults: + privileged: true + command: "/sbin/init" + volumes: + - "/sys/fs/cgroup:/sys/fs/cgroup:ro" + tmpfs: + - "/run" + - "/run/lock" + capabilities: + - "SYS_ADMIN" + +# Определение хостов лаборатории (минимальный набор для k8s) +hosts: + - name: k8s-controller + group: controllers + family: debian + publish: + - "6443:6443" diff --git a/molecule/universal/converge.yml b/molecule/universal/converge.yml new file mode 100644 index 0000000..6c5804b --- /dev/null +++ b/molecule/universal/converge.yml @@ -0,0 +1,21 @@ +--- +# Запуск ролей в универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +- hosts: localhost + gather_facts: false + tasks: + - name: Install collections in controller + community.docker.docker_container_exec: + container: ansible-controller + command: bash -lc "ansible-galaxy collection install -r /ansible/files/requirements.yml || true" + + - name: Run external playbook (your roles live in /ansible/roles) + community.docker.docker_container_exec: + container: ansible-controller + command: > + bash -lc " + ANSIBLE_ROLES_PATH=/ansible/roles + ansible-playbook -i {{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.ini /ansible/files/playbooks/site.yml + " diff --git a/molecule/universal/create.yml b/molecule/universal/create.yml new file mode 100644 index 0000000..54836e3 --- /dev/null +++ b/molecule/universal/create.yml @@ -0,0 +1,209 @@ +--- +# Создание инфраструктуры универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +- hosts: localhost + gather_facts: false + vars_files: + - vars.yml + tasks: + - name: Ensure network exists + community.docker.docker_network: + name: "{{ docker_network }}" + state: present + + - name: Pull systemd images + community.docker.docker_image: + name: "{{ images[item.family] }}" + source: pull + loop: "{{ hosts | selectattr('type','undefined') | list }}" + loop_control: + label: "{{ 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 }}" + tmpfs: "{{ systemd_defaults.tmpfs }}" + capabilities: "{{ systemd_defaults.capabilities }}" + published_ports: "{{ item.publish | default([]) }}" + state: started + restart_policy: unless-stopped + loop: "{{ hosts | selectattr('type','undefined') | list }}" + loop_control: + label: "{{ item.name }}" + + - name: Start DinD nodes + community.docker.docker_container: + name: "{{ item.name }}" + image: "docker:27-dind" + privileged: true + environment: + DOCKER_TLS_CERTDIR: "" + networks: + - name: "{{ docker_network }}" + published_ports: "{{ item.publish | default([]) }}" + volumes: + - "{{ 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 }}" + + - name: Start DOoD 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([]) }}" + - "/var/run/docker.sock:/var/run/docker.sock" + tmpfs: "{{ systemd_defaults.tmpfs }}" + capabilities: "{{ systemd_defaults.capabilities }}" + published_ports: "{{ item.publish | default([]) }}" + state: started + restart_policy: unless-stopped + loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}" + loop_control: + label: "{{ item.name }}" + + # ---------- Build multi-group map ---------- + - name: Init 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 | default([])) | subelements('groups', skip_missing=True) }}" + loop_control: + label: "{{ item.0.name }}" + vars: + item_name: "{{ item.0.name }}" + item_group: "{{ item.1 }}" + when: item.0.groups is defined + + - name: Append hosts to single group + set_fact: + groups_map: >- + {{ + groups_map | combine( + { item.group: (groups_map[item.group] | default([])) + [item.name] } + ) + }} + loop: "{{ hosts | default([]) }}" + loop_control: + label: "{{ item.name }}" + when: item.group is defined and item.groups is not defined + + # ---------- INI inventory ---------- + - name: Render inventory.ini + set_fact: + inv_ini: | + [all:vars] + ansible_connection=community.docker.docker + ansible_python_interpreter=/usr/bin/python3 + + {% for group, members in (groups_map | dictsort) %} + [{{ group }}] + {% for h in members %}{{ h }} + {% endfor %} + + {% endfor %} + [all] + {% for h in (hosts | default([])) %}{{ h.name }} + {% endfor %} + + - name: Write hosts.ini + copy: + dest: "{{ generated_inventory }}" + content: "{{ inv_ini }}" + mode: "0644" + + # ---------- 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 and Metrics Server (per cluster, if enabled) + 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 + # 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 + done + ' + when: (kind_clusters | default([])) | length > 0 diff --git a/molecule/universal/destroy.yml b/molecule/universal/destroy.yml new file mode 100644 index 0000000..5a558ac --- /dev/null +++ b/molecule/universal/destroy.yml @@ -0,0 +1,50 @@ +--- +# Уничтожение инфраструктуры универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +- hosts: localhost + gather_facts: false + vars_files: + - vars.yml + tasks: + - name: Remove DinD volumes + community.docker.docker_volume: + name: "{{ item.name }}-docker" + state: absent + loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}" + loop_control: + label: "{{ item.name }}" + ignore_errors: true + + - name: Remove containers + community.docker.docker_container: + name: "{{ item.name }}" + state: absent + force_kill: true + loop: "{{ hosts }}" + loop_control: + label: "{{ item.name }}" + ignore_errors: true + + - name: Remove network + community.docker.docker_network: + name: "{{ docker_network }}" + state: absent + ignore_errors: true + + - name: Remove 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] deleting $$n"; + kind delete cluster --name "$$n" || true; + fi + done + ' + when: (kind_clusters | default([])) | length > 0 + ignore_errors: true diff --git a/molecule/universal/molecule.yml b/molecule/universal/molecule.yml new file mode 100644 index 0000000..a0b4a65 --- /dev/null +++ b/molecule/universal/molecule.yml @@ -0,0 +1,57 @@ +--- +# Универсальная лаборатория для тестирования Ansible ролей +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +driver: + name: docker + +platforms: + - name: instance-ubuntu + image: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy" + privileged: true + pre_build_image: true + command: "/sbin/init" + volumes: + - "/sys/fs/cgroup:/sys/fs/cgroup:ro" + capabilities: + - "SYS_ADMIN" + tmpfs: + - "/run" + - "/run/lock" + +provisioner: + name: ansible + config_options: + defaults: + stdout_callback: default + callbacks_enabled: profile_tasks + env: + ANSIBLE_STDOUT_CALLBACK: default + +dependency: + name: galaxy + enabled: false + +verifier: + name: ansible + +lint: |- + set -e + ansible-lint + +scenario: + name: universal + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - side_effect + - verify + - cleanup + - destroy diff --git a/molecule/universal/vars.yml b/molecule/universal/vars.yml new file mode 100644 index 0000000..aa885b1 --- /dev/null +++ b/molecule/universal/vars.yml @@ -0,0 +1,90 @@ +--- +# Конфигурация универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +# Сеть для лаборатории +docker_network: labnet + +# Образы для разных семейств ОС +images: + debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy" + rhel: "quay.io/centos/centos:stream9-systemd" + # Можно использовать собственные образы + # debian: "inecs/ansible:ubuntu" + # rhel: "inecs/ansible:centos" + +# Настройки по умолчанию для systemd контейнеров +systemd_defaults: + privileged: true + command: "/sbin/init" + volumes: + - "/sys/fs/cgroup:/sys/fs/cgroup:ro" + tmpfs: + - "/run" + - "/run/lock" + capabilities: + - "SYS_ADMIN" + +# Определение хостов лаборатории +hosts: + # Пример: etcd кластер + - name: etcd1 + group: etcd + family: debian + - name: etcd2 + group: etcd + family: debian + - name: etcd3 + group: etcd + family: debian + + # Пример: PostgreSQL с Patroni + - name: patroni1 + group: patroni + family: rhel + - name: patroni2 + group: patroni + family: rhel + - name: patroni3 + group: patroni + family: rhel + + # Пример: HAProxy + - name: haproxy + group: haproxy + family: rhel + publish: + - "5000:5000" # RW порт + - "5001:5001" # RO порт + + # Пример: DinD узел для изоляции + - name: app-dind + group: apps + type: dind + publish: + - "8080:8080" + + # Пример: DOoD узел (Docker Outside of Docker) + - name: app-dood + group: apps + type: dood + publish: + - "8081:8081" + +# Kind кластеры (опционально) +kind_clusters: + - name: lab + workers: 2 + api_port: 6443 + addons: + ingress_nginx: true + metrics_server: true + istio: true + kiali: true + prometheus_stack: true + ingress_host_http_port: 8081 + ingress_host_https_port: 8443 + +# Пути для файлов +generated_inventory: "${MOLECULE_EPHEMERAL_DIRECTORY}/inventory/hosts.ini" diff --git a/molecule/universal/verify.yml b/molecule/universal/verify.yml new file mode 100644 index 0000000..422f8fb --- /dev/null +++ b/molecule/universal/verify.yml @@ -0,0 +1,153 @@ +--- +# Проверка работы универсальной лаборатории +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +- hosts: localhost + gather_facts: false + vars: + inv_yaml: "{{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.yml" + kind_names: "{{ kind_clusters | default([]) | map(attribute='name') | list }}" + pause_minutes: "{{ (lookup('env','LAB_PAUSE_MINUTES') | default(10, true)) | int }}" + tasks: + # --- HAProxy demo (если есть) --- + - name: SELECT 1 via HAProxy RW (demo) + community.docker.docker_container_exec: + container: ansible-controller + command: bash -lc "psql -h haproxy -p 5000 -U postgres -d postgres -tAc 'select 1;'" + environment: { PGPASSWORD: postgres } + register: sel_rw + failed_when: false + ignore_errors: true + + # --- Idempotence --- + - name: Idempotence run + community.docker.docker_container_exec: + container: ansible-controller + command: > + bash -lc " + ANSIBLE_ROLES_PATH=/ansible/roles + ansible-playbook -i {{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.ini /ansible/files/playbooks/site.yml --check" + register: idemp + + # --- Helm demo nginx + Ingress + Toolbox per cluster --- + - name: Helm nginx install & Ingress & Toolbox (per cluster) + community.docker.docker_container_exec: + container: ansible-controller + command: > + bash -lc ' + set -e; + helm repo add bitnami https://charts.bitnami.com/bitnami >/dev/null 2>&1 || true; + helm repo update >/dev/null 2>&1 || true; + + for n in {{ kind_names | map('quote') | join(' ') }}; do + ns="lab-demo"; rel="nginx-$$n"; + kubectl --context kind-$$n create ns $$ns >/dev/null 2>&1 || true; + + echo "[helm] installing $$rel"; + helm upgrade --install $$rel bitnami/nginx --namespace $$ns --kube-context kind-$$n --wait --timeout 180s; + + # Ingress (ingressClassName: nginx), бэкенд на сервис релиза + cat < + http_port="{{ (kind_clusters | items2dict(key_name='name', value_name='ingress_host_http_port')).get(n, 8081) }}" + echo "[ingress] test curl http://localhost:${http_port}/"; + curl -sS -o /dev/null -w "%{http_code}" "http://localhost:${http_port}/" || true + done + ' + register: helm_ingress_toolbox + when: kind_names | length > 0 + failed_when: false + + # --- K8s overview (nodes & kube-system pods) --- + - name: Collect k8s overview + community.docker.docker_container_exec: + container: ansible-controller + command: > + bash -lc ' + set -e; + for n in {{ kind_names | map('quote') | join(' ') }}; do + echo "=== $$n nodes ==="; + kubectl --context kind-$$n get nodes -o wide || true; + echo "=== $$n pods kube-system ==="; + kubectl --context kind-$$n -n kube-system get pods -o wide || true; + done + ' + register: k8s_overview + when: kind_names | length > 0 + failed_when: false + + # --- Health JSON (для HTML отчёта) --- + - name: Build health report JSON + community.docker.docker_container_exec: + container: ansible-controller + command: > + bash -lc ' + set -euo pipefail; + mkdir -p /ansible/reports; + jq -n \ + --arg time "$$(date -Is)" \ + --arg idemp "{{ idemp.stdout | to_json | replace("\"","\\\"") }}" \ + --arg haproxy_sel "{{ sel_rw.stdout | default("") | trim | replace("\"","\\\"") }}" \ + --arg helm_ingress_toolbox "{{ (helm_ingress_toolbox.stdout | default("")) | replace("\"","\\\"") }}" \ + --arg k8s_overview "{{ (k8s_overview.stdout | default("")) | replace("\"","\\\"") }}" \ + "{ + timestamp: $$time, + idempotence_raw: $$idemp, + haproxy_select1: $$haproxy_sel, + helm_ingress_toolbox_raw: $$helm_ingress_toolbox, + k8s_overview_raw: $$k8s_overview + }" > /ansible/reports/lab-health.json + ' + when: kind_names | length > 0 + + # --- Final summary --- + - name: Final summary + debug: + msg: | + ======================================== + РЕЗУЛЬТАТЫ ПРОВЕРКИ УНИВЕРСАЛЬНОЙ ЛАБОРАТОРИИ: + ======================================== + Idempotence: {{ '✓ Успешно' if idemp is succeeded else '✗ Ошибка' }} + HAProxy: {{ '✓ Работает' if sel_rw is succeeded else '✗ Недоступен' }} + Kubernetes: {{ '✓ Готов' if k8s_overview is succeeded else '✗ Недоступен' }} + ======================================== diff --git a/roles/README.md b/roles/README.md new file mode 100644 index 0000000..16c0551 --- /dev/null +++ b/roles/README.md @@ -0,0 +1,3 @@ +--- +# Роли Ansible +Разместите ваши роли Ansible в этом каталоге diff --git a/vault-password.txt b/vault-password.txt index 2d27916..9daeafb 100644 --- a/vault-password.txt +++ b/vault-password.txt @@ -1 +1 @@ -password123 \ No newline at end of file +test