SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Ansible: как игнорировать недоступные хосты

Ansible по-разному обрабатывает сбои задач и недоступные узлы. Используйте параметр ignore_unreachable, чтобы продолжить выполнение плейбука, если соединение с хостом прервано.

Недоступный хост не является сбойной задачей

Чтобы игнорировать недоступные хосты в Ansible, установите ignore_unreachable: true, и этот переключатель сработает. Важно понимать, когда его использовать, так как Ansible обрабатывает две разные проблемы двумя разными способами. Задача, которая была запущена на хосте и вернула ошибку, считается сбоем (failure). Хост, к которому Ansible вообще не смог подключиться, считается недоступным (unreachable). ignore_errors охватывает только первый случай. ignore_unreachable охватывает только второй.

Ниже показана разница в сводке выполнения плейбука.

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=0  changed=0  unreachable=1  failed=0  skipped=0  rescued=0  ignored=0

Ansible подключился к web1 и выполнил семь задач. web2 показывает unreachable=1 и failed=0, что означает, что на этом хосте вообще ничего не запускалось. Ansible не смог установить соединение, поэтому исключил хост из плейбука и продолжил работу с остальными. Если этот плейбук устанавливал обновление безопасности, то на одном из ваших серверов его нет.

Почему хост становится недоступным

Недоступность означает, что соединение прервалось до того, как какой-либо модуль смог обратиться к хосту. Вывода модуля для анализа нет, есть только ошибка соединения, которая возникает на первой же задаче, затрагивающей данную машину.

fatal: [web2]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.20 port 22: Connection refused", "unreachable": true}

Поле msg содержит истинную причину ошибки. Вот те, с которыми вы столкнетесь:

  • Connection refused: TCP-соединение было отклонено, значит, на этом порту никто не слушает. Служба sshd остановлена, либо SSH перенесен на другой порт, а в вашем инвентаре все еще указан 22.
  • Connection timed out: ответа нет совсем. Межсетевой экран отбрасывает пакеты или сервер выключен. Каждая попытка занимает время, равное таймауту соединения, который по умолчанию составляет 10 секунд.
  • Host key verification failed.: ключ в ~/.ssh/known_hosts не совпадает с ключом, который предоставил сервер. Переустановленный VPS сохраняет IP-адрес, но получает новый ключ хоста, поэтому такая ситуация ожидаема после переустановки и критична в любое другое время.
  • Permission denied (publickey): SSH ответил и отклонил ваш ключ. Порт в порядке, значит, проблема в аутентификации: обычно это неверный ansible_user или не загруженный ключ.
  • Timeout (12s) waiting for privilege escalation prompt: соединение установлено, а become — нет. sudo ожидает пароль, который не поступает.

Отсутствие интерпретатора Python — это причина, которую люди ожидают увидеть в списке, но она к нему не относится. SSH подключается, значит, хост доступен. Однако модулю негде выполняться:

fatal: [db1]: FAILED! => {"changed": false, "module_stdout": "/bin/sh: 1: /usr/bin/python3: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter", "rc": 127}

Эта строка сообщает FAILED!, а в итоговой сводке она учитывается в failed, поэтому ignore_unreachable никогда не дойдет до этой задачи. Установите ansible_python_interpreter для этого хоста или установите на нем python3.

Как игнорировать недоступные хосты в плейбуке

На уровне задачи этот параметр указывается рядом с модулем:

- name: Read the package list, and do not stop if the host is down
  ansible.builtin.command: dpkg -l
  register: packages
  changed_when: false
  ignore_unreachable: true

На уровне плейбука он задает значение по умолчанию для каждой задачи в плейбуке, при этом отдельная задача может переопределить его обратно:

- name: Opportunistic fleet maintenance
  hosts: all
  ignore_unreachable: true
  tasks:
    - name: This runs, cannot connect, and the play carries on
      ansible.builtin.ping:

    - name: This one still ends the play for a host that is down
      ansible.builtin.ping:
      ignore_unreachable: false

Важно понимать, что происходит на низком уровне. Если установлено ignore_unreachable, хост не удаляется из плейбука, поэтому каждая последующая задача снова пытается установить соединение и снова завершается с той же ошибкой. Каждая такая попытка ожидает истечения тайм-аута соединения — 10 секунд, если вы не изменили timeout в ansible.cfg. Плейбук из двадцати задач, запущенный против одного неработающего сервера, увеличит время выполнения примерно на 200 секунд и добавит двадцать красных строк в лог.

