- YAML inventory с мультигруппами в create.yml - Vault preflight проверки в converge.yml (шифрование/расшифровка) - Pre_tasks с include_vars для lab preset - Chaos Engineering playbook для тестирования отказоустойчивости - Idempotence проверки в verify.yml - Health Dashboard с JSON отчетом - Secrets Inspector скрипт для проверки безопасности - Common tools установка в site.yml Новые команды: - make chaos - запуск Chaos Engineering тестов - make check-secrets - проверка безопасности секретов - make idempotence - проверка идемпотентности Обновления в файлах: - molecule/universal/create.yml: добавлена генерация YAML inventory - molecule/universal/molecule.yml: обновлен для использования YAML inventory - molecule/universal/converge.yml: добавлены vault preflight проверки - molecule/universal/verify.yml: добавлены idempotence и health dashboard - files/playbooks/chaos.yml: новый Chaos Engineering playbook - files/playbooks/site.yml: добавлены common tools - scripts/secret_scan.sh: новый Secrets Inspector - Makefile: добавлены новые команды - README.md: обновлена документация Преимущества: - Мультигруппы в YAML inventory для сложных конфигураций - Автоматическая проверка и нормализация vault файлов - Тестирование отказоустойчивости через Chaos Engineering - Проверка идемпотентности для качества ролей - Health Dashboard для мониторинга состояния лаборатории - Secrets Inspector для безопасности - Установка common tools для всех хостов Автор: Сергей Антропов Сайт: https://devops.org.ru
		
			
				
	
	
		
			306 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
| ---
 | ||
| # Проверка работы универсальной лаборатории
 | ||
| # Автор: Сергей Антропов
 | ||
| # Сайт: 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.yml /ansible/files/playbooks/site.yml --check"
 | ||
|       register: idemp
 | ||
| 
 | ||
|     - name: Assert idempotence
 | ||
|       assert:
 | ||
|         that:
 | ||
|           - "'changed=0' in idemp.stdout"
 | ||
|         fail_msg: "Playbook is not idempotent: {{ idemp.stdout }}"
 | ||
| 
 | ||
|     # --- 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;
 | ||
|             # метка для автосайдкаров Istio — не мешает, если Istio отключен
 | ||
|             kubectl --context kind-$$n label ns $$ns istio-injection=enabled --overwrite >/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 <<EOF | kubectl --context kind-$$n -n $$ns apply -f -
 | ||
|             apiVersion: networking.k8s.io/v1
 | ||
|             kind: Ingress
 | ||
|             metadata:
 | ||
|               name: nginx
 | ||
|               annotations:
 | ||
|                 kubernetes.io/ingress.class: nginx
 | ||
|             spec:
 | ||
|               rules:
 | ||
|               - host: localhost
 | ||
|                 http:
 | ||
|                   paths:
 | ||
|                   - path: /
 | ||
|                     pathType: Prefix
 | ||
|                     backend:
 | ||
|                       service:
 | ||
|                         name: $$rel
 | ||
|                         port:
 | ||
|                           number: 80
 | ||
|             EOF
 | ||
| 
 | ||
|             # Toolbox — чтобы можно было "зайти в кластер"
 | ||
|             cat <<EOF | kubectl --context kind-$$n -n $$ns apply -f -
 | ||
|             apiVersion: apps/v1
 | ||
|             kind: Deployment
 | ||
|             metadata: { name: toolbox }
 | ||
|             spec:
 | ||
|               replicas: 1
 | ||
|               selector: { matchLabels: { app: toolbox } }
 | ||
|               template:
 | ||
|                 metadata: { labels: { app: toolbox } }
 | ||
|                 spec:
 | ||
|                   containers:
 | ||
|                     - name: sh
 | ||
|                       image: alpine:3
 | ||
|                       command: ["/bin/sh","-c","sleep 1000000"]
 | ||
|             EOF
 | ||
| 
 | ||
|             kubectl --context kind-$$n -n $$ns rollout status deploy/toolbox --timeout=90s || true
 | ||
| 
 | ||
|             # curl по Ingress с хоста: http://localhost:<mapped>
 | ||
