feat: Добавлена система пресетов для Molecule

- Создана система пресетов для быстрого переключения между конфигурациями
- Добавлены пресеты: minimal, standard, docker, cluster
- Обновлена структура проекта с папками cicd/, vault/, scripts/
- Упрощена система vault с функциональными секретами
- Добавлены скрипты для работы с пресетами
- Обновлен Makefile с командами для пресетов
- Удалены старые файлы и структуры

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
2025-10-22 20:31:23 +03:00
parent deebf78047
commit 0b981ca61e
53 changed files with 1377 additions and 728 deletions

View File

@@ -0,0 +1,52 @@
---
- hosts: localhost
gather_facts: false
vars:
# перечисли файлы/глобы, которые нужно временно расшифровать
vault_targets:
- /ansible/vault/secrets.yml
# добавляй сюда свои пути (host_vars/*/vault.yml, group_vars/*/vault.yml, и т.п.)
tasks:
- name: Install required collections (use repo's requirements.yml)
community.docker.docker_container_exec:
container: ansible
command: bash -lc "ansible-galaxy collection install -r /ansible/requirements.yml || true"
- name: Decrypt vault targets (best-effort)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc '
set -euo pipefail;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
if [ -e "$p" ]; then
echo "[vault] decrypt $p";
ansible-vault decrypt --vault-password-file /ansible/vault-password.txt "$p" || true;
fi
done
'
- name: Run external playbook (your lab play)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc "
ANSIBLE_ROLES_PATH=/ansible/roles
ansible-playbook -i {{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.ini /ansible/molecule/universal/site.yml
"
- name: Re-encrypt vault targets (always)
community.docker.docker_container_exec:
container: ansible
command: >
bash -lc '
set -euo pipefail;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
if [ -e "$p" ]; then
echo "[vault] encrypt $p";
ansible-vault encrypt --encrypt-vault-id default --vault-password-file /ansible/vault-password.txt "$p" || true;
fi
done
'
ignore_errors: true

View File

@@ -0,0 +1,107 @@
---
- hosts: localhost
gather_facts: false
vars_files:
- hosts.yml
tasks:
- name: Ensure network exists
community.docker.docker_network:
name: "{{ docker_network }}"
state: present
# SYSTEMD nodes
- 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 | default([])) + (item.volumes | default([])) }}"
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
loop: "{{ hosts | selectattr('type','undefined') | list }}"
loop_control: { label: "{{ item.name }}" }
# DinD nodes
- name: Start DinD nodes (docker:27-dind)
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 }}" }
# DOoD nodes (mount docker.sock)
- name: Start DOoD nodes (systemd + docker.sock mount)
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'] + (item.volumes | default([])) }}"
tmpfs: "{{ (systemd_defaults.tmpfs | default([])) + (item.tmpfs | default([])) }}"
capabilities: "{{ (systemd_defaults.capabilities | default([])) + (item.capabilities | default([])) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
loop_control: { label: "{{ item.name }}" }
# Build groups map
- name: Build groups map {group: [hosts]}
set_fact:
groups_map: "{{ groups_map | default({}) }}"
- name: Append hosts to groups
set_fact:
groups_map: "{{ groups_map | combine({ item_group: (groups_map[item_group] | default([])) + [item_name] }) }}"
loop: "{{ hosts | subelements('groups', skip_missing=True) }}"
loop_control:
label: "{{ item.0.name }}"
vars:
item_name: "{{ item.0.name }}"
item_group: "{{ item.1 }}"
# Render inventory
- name: Render inventory ini
set_fact:
inv_content: |
[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 %}{{ h.name }}
{% endfor %}
- name: Write inventory file
copy:
dest: "{{ generated_inventory }}"
content: "{{ inv_content }}"
mode: "0644"

View File

@@ -0,0 +1,29 @@
---
- hosts: localhost
gather_facts: false
vars_files:
- hosts.yml
tasks:
- 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 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 network
community.docker.docker_network:
name: "{{ docker_network }}"
state: absent
ignore_errors: true

View File

