delegate_to и run_once в Ansible: где выполняется задача и сколько раз
Playbook обновляет несколько серверов и в конце должен один раз обновить общий реестр. Задачу делегируют на localhost. Она выполняется для каждого сервера. После добавления run_once повторов становится меньше, но при serial они всё равно остаются. Здесь участвуют три независимых решения: за какой host идёт задача, где выполняется модуль и сколько раз её выбирает стратегия.
Delegate_to меняет узел выполнения поддерживающей делегирование задачи. Он не убирает её из цикла по inventory hosts. Если play содержит десять узлов, команда, делегированная на localhost, обычно будет инициирована десять раз — по одному разу для каждого исходного host.
Проверим пакетное выполнение без изменений
Пусть inventory содержит группу web из четырёх тестовых узлов. Следующий play запускает только команду чтения имени на controller и печатает результат:
- name: Observe delegation by batch
hosts: web
gather_facts: false
serial: 2
tasks:
- name: Read controller hostname once per batch
ansible.builtin.command:
argv: [/usr/bin/hostname]
delegate_to: localhost
run_once: true
changed_when: false
register: controller_name
- name: Show original host and delegated result
ansible.builtin.debug:
msg: "host={{ inventory_hostname }} controller={{ controller_name.stdout }}"
При обычной linear-стратегии четыре host разделены на два пакета. Команда hostname выполняется на localhost один раз в каждом пакете, то есть дважды за play. Debug относится к каждому исходному host; зарегистрированный результат run_once распространяется на активные hosts пакета.
Число два здесь следует из serial:2 и четырёх hosts, а не из свойств hostname. Если убрать serial, run_once обычно относится к единственному пакету всего play. Если изменить стратегию выполнения, проверьте поддержку run_once отдельно; не переносите ожидания linear автоматически на free.
Разберём контекст переменных
inventory_hostname продолжает обозначать исходный host. Для сведений о нём удобно явно использовать hostvars[inventory_hostname]. Параметры подключения и часть контекста делегированной задачи определяются узлом назначения. Из-за этого неявные ссылки на переменные могут неожиданно брать сведения controller вместо web-сервера.
Если задача собирает facts на делегированном узле, отдельно определите, кому их присваивать через delegate_facts. Место выполнения модуля и место хранения результата — разные вопросы. Обычный register также не означает «создать одну глобальную переменную независимо от hosts».
Выберем область однократного действия
Если общий реестр обновляется после каждого пакета, run_once при serial как раз соответствует задаче. Если действие должно выполняться один раз после завершения всей группы, понятнее выделить отдельный play на localhost после серверного play. Тогда граница явно видна и не зависит от размера пакета.
Другой подход — условие по первому host из ansible_play_hosts_all. Оно может ограничить выполнение одной выбранной идентичностью, но требует решения на случай отказа или исключения этого host. Для критического общего действия отдельный этап с явными условиями готовности часто проще проверить.
Делегирование не устраняет конкуренцию
Несколько hosts могут одновременно делегировать запись в один файл или API. Перенос операции на localhost не делает её последовательной. Если действие должно произойти для каждого host, но без конкурирующей записи, используйте подходящий throttle, serial либо один агрегирующий цикл. Выбор зависит от того, нужна ли одна операция на группу или отдельная для каждой машины.
Наконец, run_once не гарантирует «ровно один раз навсегда». Повторный запуск playbook снова выполнит задачу. Прерванное выполнение после внешнего действия, но до фиксации результата тоже может привести к повтору. Для регистрации релиза используйте стабильный идентификатор операции и идемпотентный интерфейс получателя.
Проверку начинайте с безвредной команды и четырёх тестовых hosts, как в примере. Зафиксируйте число выполнений при разных serial, затем подключайте реальное действие. Так количество вызовов и контекст переменных становятся заранее понятными, а общая операция не зависит от случайного числа серверов в пакете.
Ещё по теме
- Ansible Vault и no_log: где секреты остаются после расшифровки
- Ansible --tags пропускает нужные задачи: include_tasks и import_tasks
Продолжить изучение
Хотите собрать настройку серверов в повторяемый процесс? Inventory, роли, шаблоны и обновление сервисов входят в программу «Ansible: автоматизация инфраструктуры».
Продолжить на практике
- Ansible: автоматизация инфраструктуры ↗
Настраивайте серверы воспроизводимо: inventory, playbooks, роли, шаблоны и секреты. Соберите автоматизацию для нескольких окружений, проверьте повторный запуск и обновляйте сервисы управляемо.