|             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
 | ||
| 
 | ||
|     # --- Istio/Kiali overview (если включены) ---
 | ||
|     - name: Istio & Kiali status
 | ||
|       community.docker.docker_container_exec:
 | ||
|         container: ansible-controller
 | ||
|         command: >
 | ||
|           bash -lc '
 | ||
|           set -e;
 | ||
|           for n in {{ kind_names | map('quote') | join(' ') }}; do
 | ||
|             echo "=== $$n istio pods ===";
 | ||
|             kubectl --context kind-$$n -n istio-system get pods -o wide || true;
 | ||
|             echo "=== $$n services (istio-system) ===";
 | ||
|             kubectl --context kind-$$n -n istio-system get svc || true;
 | ||
|           done
 | ||
|           '
 | ||
|       register: istio_kiali
 | ||
|       when: kind_names | length > 0
 | ||
|       failed_when: false
 | ||
| 
 | ||
|     # === Istio Bookinfo demo (если включён Istio) ===
 | ||
|     - name: Deploy Istio Bookinfo + Gateway/Routes (per cluster)
 | ||
|       community.docker.docker_container_exec:
 | ||
|         container: ansible-controller
 | ||
|         command: >
 | ||
|           bash -lc '
 | ||
|           set -e;
 | ||
|           for n in {{ kind_names | map('quote') | join(' ') }}; do
 | ||
|             # проверим что istio есть (namespace и istiod)
 | ||
|             if ! kubectl --context kind-$$n get ns istio-system >/dev/null 2>&1; then
 | ||
|               echo "[bookinfo] skip $$n: istio not installed"; continue;
 | ||
|             fi
 | ||
| 
 | ||
|             kubectl --context kind-$$n create ns bookinfo >/dev/null 2>&1 || true;
 | ||
|             kubectl --context kind-$$n label ns bookinfo istio-injection=enabled --overwrite || true;
 | ||
| 
 | ||
|             # Bookinfo (официальные манифесты)
 | ||
|             kubectl --context kind-$$n -n bookinfo apply -f https://raw.githubusercontent.com/istio/istio/release-1.22/samples/bookinfo/platform/kube/bookinfo.yaml;
 | ||
| 
 | ||
|             # DestinationRules (подсети версий)
 | ||
|             kubectl --context kind-$$n -n bookinfo apply -f https://raw.githubusercontent.com/istio/istio/release-1.22/samples/bookinfo/networking/destination-rule-all.yaml;
 | ||
| 
 | ||
|             # Gateway + VirtualService (route 90% v1, 10% v2 для reviews)
 | ||
|             cat <<EOF | kubectl --context kind-$$n -n bookinfo apply -f -
 | ||
|             apiVersion: networking.istio.io/v1beta1
 | ||
|             kind: Gateway
 | ||
|             metadata: { name: bookinfo-gateway }
 | ||
|             spec:
 | ||
|               selector:
 | ||
|                 istio: ingressgateway
 | ||
|               servers:
 | ||
|               - port: { number: 80, name: http, protocol: HTTP }
 | ||
|                 hosts: ["*"]
 | ||
|             ---
 | ||
|             apiVersion: networking.istio.io/v1beta1
 | ||
|             kind: VirtualService
 | ||
|             metadata: { name: bookinfo }
 | ||
|             spec:
 | ||
|               hosts: ["*"]
 | ||
|               gateways: ["bookinfo-gateway"]
 | ||
|               http:
 | ||
|               - match:
 | ||
|                 - uri:
 | ||
|                     prefix: /productpage
 | ||
|                 - uri:
 | ||
|                     prefix: /static
 | ||
|                 - uri:
 | ||
|                     prefix: /login
 | ||
|                 - uri:
 | ||
|                     prefix: /logout
 | ||
|                 - uri:
 | ||
|                     prefix: /api/v1/products
 | ||
|                 route:
 | ||
|                 - destination:
 | ||
|                     host: productpage
 | ||
|                     port: { number: 9080 }
 | ||
|               - match:
 | ||
|                 - uri:
 | ||
|                     prefix: /reviews
 | ||
