33 KiB
Подробное руководство по файлам Molecule
Автор: Сергей Антропов
Сайт: https://devops.org.ru
📋 Обзор
Molecule - это инструмент для тестирования Ansible ролей в изолированных окружениях. В проекте AnsibleLab используется универсальная конфигурация Molecule с поддержкой множества операционных систем и различных preset'ов для тестирования.
🔧 Fallback значения
Важная особенность: Все файлы Molecule содержат fallback значения, что обеспечивает работоспособность системы даже без preset файлов. Это означает, что:
- Система всегда работает - даже если preset файл не найден
- Быстрый старт - можно запустить тестирование без настройки
- Надежность - меньше точек отказа в системе
- Отладка - легче диагностировать проблемы с preset'ами
🏗️ Структура файлов Molecule
molecule/
├── default/ # Основная конфигурация Molecule
│ ├── molecule.yml # Главный конфигурационный файл
│ ├── create.yml # Создание тестовых контейнеров
│ ├── converge.yml # Выполнение ролей в контейнерах
│ ├── verify.yml # Проверка результатов тестирования
│ ├── destroy.yml # Удаление тестовых контейнеров
│ └── site.yml # Основной playbook для тестирования
└── presets/ # Preset конфигурации для разных сценариев
├── minimal.yml # Минимальный preset (1 хост)
├── performance.yml # Performance preset (12 хостов)
├── security.yml # Security preset (10 хостов)
├── etcd-patroni.yml # etcd-patroni preset
└── ... # Другие preset'ы
📄 Детальное описание файлов
1. molecule/default/molecule.yml - Главный конфигурационный файл
Назначение: Основная конфигурация Molecule с настройками драйвера, платформ, provisioner'а и verifier'а.
Основные секции:
Driver (Драйвер):
driver:
name: docker
- Назначение: Определяет использование Docker в качестве драйвера
- Функция: Создание и управление тестовыми контейнерами
Platforms (Платформы):
platforms:
- name: placeholder
image: ghcr.io/ansible-community/molecule-ubuntu-systemd:jammy
pre_build_image: true
- name: ansible-controller
image: inecs/ansible-lab:ansible-controller-latest
pre_build_image: true
# ... другие образы
- Назначение: Определяет доступные Docker образы для тестирования
- Поддерживаемые ОС: Ubuntu, Debian, RHEL, CentOS, AlmaLinux, Rocky Linux, Alt Linux, Astra Linux, RedOS
- Собственные образы: AnsibleLab создает собственные образы для тестирования
Provisioner (Провижнер):
provisioner:
name: ansible
config_options:
defaults:
stdout_callback: yaml
env:
ANSIBLE_STDOUT_CALLBACK: yaml
inventory:
links:
hosts: "${MOLECULE_EPHEMERAL_DIRECTORY}/inventory/hosts.ini"
playbooks:
create: create.yml
converge: converge.yml
destroy: destroy.yml
- Назначение: Настройка Ansible как provisioner'а
- Функции:
- Настройка вывода в YAML формате
- Связывание инвентори файла
- Определение playbook'ов для разных этапов
Dependency (Зависимости):
dependency:
name: galaxy
- Назначение: Установка зависимостей через Ansible Galaxy
- Функция: Автоматическая установка коллекций из
requirements.yml
Verifier (Верификатор):
verifier:
name: ansible
- Назначение: Использование Ansible для проверки результатов
- Функция: Выполнение
verify.ymlдля проверки состояния системы
Lint (Линтер):
lint: |-
set -e
ansible-lint /workspace/roles/
- Назначение: Проверка синтаксиса ролей
- Функция: Запуск
ansible-lintдля всех ролей в директорииroles/
2. molecule/default/create.yml - Создание тестовых контейнеров
Назначение: Создание и настройка тестовых контейнеров согласно выбранному preset'у с fallback значениями.
Переменные и конфигурация:
Fallback значения (по умолчанию):
vars:
# Получаем preset из переменной окружения или используем default
preset_name: "{{ lookup('env', 'MOLECULE_PRESET') | default('default') }}"
preset_file: "/workspace/molecule/presets/{{ preset_name }}.yml"
# Fallback значения если preset файл не найден
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
images:
alt: "inecs/ansible-lab:alt9-latest"
astra: "inecs/ansible-lab:astra-linux-latest"
rhel: "inecs/ansible-lab:rhel-latest"
centos: "inecs/ansible-lab:centos-latest"
alma: "inecs/ansible-lab:alma-latest"
rocky: "inecs/ansible-lab:rocky-latest"
redos: "inecs/ansible-lab:redos-latest"
ubuntu: "inecs/ansible-lab:ubuntu-latest"
debian: "inecs/ansible-lab:debian-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
- name: u1
family: debian
groups: [test]
- Назначение: Определение fallback значений для случаев когда preset файл не найден
- Функция: Обеспечение работоспособности даже без preset файлов
- Образы: Собственные образы AnsibleLab для всех поддерживаемых ОС
- Systemd настройки: Стандартные настройки для systemd контейнеров
Основные задачи:
Load preset configuration:
- name: Load preset configuration
include_vars: "{{ preset_file }}"
when: preset_file is file
ignore_errors: true
- Назначение: Загрузка конфигурации preset'а (перезаписывает fallback значения)
- Функция: Динамическое определение тестового окружения
- Переменные:
MOLECULE_PRESETопределяет какой preset использовать - Fallback: Если preset файл не найден, используются значения по умолчанию
Преимущества fallback значений:
- Надежность: Система работает даже без preset файлов
- Быстрый старт: Можно запустить тестирование без настройки preset'ов
- Стандартизация: Единые настройки для всех ОС
- Отладка: Легче диагностировать проблемы с preset файлами
- Разработка: Удобно для разработки новых preset'ов
Ensure network exists:
- name: Ensure network exists
community.docker.docker_network:
name: "{{ docker_network }}"
state: present
- Назначение: Создание Docker сети для тестирования
- Функция: Обеспечение сетевого взаимодействия между контейнерами
- По умолчанию: Сеть
labnet
Pull systemd images:
- name: Pull systemd images
community.docker.docker_image:
name: "{{ images[item.family] }}"
source: pull
loop: "{{ hosts | selectattr('type','undefined') | list }}"
- Назначение: Загрузка Docker образов для systemd контейнеров
- Функция: Подготовка образов для создания контейнеров
- Поддержка: Различные семейства ОС (debian, rhel, alt, astra)
Start systemd nodes:
- 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([]) }}"
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
- Назначение: Создание и запуск systemd контейнеров
- Функции:
- Создание контейнеров с systemd поддержкой
- Настройка привилегированного режима
- Монтирование cgroup для systemd
- Настройка tmpfs для /run
- Публикация портов
- Настройка переменных окружения
Start DinD nodes (Docker-in-Docker):
- name: Start DinD nodes (docker:27-dind)
community.docker.docker_container:
name: "{{ item.name }}"
image: "docker:27-dind"
networks:
- name: "{{ docker_network }}"
privileged: true
env:
DOCKER_TLS_CERTDIR: ""
published_ports: "{{ item.publish | default([]) }}"
volumes: "{{ (item.volumes | default([])) + [item.name + '-docker:/var/lib/docker'] }}"
state: started
restart_policy: unless-stopped
- Назначение: Создание Docker-in-Docker контейнеров
- Функции:
- Тестирование Docker Compose
- Тестирование Dockerfile'ов
- Изолированная Docker среда
- Отдельные volumes для Docker данных
Start DOoD nodes (Docker-out-of-Docker):
- 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([]) }}"
capabilities: "{{ systemd_defaults.capabilities | default([]) }}"
published_ports: "{{ item.publish | default([]) }}"
env: "{{ item.env | default({}) }}"
state: started
restart_policy: unless-stopped
- Назначение: Создание Docker-out-of-Docker контейнеров
- Функции:
- Доступ к Docker daemon хоста
- Тестирование Docker операций
- Комбинация systemd + Docker
Build groups map:
- name: Initialize 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 | subelements('groups', skip_missing=True) }}"
- Назначение: Создание карты групп для инвентори
- Функция: Группировка хостов по назначению (servers, database, cache, etc.)
Render inventory ini:
- 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 %}
- Назначение: Генерация инвентори файла
- Функция: Создание динамического инвентори для Ansible
- Формат: INI формат с группами и переменными
Write inventory file:
- name: Write inventory file
copy:
dest: "{{ generated_inventory }}"
content: "{{ inv_content }}"
mode: "0644"
- Назначение: Сохранение инвентори файла
- Функция: Запись сгенерированного инвентори в файл
Display inventory summary:
- name: Display inventory summary
debug:
msg: |
📋 Inventory Summary:
- Total hosts: {{ hosts | length }}
- Groups: {{ groups_map.keys() | list | join(', ') }}
- Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }}
- DinD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
- DOoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
- Назначение: Отображение сводки по созданным контейнерам
- Функция: Информация о количестве и типах контейнеров
3. molecule/default/converge.yml - Выполнение ролей
Назначение: Выполнение Ansible ролей в созданных контейнерах.
Основные задачи:
Load preset configuration:
- name: Load preset configuration
include_vars: "{{ preset_file }}"
when: preset_file is file
ignore_errors: true
- Назначение: Загрузка конфигурации preset'а
- Функция: Применение настроек preset'а для выполнения
Preflight vault — normalize state:
- name: Preflight vault — normalize state (encrypt if plaintext, then decrypt)
community.docker.docker_container_exec:
container: ansible-controller
command: >
bash -lc '
set -euo pipefail; shopt -s nullglob globstar;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
for f in $p; do
[ -f "$f" ] || continue;
if head -n1 "$f" | grep -q "^\$ANSIBLE_VAULT;"; then
echo "[vault] already encrypted: $f";
else
echo "[vault] plaintext -> encrypt: $f";
ansible-vault encrypt --encrypt-vault-id default --vault-password-file /workspace/vault/.vault "$f";
fi
echo "[vault] decrypt for run: $f";
ansible-vault decrypt --vault-password-file /workspace/vault/.vault "$f";
done
done
'
- Назначение: Подготовка vault файлов для выполнения
- Функции:
- Шифрование незашифрованных файлов
- Расшифровка файлов для выполнения
- Нормализация состояния vault файлов
Run lab playbook:
- name: Run lab playbook
community.docker.docker_container_exec:
container: ansible-controller
command: >
bash -lc "
ANSIBLE_ROLES_PATH=/workspace/roles
ansible-playbook -i {{ lookup('env','MOLECULE_EPHEMERAL_DIRECTORY') }}/inventory/hosts.ini /workspace/molecule/default/site.yml
"
- Назначение: Выполнение основного playbook'а
- Функции:
- Запуск
site.ymlв ansible-controller контейнере - Использование сгенерированного инвентори
- Установка пути к ролям
- Запуск
Post-run — re-encrypt secrets:
- name: Post-run — re-encrypt secrets
community.docker.docker_container_exec:
container: ansible-controller
command: >
bash -lc '
set -euo pipefail; shopt -s nullglob globstar;
for p in {{ vault_targets | map('quote') | join(' ') }}; do
for f in $p; do
[ -f "$f" ] || continue;
if head -n1 "$f" | grep -q "^\$ANSIBLE_VAULT;"; then
echo "[vault] ok (encrypted): $f";
else
echo "[vault] encrypt back: $f";
ansible-vault encrypt --encrypt-vault-id default --vault-password-file /workspace/vault/.vault "$f" || true;
fi
done
done
'
ignore_errors: true
- Назначение: Повторное шифрование секретов после выполнения
- Функция: Обеспечение безопасности vault файлов
4. molecule/default/verify.yml - Проверка результатов
Назначение: Проверка состояния системы после выполнения ролей.
Основные задачи:
Load preset configuration:
- name: Load preset configuration
include_vars: "{{ preset_file }}"
when: preset_file is file
ignore_errors: true
- Назначение: Загрузка конфигурации preset'а
- Функция: Применение настроек для проверки
Check systemd nodes status:
- name: Check systemd nodes status
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: systemctl is-system-running
loop: "{{ hosts | selectattr('type','undefined') | list }}"
register: systemd_status
ignore_errors: true
- Назначение: Проверка состояния systemd в контейнерах
- Функция: Убедиться что systemd работает корректно
Check DinD nodes docker daemon:
- name: Check DinD nodes docker daemon
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: docker version --format '{{.Server.Version}}'
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
register: dind_status
ignore_errors: true
- Назначение: Проверка Docker daemon в DinD контейнерах
- Функция: Убедиться что Docker работает в контейнерах
Check DOoD nodes docker access:
- name: Check DOoD nodes docker access
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: docker ps --format '{{.Names}}'
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list }}"
register: dood_status
ignore_errors: true
- Назначение: Проверка доступа к Docker в DOoD контейнерах
- Функция: Убедиться что контейнеры могут обращаться к Docker daemon
Test network connectivity between nodes:
- name: Test network connectivity between nodes
community.docker.docker_container_exec:
container: "{{ item.0.name }}"
command: ping -c 1 {{ item.1.name }}
loop: "{{ hosts | subelements(hosts, 'name') }}"
when: item.0.name != item.1.name
register: ping_results
ignore_errors: true
- Назначение: Проверка сетевого взаимодействия между контейнерами
- Функция: Убедиться что контейнеры могут общаться друг с другом
Check published ports:
- name: Check published ports
community.docker.docker_container_exec:
container: "{{ item.name }}"
command: netstat -tlnp
loop: "{{ hosts | selectattr('publish','defined') | list }}"
register: port_status
ignore_errors: true
- Назначение: Проверка опубликованных портов
- Функция: Убедиться что порты доступны
Display verification summary:
- name: Display verification summary
debug:
msg: |
✅ Verification Summary:
- Total hosts: {{ hosts | length }}
- Systemd nodes: {{ hosts | selectattr('type','undefined') | list | length }}
- DinD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
- DOoD nodes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dood') | list | length }}
- Groups: {{ groups_map.keys() | list | join(', ') }}
- Network: {{ docker_network }}
- Назначение: Отображение итоговой сводки проверки
- Функция: Информация о состоянии всех компонентов
5. molecule/default/destroy.yml - Удаление контейнеров
Назначение: Очистка тестовых контейнеров и ресурсов.
Основные задачи:
Load preset configuration:
- name: Load preset configuration
include_vars: "{{ preset_file }}"
when: preset_file is file
ignore_errors: true
- Назначение: Загрузка конфигурации preset'а
- Функция: Определение какие ресурсы нужно удалить
Stop and remove containers:
- name: Stop and remove containers
community.docker.docker_container:
name: "{{ item.name }}"
state: absent
force_kill: true
loop: "{{ hosts }}"
ignore_errors: true
- Назначение: Остановка и удаление всех контейнеров
- Функция: Полная очистка тестовых контейнеров
Remove DinD volumes:
- name: Remove DinD volumes
community.docker.docker_volume:
name: "{{ item.name }}-docker"
state: absent
loop: "{{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list }}"
ignore_errors: true
- Назначение: Удаление volumes для DinD контейнеров
- Функция: Очистка Docker данных
Remove custom volumes:
- name: Remove custom volumes
community.docker.docker_volume:
name: "{{ item.volumes | default([]) | select('match', '^[^:]+$') | list }}"
state: absent
loop: "{{ hosts }}"
ignore_errors: true
when: item.volumes is defined
- Назначение: Удаление пользовательских volumes
- Функция: Очистка дополнительных volumes
Remove network:
- name: Remove network
community.docker.docker_network:
name: "{{ docker_network }}"
state: absent
ignore_errors: true
- Назначение: Удаление Docker сети
- Функция: Очистка сетевых ресурсов
Display cleanup summary:
- name: Display cleanup summary
debug:
msg: |
🧹 Cleanup Summary:
- Removed containers: {{ hosts | length }}
- Removed DinD volumes: {{ hosts | selectattr('type','defined') | selectattr('type','equalto','dind') | list | length }}
- Network: {{ docker_network }}
- Назначение: Отображение сводки по очистке
- Функция: Информация о удаленных ресурсах
6. molecule/default/site.yml - Основной playbook
Назначение: Основной playbook для тестирования ролей.
Структура:
Подготовка окружения:
- Обновление пакетов для всех ОС
- Установка common tools
- Настройка пользователей
- Создание рабочих директорий
Импорт deploy.yml:
- import_playbook: ../../roles/deploy.yml
- Назначение: Импорт playbook'а с ролями
- Функция: Разделение логики подготовки и выполнения ролей
🎯 Preset файлы
molecule/presets/minimal.yml - Минимальный preset
Назначение: Быстрое тестирование с одним хостом.
Характеристики:
- Количество хостов: 1
- ОС: Debian
- Группы: test
- Использование: Быстрая проверка ролей
molecule/presets/performance.yml - Performance preset
Назначение: Нагрузочное тестирование с множеством хостов.
Характеристики:
- Количество хостов: 12
- Серверы: 5 узлов (web, app)
- База данных: 3 узла
- Кэш: 3 узла Redis
- Load balancer: 1 узел HAProxy
- DinD: 1 узел для Docker Compose
Группы:
servers- веб-серверыdatabase- базы данныхcache- кэш серверыloadbalancer- балансировщик нагрузкиapps- приложения
molecule/presets/security.yml - Security preset
Назначение: Тестирование безопасности с изолированными сетями.
Характеристики:
- Количество хостов: 10
- Bastion хосты: 2 (точки входа)
- Внутренние серверы: 3
- База данных: 2 (изолированная сеть)
- Мониторинг: 2
- Firewall: 2
- DOoD: 1 для Docker безопасности
Группы:
bastion- точки входаinternal- внутренние серверыdatabase- базы данныхmonitoring- мониторингfirewall- сетевые компонентыsecurity- компоненты безопасности
🚀 Использование
Базовые команды:
# Тестирование с минимальным preset'ом
make role test minimal
# Тестирование с performance preset'ом
make role test performance
# Тестирование с security preset'ом
make role test security
# Тестирование конкретной роли
make role test minimal ping
# Тестирование без preset'а (используются fallback значения)
make role test
# Тестирование с несуществующим preset'ом (используются fallback значения)
MOLECULE_PRESET=nonexistent make role test
Продвинутое использование:
# Тестирование с кастомным preset'ом
MOLECULE_PRESET=custom make role test
# Отладка с подробным выводом
make role test minimal --verbose
# Проверка только создания контейнеров
molecule create -s default
# Проверка только выполнения ролей
molecule converge -s default
# Проверка только верификации
molecule verify -s default
# Очистка контейнеров
molecule destroy -s default
🔧 Настройка
Создание собственного preset'а:
# molecule/presets/custom.yml
---
docker_network: labnet
generated_inventory: "{{ molecule_ephemeral_directory }}/inventory/hosts.ini"
images:
ubuntu: "inecs/ansible-lab:ubuntu-latest"
rhel: "inecs/ansible-lab:rhel-latest"
systemd_defaults:
privileged: true
command: "/sbin/init"
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:ro"
tmpfs: ["/run", "/run/lock"]
capabilities: ["SYS_ADMIN"]
hosts:
- name: web1
family: ubuntu
groups: [web, servers]
publish: ["80:80"]
- name: db1
family: rhel
groups: [database, internal]
Использование fallback значений:
# Тестирование без preset'а (используются fallback значения из create.yml)
make role test
# Тестирование с несуществующим preset'ом (используются fallback значения)
MOLECULE_PRESET=nonexistent make role test
# Проверка fallback значений
molecule create -s default --debug
Использование кастомного preset'а:
MOLECULE_PRESET=custom make role test
🐛 Troubleshooting
Проблемы с контейнерами:
Ошибка: Контейнер не запускается Решение: Проверить доступность Docker образа и ресурсы системы
Ошибка: Systemd не работает
Решение: Убедиться что контейнер запущен с privileged: true и правильными volumes
Ошибка: Сетевое взаимодействие не работает Решение: Проверить создание Docker сети и настройки firewall
Проблемы с preset файлами:
Ошибка: Preset файл не найден
Решение: Система автоматически использует fallback значения из create.yml
Ошибка: Неправильная конфигурация preset'а Решение: Проверить синтаксис YAML и доступность образов в preset файле
Ошибка: Preset файл не загружается
Решение: Убедиться что файл находится в molecule/presets/ и имеет правильное имя
Проблемы с ролями:
Ошибка: Роль не найдена
Решение: Проверить путь к роли в roles/deploy.yml
Ошибка: Ошибка выполнения роли Решение: Проверить совместимость роли с ОС и зависимости
Проблемы с vault:
Ошибка: Не удается расшифровать vault файлы
Решение: Проверить наличие файла .vault и правильность пароля
Ошибка: Vault файлы остались расшифрованными Решение: Проверить права доступа и настройки vault
📊 Мониторинг
Логи выполнения:
# Просмотр логов Molecule
molecule test -s default --debug
# Логи конкретного этапа
molecule create -s default --debug
molecule converge -s default --debug
molecule verify -s default --debug
Проверка состояния:
# Статус контейнеров
docker ps
# Статус сети
docker network ls
# Статус volumes
docker volume ls
🎯 Лучшие практики
- Используйте подходящие preset'ы для разных типов тестирования
- Проверяйте совместимость ролей с различными ОС
- Используйте теги для разделения задач в ролях
- Документируйте зависимости ролей
- Тестируйте на разных preset'ах перед коммитом
- Используйте vault для секретных данных
- Очищайте ресурсы после тестирования
- Используйте fallback значения для быстрого старта без preset'ов
- Проверяйте доступность образов перед созданием preset'ов
- Документируйте кастомные preset'ы для команды
🔗 Связанные файлы
roles/deploy.yml- playbook с ролямиinventory/hosts.ini- статический инвенториvault/.vault- пароль для vaultrequirements.yml- зависимости AnsibleMakefile- команды для запуска тестов