Обработчики, условия и циклы
Урок 10 - добавляем логику: перезапуск по событию, ветвления и циклы.
«Перезапускай nginx не на каждом прогоне, а только когда реально поменялся его конфиг».
Базовых задач хватает не всегда. Часто нужно: перезапустить сервис, но только если изменился его конфиг; выполнить задачу при условии; повторить задачу для списка элементов. Для этого есть handlers, when и loop.
Handlers и notify
Handler - особая задача, которая выполняется только если её «вызвали» через notify, и только если вызвавшая задача завершилась как changed. Классика - перезапуск сервиса при изменении конфига.
tasks:
- name: Развернуть конфиг nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Перезапустить nginx
handlers:
- name: Перезапустить nginx
ansible.builtin.service:
name: nginx
state: restarted
Если шаблон не изменился - задача ok, handler не запустится. Если изменился - changed, и в конце плея nginx перезапустится. Причём даже если уведомление пришло от пяти задач, handler выполнится один раз.
Условие when
- name: Поставить firewalld только на RedHat-системах
ansible.builtin.dnf:
name: firewalld
state: present
when: ansible_facts['os_family'] == "RedHat"
Выражение в when - это Jinja2 без двойных скобок. Если оно ложно - задача пропускается (skipping).
Циклы loop
- name: Создать несколько пользователей
ansible.builtin.user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- carol
Переменная item по очереди принимает каждое значение списка. Если совместить loop и when, условие проверяется отдельно для каждого элемента.
Как работает под капотом
Notify не запускает handler сразу - он ставит его в очередь. Очередь handler'ов выполняется в конце плея, причём каждый handler не более одного раза, в порядке их объявления. Это сделано осознанно: незачем перезапускать сервис посреди настройки - лучше один раз в конце, когда все конфиги на месте.
task A (changed) --notify--> [очередь: restart nginx]
task B (changed) --notify--> [очередь: restart nginx] (дубль убран)
task C (ok) ----------- ничего
|
конец плея -> выполнить очередь -> restart nginx (1 раз)
# Модель notify: собираем уникальные handler'ы, выполняем в конце плея
notified = [] # порядок объявления важен, дубли убираем
def notify(handler, changed):
if changed and handler not in notified:
notified.append(handler)
notify("restart nginx", changed=True) # task A
notify("restart nginx", changed=True) # task B -> дубль, не добавится
notify("reload sysctl", changed=False) # не changed -> пропуск
print("В конце плея выполнить:", notified) # ['restart nginx']
Попробуй сам ▶ Несмотря на два уведомления о рестарте nginx, в очереди он один. А reload sysctl не попал, потому что вызвавшая задача была ok, а не changed.
Частые ошибки
- Имя в notify не совпадает с name handler'а. Они связываются по точному совпадению строки - опечатка и handler молча не сработает.
- Двойные скобки в when. В
whenJinja-выражение пишется без{{ }}. - Ждать, что handler сработает посреди плея. Он выполняется в конце (или раньше, если явно вызвать
meta: flush_handlers).
Best practices
- Перезапуск сервисов - всегда через handler с notify, а не как обычную задачу: так избегаешь лишних рестартов.
- В
whenопирайся на факты (ansible_facts) - о них следующий раздел. - Для повторяющихся однотипных задач используй
loopвместо копипасты.
В реальной работе
Связка template + notify + handler - это типовой паттерн настройки почти любого сервиса: развернул конфиг из шаблона, и если он реально изменился - перезапустил сервис; не изменился - не трогаешь работающий процесс. Так избегают лишних рестартов, которые на проде означают кратковременные обрывы соединений. Циклы же часто комбинируют со словарями, а не только списками строк: loop по списку словарей позволяет одной задачей создать несколько пользователей, у каждого со своими группами и shell. А когда логика становится сложной, задачи группируют в блоки (block) с общим условием и обработкой ошибок через rescue - это уже структурное программирование внутри playbook.
Итоги
Handlers через notify дают перезапуск по событию (только при changed, один раз в конце плея), when добавляет условия, loop - повторение по списку. Это превращает простые playbook'и в гибкую логику. Дальше - переменные, факты и шаблоны.