@@ -0,0 +1,44 @@
---
# Пресет с Docker контейнерами
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
# systemd-ready образы
images:
debian: "ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy"
rhel: "quay.io/centos/centos:stream9-systemd"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
# Тестовые хосты
- name: test1
family: debian
groups: [test]
- name: test2
family: rhel
groups: [test]
# DinD узел (Docker-in-Docker)
- name: docker1
type: dind
groups: [docker]
publish: ["8080:8080"]
# DOoD узел (Docker-out-of-Docker)
- name: dood1
type: dood
family: debian
groups: [dood]
publish: ["8081:8081"]
env:
DOCKER_HOST: unix:///var/run/docker.sock

View File

@@ -0,0 +1,28 @@
---
# Универсальная конфигурация Molecule
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
driver:
name: delegated
provisioner:
name: ansible
config_options:
defaults:
stdout_callback: yaml
env:
ANSIBLE_STDOUT_CALLBACK: yaml
inventory:
links:
hosts: "${MOLECULE_EPHEMERAL_DIRECTORY}/inventory/hosts.ini"
dependency:
name: galaxy
verifier:
name: ansible
lint: |-
set -e
ansible-lint

View File

@@ -0,0 +1,95 @@
---
# Универсальный плейбук для тестирования
# Автор: Сергей Антропов
# Сайт: 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: Common tools
package:
name:
- curl
- jq
- ca-certificates
- iproute2
- iputils-ping
- procps
- net-tools
- sudo
- vim
state: present
# ===== ТЕСТОВЫЕ РОЛИ =====
- name: Deploy example role to test hosts
hosts: test
become: true
roles:
- example
vars:
example_package_name: "nginx"
example_directory: "/opt/example"
example_setting: "test"
example_port: 8080
- name: Deploy example role to docker hosts (DinD)
hosts: docker
become: true
roles:
- example
vars:
example_package_name: "docker"
example_directory: "/opt/docker-example"
example_setting: "dind"
example_port: 8080
- name: Deploy example role to dood hosts (DOoD)
hosts: dood
become: true
roles:
- example
vars:
example_package_name: "docker"
example_directory: "/opt/dood-example"
example_setting: "dood"
example_port: 8081
# ===== Пример: поднять compose внутри DinD-хостов =====
- name: Deploy stack inside DinD nodes
hosts: docker
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: Create simple docker-compose.yml
copy:
dest: "{{ stack_dir }}/docker-compose.yml"
content: |
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "8080:80"
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
- name: Deploy stack with docker-compose
community.docker.docker_compose_v2:
project_src: "{{ stack_dir }}"
state: present
docker_host: "{{ docker_host }}"

View File