Поэтому выполните проверку один раз, а затем корректно исключите этот хост:

- name: Opportunistic fleet maintenance
  hosts: all
  gather_facts: false
  tasks:
    - name: Check that the host answers before doing any work
      ansible.builtin.ping:
      register: reachable
      ignore_unreachable: true

    - name: End the play for this host if it never answered
      ansible.builtin.meta: end_host
      when: reachable.unreachable | default(false)

    - name: Gather facts now that the connection is known good
      ansible.builtin.setup:

    - name: Refresh the package index
      ansible.builtin.apt:
        update_cache: true
      become: true

Это обеспечивает одну попытку соединения на неработающий хост вместо одной попытки на каждую задачу. Параметр end_host, добавленный в Ansible 2.8, завершает выполнение плейбука для текущего хоста, не помечая его как «failed». Ключ unreachable присутствует в зарегистрированном результате только в случае сбоя соединения, поэтому default(false) сохраняет условие валидным для всех хостов, которые ответили. Сбор фактов на уровне плейбука отключен, так как в противном случае неявная задача Gathering Facts стала бы той самой задачей, которая столкнется с разорванным соединением, а вам нужно, чтобы это была ваша собственная проверка ping.

ignore_unreachable является ключевым словом как для плейбука, так и для задачи. Размещайте его в плейбуке там, где его увидит читатель, а не внутри роли, так как он определяет, какие хосты могут отсутствовать при запуске. В разделе Разделение между плейбуками и ролями рассматривается, на каком уровне должны задаваться подобные настройки.

Почему ignore_errors — неподходящий инструмент в данном случае

Документация Ansible прямо указывает на это ограничение. ignore_errors "работает только тогда, когда задача может быть выполнена и возвращает значение 'failed'. Он не заставляет Ansible игнорировать ошибки неопределенных переменных, сбои соединения, проблемы выполнения (например, отсутствие пакетов) или синтаксические ошибки".

Сбой соединения никогда не становится результатом задачи с failed: true. Он поступает как отдельный флаг, и Ansible реагирует на этот флаг в первую очередь: хост попадает в список недоступных (unreachable) и исключается из выполнения play. Добавьте ignore_errors: true ко всем двенадцати задачам в play, и хост с закрытым SSH-портом всё равно остановится на первой же из них. Это наиболее распространенное заблуждение в данной области, и стоит проверить ваши старые плейбуки на его наличие, особенно те, что были написаны во время обучения написанию первого плейбука для VPS.

Отладка перед подавлением ошибок

Постоянное подавление ошибок приводит к деградации парка серверов, так как недоступный хост — это хост, который никто не обновляет. Сначала выполните следующие действия. Все приведенные команды работают только в режиме чтения.

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o запускает один модуль на одном хосте и выводит одну строку.
  2. Добавьте -vvvv к той же команде. Ansible выведет полную команду ssh, которую он формирует, включая целевого пользователя, порт, закрытый ключ и передаваемые параметры.
  3. Выполните эту команду ssh самостоятельно с помощью -v. Если обычный ssh не может подключиться, проблема находится на уровне ниже Ansible, и никакие параметры playbook её не исправят.
  4. Прочитайте строку msg и сопоставьте её со списком выше. Connection refused и Connection timed out указывают на разные проблемы: одна связана со службой SSH, другая — с сетевым маршрутом.
  5. Для Host key verification failed. проверьте данные, сохраненные в ssh-keygen -F web2.example.com. Если сервер был переустановлен, удалите старую запись с помощью ssh-keygen -R web2.example.com и примите новый ключ после сверки с консолью провайдера. Установка host_key_checking = False в ansible.cfg устраняет ошибку, но также отключает проверку, которая предупреждает о том, что по этому адресу теперь отвечает другая машина.
  6. Для Permission denied (publickey) подтвердите, какие параметры использует Ansible. ansible-inventory -i inventory.ini --host web2 выводит действующие переменные, включая ansible_user и ansible_port.
  7. Если SSH работает, а модули — нет, проверьте интерпретатор с помощью ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. Модуль raw выполняет команду через оболочку и не требует наличия python на целевой системе.

Только после этого игнорирование хоста станет осознанным решением, а не привычкой.

