SSD Nodes Learn 🎉 VPS від $5.50/міс
Посібники Matt ConnorВід Matt Connor

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

Ansible розрізняє помилку задачі та недоступний хост. Дізнайтеся, як працюють ignore_unreachable, serial і max_fail_percentage та що буде пропущено.

Недоступний хост — це не невдала задача

Щоб ігнорувати недоступні хости в Ansible, задайте ignore_unreachable: true. Цей параметр працює. Важливо знати, коли його застосовувати, оскільки Ansible обробляє дві різні проблеми по-різному. Якщо задача виконалася на хості та повернула помилку, це failure. Якщо Ansible взагалі не зміг підключитися до хоста, це unreachable. ignore_errors охоплює лише перший випадок. ignore_unreachable охоплює лише другий випадок.

Ось відмінність у підсумку виконання play.

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 не зміг встановити з’єднання, тому вилучив хост із play і продовжив роботу з рештою. Якщо цей play встановлював оновлення безпеки, один із ваших серверів його не має.

Що означає, що хост недоступний

Недоступний означає, що з’єднання завершилося помилкою до того, як будь-який модуль досяг хоста. Виводу модуля немає. Доступна лише помилка з’єднання. Вона з’являється на першому завданні, яке звертається до цієї машини.

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 перенесено на інший порт, а в inventory досі вказано 22.
  • Connection timed out: відповіді немає взагалі. Firewall відкидає пакети, або сервер вимкнено. Кожна спроба триває весь час очікування з’єднання, який за замовчуванням становить 10 секунд.
  • Host key verification failed.: ключ у ~/.ssh/known_hosts не збігається з ключем, який надав сервер. Після перебудови VPS з тією самою IP-адресою сервер отримує новий host key. Після перевстановлення це очікувано, але в будь-який інший момент є серйозною проблемою.
  • 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.

Як ігнорувати недоступні хости у play

На рівні завдання keyword розташовується поруч із модулем:

- 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

На рівні play він задає значення за замовчуванням для кожного завдання в цьому play. Окреме завдання може скасувати це значення:

- 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, хост більше не вилучається з play, тому кожне наступне завдання знову намагається підключитися до нього і знову завершується так само. Кожна така спроба очікує завершення тайм-ауту підключення: 10 секунд, якщо не змінити timeout у ansible.cfg. Play із двадцятьма завданнями для одного непрацюючого сервера додає до виконання приблизно 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, завершує play для поточного хоста, не позначаючи його як failed. Ключ unreachable існує в зареєстрованому результаті лише тоді, коли підключення не вдалося, тому default(false) зберігає коректність умови для кожного хоста, який відповів. Збір фактів вимкнено на рівні play, оскільки інакше неявне завдання Gathering Facts стало б першим завданням, яке зіткнулося з розірваним підключенням, тоді як ви хочете, щоб цим завданням був власний ping.

ignore_unreachable — це keyword рівня play і завдання. Залишайте його в playbook, де читач може його побачити, а не всередині role, оскільки він визначає, які хости виконання може пропустити. Розподіл між playbook і role пояснює, який рівень має відповідати за такі налаштування.

Чому тут не слід використовувати ignore_errors

Документація Ansible прямо описує це обмеження. ignore_errors "працює лише тоді, коли завдання може виконатися та повертає значення 'failed'. Цей параметр не змушує Ansible ігнорувати помилки невизначених змінних, помилки підключення, проблеми під час виконання (наприклад, відсутні пакети) або синтаксичні помилки."

Помилка підключення ніколи не стає результатом завдання з failed: true. Вона надходить як окремий прапорець, і Ansible спочатку обробляє саме його: додає хост до списку недоступних і вилучає його з play. Додайте ignore_errors: true до всіх дванадцяти завдань play — хост із закритим SSH-портом усе одно зупиниться на першому завданні. Це найпоширеніша плутанина в цій темі. Перевірте її в старих playbook, особливо в тих, які ви писали, навчаючись створювати перший playbook для VPS.

Налагоджуйте, перш ніж придушувати помилку

Постійне придушення помилок призводить до розбіжностей у конфігурації fleet, оскільки хост, до якого ніхто не може підключитися, ніхто також не оновлює. Спочатку виконайте ці кроки в наведеному порядку. Кожна команда тут лише читає дані.

  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 виконує команду через shell і не потребує python на цільовому хості.

Лише після цього ігнорування хоста стає свідомим рішенням, а не звичкою.

Рекап окремо враховує недоступні хости, а CI зазвичай не виявляє цю проблему

ansible-playbook завершується з кодом 0 у разі успіху, з кодом 2, якщо не вдалося виконати завдання щонайменше на одному хості, і з кодом 4, якщо щонайменше один хост був недоступний. Ці два значення є бітовими прапорами у вихідному коді, тому запуск із хостом, на якому сталася помилка, і недоступним хостом завершується з кодом 6. Команда ansible повертає ті самі коди. Це перевірено за вихідним кодом ansible-core у серпні 2026 року.

Тепер задайте ignore_unreachable: true і запустіть той самий play із семи завдань для того самого недоступного хоста:

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, яке запускає playbook і перевіряє лише $?, вважає такий запуск успішним. У його підсумку ніщо не вказує, що до машини взагалі не було доступу. Зробіть перевірку доступності окремим кроком перед play:

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