|                 route:
 | ||
|                 - destination:
 | ||
|                     host: reviews
 | ||
|                     subset: v1
 | ||
|                     port: { number: 9080 }
 | ||
|                   weight: 90
 | ||
|                 - destination:
 | ||
|                     host: reviews
 | ||
|                     subset: v2
 | ||
|                     port: { number: 9080 }
 | ||
|                   weight: 10
 | ||
|             EOF
 | ||
| 
 | ||
|             # Ждём доступности productpage/reviews
 | ||
|             kubectl --context kind-$$n -n bookinfo rollout status deploy/productpage-v1 --timeout=180s || true
 | ||
|             kubectl --context kind-$$n -n bookinfo rollout status deploy/reviews-v1 --timeout=180s || true
 | ||
|             kubectl --context kind-$$n -n bookinfo rollout status deploy/reviews-v2 --timeout=180s || true
 | ||
| 
 | ||
|             echo "[bookinfo] try curl through Istio IngressGateway (port-forward 8082 if needed)";
 | ||
|           done
 | ||
|           '
 | ||
|       register: istio_bookinfo
 | ||
|       when: kind_names | length > 0
 | ||
|       failed_when: false
 | ||
| 
 | ||
|     - name: Apply DestinationRule TrafficPolicy for bookinfo (after deploy)
 | ||
|       community.docker.docker_container_exec:
 | ||
|         container: ansible-controller
 | ||
|         command: >
 | ||
|           bash -lc '
 | ||
|           set -e;
 | ||
|           for n in {{ kind_names | map("quote") | join(" ") }}; do
 | ||
|             if kubectl --context kind-$$n get ns bookinfo >/dev/null 2>&1; then
 | ||
|               echo "[istio] traffic policies for bookinfo on $$n";
 | ||
|               # из общего файла — применятся только DR в namespace bookinfo
 | ||
|               kubectl --context kind-$$n -n bookinfo apply -f /ansible/files/k8s/istio/trafficpolicy.yaml || true;
 | ||
|             fi
 | ||
|           done
 | ||
|           '
 | ||
|       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 istio_kiali "{{ (istio_kiali.stdout | default("")) | replace("\"","\\\"") }}" \
 | ||
|             --arg istio_bookinfo "{{ (istio_bookinfo.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,
 | ||
|               istio_kiali_raw: $$istio_kiali,
 | ||
|               istio_bookinfo_raw: $$istio_bookinfo,
 | ||
|               k8s_overview_raw: $$k8s_overview
 | ||
|             }" > /ansible/reports/lab-health.json
 | ||
|           '
 | ||
|       when: kind_names | length > 0
 | ||
| 
 | ||
|     # --- Health Dashboard ---
 | ||
|     - name: Generate health report
 | ||
|       community.docker.docker_container_exec:
 | ||
|         container: ansible-controller
 | ||
|         command: >
 | ||
|           bash -lc '
 | ||
|           mkdir -p /ansible/reports;
 | ||
|           echo "{
 | ||
|             \"timestamp\": \"$(date -Iseconds)\",
 | ||
|             \"lab_status\": \"healthy\",
 | ||
|             \"containers\": [
 | ||
|               $(docker ps --format "{\"name\": \"{{.Names}}\", \"status\": \"{{.Status}}\", \"ports\": \"{{.Ports}}\"}" | tr "\n" "," | sed "s/,$//")
 | ||
|             ],
 | ||
|             \"services\": [
 | ||
|               $(systemctl list-units --type=service --state=active --format=json | jq -r ".[] | select(.unit | startswith(\"postgresql\")) | {\"name\": .unit, \"status\": .sub}" | tr "\n" "," | sed "s/,$//")
 | ||
|             ],
 | ||
|             \"idempotence\": {{ "true" if "'changed=0'" in idemp.stdout else "false" }},
 | ||
|             \"vault_status\": "encrypted"
 | ||
|           }" > /ansible/reports/lab-health.json
 | ||
|           '
 | ||
| 
 | ||
|     # --- 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 '✗ Недоступен' }}
 | ||
|           ========================================
 |