Сводка учитывает недоступные узлы отдельно, и CI обычно пропускает это

ansible-playbook завершается с кодом 0 при успехе, 2 — если хотя бы один узел не выполнил задачу, и 4 — если хотя бы один узел был недоступен. Эти значения являются битовыми флагами в исходном коде, поэтому запуск, при котором один узел выдал ошибку, а другой оказался недоступен, завершится с кодом 6. Команда ansible возвращает те же коды. Данные значения были проверены по исходному коду ansible-core в августе 2026 года.

Теперь установите ignore_unreachable: true и запустите тот же плейбук из семи задач для того же недоступного узла:

PLAY RECAP *********************************************************************
web1  : ok=7  changed=2  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0
web2  : ok=7  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=7

web2 сообщает о unreachable=0 и семи задачах ok, а выполнение завершается с кодом 0. Когда этот параметр установлен, Ansible увеличивает счетчики ok и ignored для данного узла вместо счетчика, который он называет dark (именно он заполняет столбец unreachable). Красные строки UNREACHABLE! по-прежнему выводятся, поэтому лог остается достоверным, в то время как сводка и код завершения — нет.

CI-задание, которое запускает плейбук и проверяет только $?, помечает такой запуск как успешный, и в его отчете нет информации о том, что к какому-то узлу не удалось подключиться. Вынесите проверку доступности в отдельный этап перед основным плейбуком:

ansible all -i inventory.ini -m ansible.builtin.ping -o

Эта команда выводит по одной строке на каждый узел и завершается с кодом 4, если какой-либо узел недоступен. Это позволяет пайплайну прерваться при ошибке и увидеть имена узлов в логе. ping требует работающего интерпретатора Python на целевой системе, поэтому такая проверка подтверждает немного больше, чем просто наличие сетевого соединения, что обычно и требуется. Затем запустите плейбук с параметром ignore_unreachable, чтобы доступные узлы всё равно получили необходимые изменения.

any_errors_fatal и max_fail_percentage в рамках пакета

Эти два ключевых слова определяют поведение системы при возникновении ошибок на части парка серверов, при этом они по-разному обрабатывают недоступные хосты.

any_errors_fatal: true реагирует на недоступный хост. Ansible завершает текущую задачу на остальных хостах пакета, а затем останавливает выполнение play для всех хостов в этом пакете. Используйте этот параметр, если выполнение имеет смысл только при условии «всё или ничего», например, при скоординированном изменении схемы базы данных.

max_fail_percentage: 30 не реагирует на недоступный хост. Проверка делит количество неудавшихся хостов на размер пакета, при этом недоступные хосты учитываются в отдельном списке и никогда не влияют на это число. Десять хостов, из которых четыре недоступны, продолжат выполнение при max_fail_percentage: 10, в то время как два хоста, на которых задача завершилась с ошибкой, остановят выполнение play. Документация указывает на еще одну ловушку: «Установленный процент должен быть превышен, а не достигнут». При использовании serial: 4, чтобы остановить выполнение после двух сбоев из четырех, необходимо указать 49, а не 50.

Существует один случай, когда недоступные хосты самостоятельно останавливают выполнение. Если каждый хост в пакете завершился с ошибкой или недоступен, у Ansible не остается объектов для работы, и он завершает play с NO MORE HOSTS LEFT.

serial: поэтапное развертывание изменений в парке серверов

- name: Rolling nginx config update
  hosts: webservers
  serial: 2
  max_fail_percentage: 25
  tasks:
    - name: Deploy the site config
      ansible.builtin.template:
        src: site.conf.j2
        dest: /etc/nginx/conf.d/site.conf
        owner: root
        mode: "0644"
      become: true
      notify: Reload nginx
  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded
      become: true

serial: 2 выполняет весь сценарий на двух хостах, дожидается завершения и переходит к следующим двум. serial: "25%" масштабируется в зависимости от размера группы. Список serial: [1, 5, 10] задает схему «канареечного» развертывания: сначала один хост, затем пять, затем десять, а все оставшиеся хосты обрабатываются партиями размером с последнее значение. max_fail_percentage измеряется для каждой партии, поэтому эти параметры работают совместно. Если первая машина выйдет из строя, выполнение остановится до того, как пострадают сорок остальных. Именно это делает управление парком серверов Linux с одной управляющей машины безопасным при запуске одной командой.

