feat: Добавлена универсальная лаборатория для тестирования Ansible ролей

- Создана структура molecule/universal/ с поддержкой DinD и DOoD
- Добавлена поддержка Kind кластеров для Kubernetes тестирования
- Интегрированы Helm charts (nginx, prometheus-stack)
- Добавлена поддержка Istio service mesh с Kiali
- Создан Makefile с lab-целями для управления лабораторией
- Добавлена поддержка Prometheus + Grafana с автопровижинингом
- Создан README с подробной документацией

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-10-22 13:01:53 +03:00
parent deebf78047
commit b4881da7c5
14 changed files with 1051 additions and 12 deletions

View File

@@ -154,4 +154,93 @@ git:
git checkout -b $$NEW_BRANCH; \
echo "Создана и переключена на новую ветку: $$NEW_BRANCH";; \
*) echo "Unknown action. Available actions: push, pull, cluster-branch";; \
esac
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'

193
README-UNIVERSAL-LAB.md Normal file
View File

@@ -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]

View File

@@ -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
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

100
files/playbooks/site.yml Normal file
View File

@@ -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 }}"

8
files/requirements.yml Normal file
View File

@@ -0,0 +1,8 @@
---
# Коллекции Ansible для универсальной лаборатории
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
collections:
- name: community.docker
- name: community.general

View File

@@ -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"

View File

@@ -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
"

View File

@@ -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 <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
{% if (item.addons|default({})).ingress_nginx|default(false) %}
extraPortMappings:
- containerPort: 80
hostPort: {{ item.ingress_host_http_port | default(8081) }}
protocol: TCP
- containerPort: 443
hostPort: {{ item.ingress_host_https_port | default(8443) }}
protocol: TCP
{% endif %}
{% for i in range(item.workers | default(0)) %}
- role: worker
{% endfor %}
networking:
apiServerAddress: "0.0.0.0"
apiServerPort: {{ item.api_port | default(0) }}
EOF
'
loop: "{{ kind_clusters | default([]) }}"
when: (kind_clusters | default([])) | length > 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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

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

3
roles/README.md Normal file
View File

@@ -0,0 +1,3 @@
---
# Роли Ansible
Разместите ваши роли Ansible в этом каталоге

View File

@@ -1 +1 @@
password123
test