- 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 '✗ Недоступен' }}
|
||
========================================
|