Ця команда виводить по одному рядку для кожного хоста й завершується з кодом 4, якщо будь-який хост недоступний. Це дає pipeline підставу завершитися з помилкою та додає імена хостів до журналу. ping потребує робочого інтерпретатора Python на цільовому хості, тому перевіряє трохи більше, ніж лише з’єднання. Зазвичай це саме те, що потрібно. Потім запустіть playbook із ignore_unreachable, щоб доступні хости все одно отримали потрібні зміни.

any_errors_fatal і max_fail_percentage у межах batch

Ці два ключові слова play визначають поведінку після помилки на частині fleet. Вони по-різному обробляють недоступні хости.

any_errors_fatal: true реагує на недоступний хост. Ansible завершує поточне завдання на решті хостів у batch, а потім зупиняє play для всіх хостів у цьому batch. Використовуйте цей параметр, коли виконання має сенс лише за принципом «усе або нічого», наприклад під час узгодженої зміни схеми.

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

Є один випадок, коли недоступні хости самостійно зупиняють виконання. Якщо кожен хост у batch має стан failed або unreachable, 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 виконує весь play на двох хостах, завершує його, а потім запускає наступний на двох інших. serial: "25%" масштабується відповідно до розміру групи. Список, serial: [1, 5, 10], задає canary-схему: спочатку один хост, потім п’ять, потім десять; решта хостів запускаються пакетами розміром з останній пакет. max_fail_percentage вимірюється для кожного пакета, тому ці параметри працюють разом. Виведіть з ладу першу машину — виконання зупиниться, перш ніж буде пошкоджено сорок машин. Саме тому керування парком Linux-серверів з однієї керівної машини безпечно виконувати однією командою.

Коли ігнорувати недоступні хости, а коли цього не робити

Ігноруйте їх для необов’язкових завдань. Збір фактів або щогодинна перевірка розбіжностей нічого не втрачає, пропускаючи вимкнений хост, оскільки наступний запуск знову його опрацює. На рівні play правильним рішенням є ignore_unreachable: true у поєднанні з кроком ping, щоб назви пропущених хостів потрапляли туди, де їх побачить відповідальна особа.

Ніколи не ігноруйте їх під час запуску security patch. Цінність такого запуску полягає в гарантії, що виправлення встановлено на кожному хості. Якщо приховати стан unreachable, повідомлення «один сервер досі вразливий» перетворюється на бездоганний підсумок із зеленим статусом. Хост, який був недоступний протягом двох тижнів, найімовірніше, найбільше відстає від актуального стану. Нехай цей запуск завершиться з кодом 4, а відповідальна особа перевірить причину.

В обох випадках діє одне правило: придушуйте зупинку, але ніколи не приховуйте запис. Якщо хост було пропущено, це має бути зазначено в підсумку, журналі CI або сповіщенні системи моніторингу. Ansible знає про існування хоста лише протягом кількох секунд, коли play виконується для нього. Тому Ansible не підходить для виявлення того, що сервер не працює ще з вівторка. Це завдання належить системі моніторингу, а Ansible playbook, який встановлює Zabbix дає змогу отримати загальне уявлення про стан усієї інфраструктури за один робочий день.

FAQ

У чому різниця між ignore_errors та ignore_unreachable в Ansible?

ignore_errors: true застосовується до завдання, яке виконувалося на хості й завершилося помилкою, наприклад команда повернула ненульовий код завершення. ignore_unreachable: true застосовується до хоста, з яким Ansible не зміг встановити з’єднання і на якому жоден модуль не виконувався. Ці параметри працюють із різними полями результату завдання, і жоден із них не охоплює інший випадок. У документації Ansible зазначено, що ignore_errors «не змушує Ansible ігнорувати помилки невизначених змінних, помилки з’єднання, проблеми виконання (наприклад, відсутні пакети) або синтаксичні помилки», а закритий порт SSH є помилкою з’єднання.

Чи приховує ignore_unreachable хост у підсумку виконання play?

Фактично так. Якщо встановити це ключове слово, 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, тому конвеєр, який перевіряє лише код завершення, не виявить пропущену машину.

Як пропустити решту play для хоста, який не відповів?

Зробіть перше завдання ansible.builtin.ping із ignore_unreachable: true та register: reachable, а потім додайте ansible.builtin.meta: end_host з умовою when: reachable.unreachable | default(false). end_host завершує play для цього хоста, не позначаючи його як такий, що завершився помилкою. Встановіть gather_facts: false на рівні play, щоб саме перевірка доступності була завданням, яке обробляє розірване з’єднання. Без цього шаблону недоступний хост залишається в play, і кожне наступне завдання знову очікує завершення тайм-ауту з’єднання.

Чи слід ігнорувати недоступні хости під час запуску security-патчів?

Ні. Запуск оновлень має сенс, оскільки дає гарантію, що кожен хост отримав оновлення. Ігнорування недоступних хостів замінює цю гарантію успішним підсумком виконання. Дозвольте процесу завершитися з кодом 4, перегляньте імена хостів, які не відповіли, і виправте проблему. Придушення помилок доречне для повторюваних непланових запусків, під час яких наступний прохід виявить усе пропущене.

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