@@ -0,0 +1,263 @@
---
# Универсальные проверки для тестового стенда
# Автор: Сергей Антропов
# Сайт: https://devops.org.ru
- name: Verify web servers
hosts: web
become: true
tasks:
- name: Check nginx service status
systemd:
name: nginx
register: nginx_status
- name: Verify nginx is running
assert:
that:
- nginx_status.status.ActiveState == "active"
- nginx_status.status.SubState == "running"
fail_msg: "nginx service is not running"
success_msg: "nginx service is running"
- name: Test nginx response
uri:
url: "http://{{ inventory_hostname }}"
method: GET
register: nginx_response
- name: Verify nginx response
assert:
that:
- nginx_response.status == 200
fail_msg: "nginx is not responding"
success_msg: "nginx is responding correctly"
- name: Verify app servers
hosts: app
become: true
tasks:
- name: Check Python installation
command: python3 --version
register: python_version
changed_when: false
- name: Verify Python is installed
assert:
that:
- python_version.rc == 0
fail_msg: "Python3 is not installed"
success_msg: "Python3 is installed: {{ python_version.stdout }}"
- name: Check app file exists
stat:
path: /opt/myapp/app.py
register: app_file
- name: Verify app file exists
assert:
that:
- app_file.stat.exists
fail_msg: "App file does not exist"
success_msg: "App file exists and is executable"
- name: Verify database servers
hosts: database
become: true
tasks:
- name: Check SQLite installation
command: sqlite3 --version
register: sqlite_version
changed_when: false
- name: Verify SQLite is installed
assert:
that:
- sqlite_version.rc == 0
fail_msg: "SQLite is not installed"
success_msg: "SQLite is installed: {{ sqlite_version.stdout }}"
- name: Check database file exists
stat:
path: /var/lib/mydb/sample.db
register: db_file
- name: Verify database file exists
assert:
that:
- db_file.stat.exists
fail_msg: "Database file does not exist"
success_msg: "Database file exists"
- name: Test database query
command: sqlite3 /var/lib/mydb/sample.db "SELECT COUNT(*) FROM users;"
register: db_query
changed_when: false
- name: Verify database query
assert:
that:
- db_query.rc == 0
- db_query.stdout | int > 0
fail_msg: "Database query failed"
success_msg: "Database query successful: {{ db_query.stdout }} users found"
- name: Verify cache servers
hosts: cache
become: true
tasks:
- name: Check Redis service status
systemd:
name: redis
register: redis_status
- name: Verify Redis is running
assert:
that:
- redis_status.status.ActiveState == "active"
- redis_status.status.SubState == "running"
fail_msg: "Redis service is not running"
success_msg: "Redis service is running"
- name: Test Redis connection
command: redis-cli ping
register: redis_ping
changed_when: false
- name: Verify Redis connection
assert:
that:
- redis_ping.rc == 0
- redis_ping.stdout == "PONG"
fail_msg: "Redis is not responding"
success_msg: "Redis is responding correctly"
- name: Verify load balancer
hosts: loadbalancer
become: true
tasks:
- name: Check HAProxy service status
systemd:
name: haproxy
register: haproxy_status
- name: Verify HAProxy is running
assert:
that:
- haproxy_status.status.ActiveState == "active"
- haproxy_status.status.SubState == "running"
fail_msg: "HAProxy service is not running"
success_msg: "HAProxy service is running"
- name: Check HAProxy configuration
stat:
path: /etc/haproxy/haproxy.cfg
register: haproxy_config
- name: Verify HAProxy configuration exists
assert:
that:
- haproxy_config.stat.exists
fail_msg: "HAProxy configuration does not exist"
success_msg: "HAProxy configuration exists"
- name: Verify monitoring
hosts: monitoring
become: true
tasks:
- name: Check monitoring tools
command: which htop
register: htop_check
changed_when: false
- name: Verify monitoring tools are installed
assert:
that:
- htop_check.rc == 0
fail_msg: "Monitoring tools are not installed"
success_msg: "Monitoring tools are installed"
- name: Check monitoring script
stat:
path: /usr/local/bin/system-info.sh
register: monitor_script
- name: Verify monitoring script exists
assert:
that:
- monitor_script.stat.exists
fail_msg: "Monitoring script does not exist"
success_msg: "Monitoring script exists"
- name: Test monitoring script
command: /usr/local/bin/system-info.sh
register: monitor_output
changed_when: false
- name: Verify monitoring script works
assert:
that:
- monitor_output.rc == 0
- monitor_output.stdout | length > 0
fail_msg: "Monitoring script failed"
success_msg: "Monitoring script works correctly"
- name: Network connectivity tests
hosts: all
tasks:
- name: Test connectivity to web servers
wait_for:
host: "{{ item }}"
port: 80
timeout: 10
loop:
- web1
- web2
when: "'web' not in group_names"
ignore_errors: true
- name: Test connectivity to app servers
wait_for:
host: "{{ item }}"
port: 8080
timeout: 10
loop:
- app1
when: "'app' not in group_names"
ignore_errors: true
- name: Test connectivity to cache servers
wait_for:
host: "{{ item }}"
port: 6379
timeout: 10
loop:
- cache1
when: "'cache' not in group_names"
ignore_errors: true
- name: Test connectivity to load balancer
wait_for:
host: lb1
port: 80
timeout: 10
when: "'loadbalancer' not in group_names"
ignore_errors: true
- name: Final verification summary
hosts: localhost
gather_facts: false
tasks:
- name: Display verification summary
debug:
msg: |
========================================
Verification Summary
========================================
- Web servers: {{ 'OK' if web_servers_ok is defined else 'SKIPPED' }}
- App servers: {{ 'OK' if app_servers_ok is defined else 'SKIPPED' }}
- Database servers: {{ 'OK' if database_servers_ok is defined else 'SKIPPED' }}
- Cache servers: {{ 'OK' if cache_servers_ok is defined else 'SKIPPED' }}
- Load balancer: {{ 'OK' if loadbalancer_ok is defined else 'SKIPPED' }}
- Monitoring: {{ 'OK' if monitoring_ok is defined else 'SKIPPED' }}
========================================