Когда игнорировать недоступные хосты, а когда нет

Игнорируйте их при выполнении вспомогательных задач. При сборе фактов или ежечасной проверке конфигурации пропуск недоступного хоста не приводит к потере данных, так как следующий запуск его обработает. В этом случае правильным решением будет использование уровня play ignore_unreachable: true в сочетании с шагом ping, чтобы информация о пропущенных именах попала туда, где её увидит администратор.

Никогда не игнорируйте их при установке обновлений безопасности. Ценность такой операции заключается в гарантии того, что исправление установлено на каждом хосте. Подавление ошибки недоступности превращает ситуацию «один сервер всё ещё уязвим» в ложный отчёт об успешном выполнении. Хост, который был недоступен две недели, вероятнее всего, сильнее всего отстал от актуальной версии. Позвольте задаче завершиться с кодом 4 и дайте человеку разобраться с проблемой.

В обоих случаях действует одно правило: подавляйте остановку выполнения, но никогда не подавляйте запись о событии. Если хост был пропущен, это должно быть отражено в итоговом отчёте, логах CI или оповещениях системы мониторинга. Ansible знает о существовании хоста только в те секунды, когда выполняется play, поэтому это плохой инструмент для отслеживания того, что сервер не работает со вторника. Эта задача относится к мониторингу, а Ansible playbook для установки Zabbix позволит получить обзор всего парка серверов за один день.

FAQ

В чем разница между ignore_errors и ignore_unreachable в Ansible?

ignore_errors: true применяется к задаче, которая была запущена на хосте и вернула ошибку, например, если команда завершилась с ненулевым кодом. ignore_unreachable: true применяется к хосту, к которому Ansible не смог подключиться, из-за чего ни один модуль не был выполнен. Они считывают разные поля в результате выполнения задачи, и ни один из них не покрывает случай другого. В документации Ansible указано, что ignore_errors «не заставляет Ansible игнорировать ошибки неопределенных переменных, сбои подключения, проблемы выполнения (например, отсутствие пакетов) или синтаксические ошибки», а закрытый SSH-порт является сбоем подключения.

Скрывает ли ignore_unreachable хост из итоговой сводки (play recap)?

По сути, да. Если этот параметр установлен, Ansible перестает учитывать такой хост в unreachable и засчитывает его как ok и ignored один раз на задачу, после чего выполнение завершается с кодом 0. Строки fatal: [host]: UNREACHABLE! по-прежнему выводятся, поэтому лог остается точным, даже если сводка и код выхода — нет. Следите за столбцом ignored или запускайте ansible all -m ansible.builtin.ping -o как отдельный шаг, чтобы недоступный хост все равно приводил к ненулевому коду выхода.

Какой код выхода возвращает ansible-playbook, если хост недоступен?

Он возвращает 4. Выполнение, в котором хотя бы один хост завершился с ошибкой, возвращает 2. Эти два значения являются битовыми флагами, поэтому выполнение, где есть и ошибка, и недоступный хост, возвращает 6. Успешное выполнение возвращает 0. Эти коды были проверены по исходному коду ansible-core в августе 2026 года. Установка ignore_unreachable: true убирает 4, поэтому конвейер (pipeline), проверяющий только код выхода, не увидит пропущенную машину.

Как пропустить оставшуюся часть play для хоста, который не ответил?

Сделайте первую задачу ansible.builtin.ping с параметрами ignore_unreachable: true и register: reachable, а затем добавьте ansible.builtin.meta: end_host с условием when: reachable.unreachable | default(false). end_host завершает play для этого хоста, не помечая его как failed. Установите gather_facts: false для play, чтобы именно ping стал задачей, которая обнаружит разорванное соединение. Без этого шаблона «мертвый» хост останется в play, и каждая последующая задача будет снова ждать истечения тайм-аута подключения.

Стоит ли игнорировать недоступные хосты при установке патчей безопасности?

Нет. Установка патчей важна, так как она дает гарантию, что обновление получили все хосты. Игнорирование недоступных хостов заменяет эту гарантию «зеленой» сводкой. Позвольте выполнению завершиться с кодом 4, прочитайте имена хостов, которые не ответили, и исправьте их. Подавление ошибок уместно только при регулярных фоновых запусках, где следующий проход захватит все, что было пропущено ранее.

#ansible#playbooks#error-handling#inventory#automation