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:
91
Makefile
91
Makefile
@@ -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
193
README-UNIVERSAL-LAB.md
Normal 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]
|
||||
@@ -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
100
files/playbooks/site.yml
Normal 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
8
files/requirements.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
# Коллекции Ansible для универсальной лаборатории
|
||||
# Автор: Сергей Антропов
|
||||
# Сайт: https://devops.org.ru
|
||||
|
||||
collections:
|
||||
- name: community.docker
|
||||
- name: community.general
|
||||
46
molecule/presets/k8s-kind.yml
Normal file
46
molecule/presets/k8s-kind.yml
Normal 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"
|
||||
21
molecule/universal/converge.yml
Normal file
21
molecule/universal/converge.yml
Normal 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
|
||||
"
|
||||
209
molecule/universal/create.yml
Normal file
209
molecule/universal/create.yml
Normal 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
|
||||
50
molecule/universal/destroy.yml
Normal file
50
molecule/universal/destroy.yml
Normal 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
|
||||
57
molecule/universal/molecule.yml
Normal file
57
molecule/universal/molecule.yml
Normal 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
|
||||
90
molecule/universal/vars.yml
Normal file
90
molecule/universal/vars.yml
Normal 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"
|
||||
153
molecule/universal/verify.yml
Normal file
153
molecule/universal/verify.yml
Normal 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
3
roles/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
# Роли Ansible
|
||||
Разместите ваши роли Ansible в этом каталоге
|
||||
@@ -1 +1 @@
|
||||
password123
|
||||
test
|
||||
|
||||
Reference in New Issue
Block a user