Одна пьеса на десять серверов быстро превращается в копипаст: у каждого хоста свои пути, порты и версии пакетов. Ansible предлагает роли — переиспользуемые блоки задач с переменными, шаблонами и обработчиками. Освоив роли и приоритеты переменных, вы перестаёте копировать YAML и начинаете собирать инфраструктуру из понятных деталей.
Анатомия роли
Роль — это каталог с предсказуемой структурой. Задачи лежат в tasks/main.yml, значения по умолчанию в defaults/main.yml, обязательные переменные в vars/main.yml, шаблоны в templates, файлы в files, обработчики в handlers. Метаданные и зависимости описывает meta/main.yml.
Такое разделение не формальность. defaults удобно переопределять в инвентаре, а vars внутри роли имеют высокий приоритет и защищают критичные значения от случайной замены. Шаблоны и файлы ищутся относительно роли, поэтому роли можно переносить между проектами без правки путей.
Создание скелета
Не пишите структуру руками: генератор создаст каталоги и заготовки. Дальше остаётся наполнить задачи и убрать лишние файлы.
ansible-galaxy init roles/nginx
tree roles/nginx
ansible-galaxy role list
ansible-galaxy collection list
Имя роли лучше держать в нижнем регистре через подчёркивания: дефисы допустимы, но ломают часть инструментов. В репозитории храните роли отдельно от пьес, а версию фиксируйте тегом. Если роль используется в нескольких проектах, вынесите её в собственный репозиторий и подключайте через requirements.yml.
defaults, vars и приоритеты
Главная путаница в Ansible — откуда взялось значение переменной. Приоритетов много, но запомнить нужно несколько уровней. Ниже — от слабого к сильному в упрощённом виде.
- defaults роли — самые слабые, предназначены для переопределения.
- group_vars и host_vars из инвентаря.
- vars роли и vars пьесы.
- Параметры задачи, include_role и extra-vars.
extra-vars побеждает почти всё, поэтому используйте его для разовых запусков, а не для постоянной конфигурации. Значения из vars роли не переопределяются инвентарём — это частая причина «почему мой порт не применился». Всё, что должно настраиваться снаружи, держите в defaults.
ansible-inventory --host web1
ansible web1 -m ansible.builtin.debug -a "var=nginx_port"
ansible-playbook site.yml -e nginx_port=8080 --check
Команда ansible-inventory показывает итоговые переменные хоста с учётом групп. Это быстрее, чем искать по десяти файлам. Если значение неожиданное, проверьте, не задано ли оно в нескольких group_vars одновременно.
group_vars и host_vars
Общие настройки кладите в group_vars/all.yml, различия среды — в group_vars/production.yml, особенности машины — в host_vars/web1.yml. Инвентарь при этом остаётся списком хостов, а не свалкой параметров.
inventory/
production.ini
group_vars/all.yml
group_vars/production.yml
host_vars/web1.yml
Для секретов используйте ansible-vault, а не открытые YAML. Зашифрованный файл можно хранить в репозитории, ключ — в менеджере секретов или переменной окружения. Не коммитьте пароль в group_vars только потому, что «репозиторий приватный»: доступ к нему получают не только администраторы.
ansible-vault create group_vars/production/vault.yml
ansible-vault edit group_vars/production/vault.yml
ansible-playbook site.yml --ask-vault-pass
include_role и tasks_from
Роль не обязана быть монолитом. Разбейте её на файлы и вызывайте нужный набор задач через tasks_from. Это удобно для частичных операций: установить пакеты отдельно от настройки.
- name: Настроить nginx
ansible.builtin.include_role:
name: nginx
tasks_from: configure.yml
vars:
nginx_worker_processes: 4
include_role выполняется во время прогона и видит переменные, заданные перед вызовом. import_role подставляется статически и удобен, когда нужны теги и предсказуемый порядок. Не смешивайте оба подхода в одном месте без причины: разница проявляется в обработке условий и тегов.
Зависимости и requirements.yml
Зависимости роли описываются в meta/main.yml и устанавливаются до её задач. Внешние роли и коллекции фиксируйте по версиям, иначе завтрашний прогон принесёт другой код.
ansible-galaxy role install -r requirements.yml -p roles
ansible-galaxy collection install -r requirements.yml -p collections
ansible-galaxy role install geerlingguy.nginx -p roles
Файл requirements.yml храните рядом с пьесой. Для коллекций указывайте source и version, для ролей — name и version. Обновление зависимости — отдельный коммит с прогоном тестов, а не фоновая операция в CI. Каталог collections добавьте в .gitignore, если он собирается на месте.
Handlers и идемпотентность
Обработчик запускается только при изменении и один раз за прогон, даже если его уведомили несколько задач. Это экономит время и предотвращает лишние перезапуски.
- name: Шаблон конфигурации
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
mode: "0644"
notify: Reload nginx
- name: Reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Проверяйте конфигурацию до перезагрузки: добавьте задачу с command и changed_when: false, которая вызывает nginx -t. Иначе синтаксическая ошибка в шаблоне уронит сайт. Идемпотентность проверяется повторным прогоном: второй запуск не должен показывать changed у задач, которые не меняют систему.
Шаблоны и фильтры
Jinja2 закрывает большинство случаев без сложной логики. Значения по умолчанию задавайте через default, списки объединяйте через combine, а отсутствие переменной обрабатывайте явно.
{{ nginx_port | default(80) }}
{{ ansible_facts['distribution'] | lower }}
{{ nginx_upstreams | combine({'api': '10.0.0.5:8080'}, recursive=True) }}
{{ users | map(attribute='name') | join(', ') }}
Не злоупотребляйте условиями в шаблоне: если ветвлений больше двух, вынесите их в отдельные файлы или переменные. Проверяйте результат через —check —diff: diff покажет, что именно изменится в файле, и часто выявляет лишние пробелы и права.
Проверка и тесты
Минимальный набор — синтаксическая проверка, линтер и прогон в режиме проверки. Этого достаточно, чтобы поймать большинство опечаток до продакшена.
ansible-playbook site.yml --syntax-check
ansible-lint roles/nginx
ansible-playbook site.yml --check --diff --limit web1
ansible-playbook site.yml --list-tasks --tags nginx
Для сложных ролей добавьте Molecule с контейнером или виртуальной машиной. Тест должен проверять результат, а не только отсутствие ошибок: запущенный сервис, открытый порт, созданный файл. Прогон на чистом хосте выявляет скрытые зависимости от предыдущих запусков.
Чек-лист
- Настраиваемые значения в defaults, критичные — в vars.
- Секреты зашифрованы ansible-vault, ключ не в репозитории.
- Версии ролей и коллекций зафиксированы.
- Повторный прогон не даёт changed без причины.
- Конфигурация проверяется до reload сервиса.
- Есть —check, линтер и тест на чистом хосте.
Итог
Роли превращают набор задач в переиспользуемый компонент, а понятная иерархия переменных избавляет от сюрпризов при запуске. Начинайте с defaults, выносите различия сред в group_vars, фиксируйте версии зависимостей и проверяйте каждый прогон линтером. Тогда одна и та же роль будет одинаково предсказуемо работать на тестовом стенде и в продакшене.