SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor

Ansible: jak zignorować niedostępne hosty

Dowiedz się, jak poprawnie użyć parametru ignore_unreachable w Ansible. Rozróżnij błędy zadań od braku łączności z hostem i zapewnij poprawne wykonanie playbooka dla wszystkich maszyn.

Niedostępny host nie jest nieudanym zadaniem

Aby zignorować niedostępne hosty w Ansible, należy ustawić ignore_unreachable: true, co powoduje poprawne działanie przełącznika. Kluczowe jest zrozumienie, kiedy go użyć, ponieważ Ansible obsługuje dwa różne problemy na dwa odmienne sposoby. Zadanie, które zostało uruchomione na hoście i zwróciło błąd, jest traktowane jako niepowodzenie. Host, z którym Ansible nie mogło się w ogóle połączyć, jest oznaczony jako niedostępny. ignore_errors dotyczy wyłącznie pierwszego przypadku. ignore_unreachable dotyczy wyłącznie drugiego przypadku.

Oto różnica widoczna w podsumowaniu wykonania (play recap).

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 połączyło się z web1 i wykonało siedem zadań. web2 wskazuje unreachable=1 oraz failed=0, co oznacza, że na tym hoście nie uruchomiono żadnego zadania. Ansible nie nawiązało połączenia, więc usunęło hosta z procesu i kontynuowało pracę z pozostałymi. Jeśli ten proces miał na celu instalację aktualizacji bezpieczeństwa, jeden z serwerów jej nie posiada.

Przyczyny braku dostępności hosta

Brak dostępności oznacza, że połączenie nie powiodło się, zanim jakikolwiek moduł zdołał połączyć się z hostem. Brak jest danych wyjściowych modułu do analizy, występuje jedynie błąd połączenia, który pojawia się przy pierwszym zadaniu wymagającym kontaktu z maszyną.

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}

Pole msg zawiera rzeczywistą przyczynę. Oto najczęstsze z nich:

  • Connection refused: połączenie TCP zostało odrzucone, co oznacza, że na danym porcie nie nasłuchuje żadna usługa. Proces sshd jest zatrzymany lub SSH przeniesiono na inny port, podczas gdy w inwentarzu nadal widnieje port 22.
  • Connection timed out: brak jakiejkolwiek odpowiedzi. Firewall odrzuca pakiety lub serwer jest wyłączony. Każda próba trwa tyle, ile wynosi limit czasu połączenia, domyślnie 10 sekund.
  • Host key verification failed.: klucz w ~/.ssh/known_hosts nie pasuje do klucza przedstawionego przez serwer. Ponownie zainstalowany VPS zachowuje swój adres IP, otrzymując nowy klucz hosta, więc jest to zachowanie oczekiwane po reinstalacji, ale niepokojące w każdej innej sytuacji.
  • Permission denied (publickey): SSH odpowiedziało, ale odrzuciło klucz. Port jest poprawny, więc problem dotyczy uwierzytelniania, zazwyczaj błędnego ansible_user lub niezaładowanego klucza.
  • Timeout (12s) waiting for privilege escalation prompt: połączenie powiodło się, ale become nie. sudo oczekuje na hasło, które nie zostaje dostarczone.

Brak interpretera Python jest przyczyną, której użytkownicy często szukają na tej liście, jednak nie jest to błąd połączenia. SSH łączy się poprawnie, więc host jest dostępny. Moduł nie ma jednak środowiska, w którym mógłby zostać uruchomiony:

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}

Ten wiersz oznacza FAILED!, a podsumowanie zalicza go do failed, dlatego ignore_unreachable nigdy go nie przetworzy. Należy ustawić ansible_python_interpreter dla tego hosta lub zainstalować na nim python3.

Jak ignorować nieosiągalne hosty w play

Na poziomie zadania słowo kluczowe umieszcza się obok modułu:

- 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

Na poziomie play ustawia ono wartość domyślną dla każdego zadania w play, a pojedyncze zadanie może przywrócić poprzednie ustawienie:

- 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

Warto wiedzieć, co zmienia się wewnątrz. Przy ustawionym ignore_unreachable host nie jest usuwany z play, więc każde kolejne zadanie ponownie próbuje nawiązać połączenie i ponownie kończy się niepowodzeniem w ten sam sposób. Każda z tych prób oczekuje na upłynięcie limitu czasu połączenia (connection timeout), domyślnie 10 sekund, chyba że zmieniono timeout w ansible.cfg. Play składający się z dwudziestu zadań uruchomiony przeciwko jednemu niedziałającemu serwerowi wydłuża czas wykonania o około 200 sekund i dodaje dwadzieścia czerwonych linii do dziennika.

Dlatego należy sprawdzić raz, a następnie poprawnie zatrzymać obsługę tego hosta:

- 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

Dzięki temu przypada jedna próba połączenia na niedziałający host zamiast jednej na każde zadanie. end_host, dodane w Ansible 2.8, kończy play dla bieżącego hosta bez oznaczania go jako nieudanego. Klucz unreachable występuje w zarejestrowanym wyniku tylko wtedy, gdy połączenie nie powiodło się, więc default(false) utrzymuje poprawność warunku na każdym hoście, który odpowiedział. Zbieranie faktów (fact gathering) jest wyłączone na poziomie play, ponieważ domyślne zadanie Gathering Facts byłoby w przeciwnym razie zadaniem, które napotka zerwane połączenie, a celem jest, aby to własne zadanie ping było tym pierwszym.

ignore_unreachable jest słowem kluczowym zarówno dla play, jak i dla zadania. Należy umieszczać je w playbooku w miejscu widocznym dla czytelnika, a nie wewnątrz roli, ponieważ decyduje ono o tym, które hosty mogą zostać pominięte podczas uruchomienia. Podział między playbookami a rolami omawia, która warstwa powinna zarządzać tego typu ustawieniami.

Dlaczego ignore_errors jest w tym przypadku niewłaściwym narzędziem

Dokumentacja Ansible jasno określa ograniczenia tego rozwiązania. ignore_errors „działa tylko wtedy, gdy zadanie może zostać uruchomione i zwraca wartość 'failed'. Nie powoduje ignorowania przez Ansible błędów niezdefiniowanych zmiennych, awarii połączenia, problemów z wykonaniem (na przykład brakujących pakietów) ani błędów składni”.

Awaria połączenia nigdy nie staje się wynikiem zadania z failed: true. Jest ona traktowana jako osobna flaga, a Ansible reaguje na nią w pierwszej kolejności: host trafia na listę nieosiągalnych i zostaje wykluczony z play. Umieszczenie ignore_errors: true we wszystkich dwunastu zadaniach play nie zmieni faktu, że host z zamkniętym portem SSH zatrzyma się na pierwszym z nich. Jest to najczęstsze nieporozumienie w tym obszarze i warto przeszukać pod tym kątem starsze playbooki, szczególnie te napisane podczas nauki pisania pierwszego playbooka dla VPS.

Diagnostyka przed wyciszeniem

Wyciszanie błędów, które staje się trwałe, prowadzi do dryfu konfiguracji floty, ponieważ hosta, do którego nikt nie ma dostępu, nikt również nie aktualizuje. Należy najpierw wykonać poniższą procedurę. Każde z wymienionych poleceń służy wyłącznie do odczytu.

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o uruchamia jeden moduł dla jednego hosta i wyświetla jeden wiersz wyniku.
  2. Dodaj -vvvv do tego samego polecenia. Ansible wyświetli pełne polecenie ssh, które buduje, w tym użytkownika docelowego, port, klucz prywatny oraz przekazywane opcje.
  3. Uruchom to polecenie ssh samodzielnie za pomocą -v. Jeśli zwykłe ssh nie może nawiązać połączenia, problem leży poniżej warstwy Ansible i żadne słowo kluczowe w playbooku go nie rozwiąże.
  4. Odczytaj ciąg msg i porównaj go z powyższą listą. Connection refused oraz Connection timed out wskazują na dwa różne miejsca: jedno na usługę SSH, a drugie na ścieżkę sieciową.
  5. W przypadku Host key verification failed. sprawdź, co zostało zapisane w ssh-keygen -F web2.example.com. Jeśli serwer został przebudowany, usuń stary wpis za pomocą ssh-keygen -R web2.example.com i zaakceptuj nowy klucz po zweryfikowaniu go w konsoli dostawcy. Ustawienie host_key_checking = False w ansible.cfg usuwa błąd, ale również wyłącza kontrolę, która informuje o tym, że pod danym adresem odpowiada inna maszyna.
  6. W przypadku Permission denied (publickey) potwierdź, jakich parametrów według Ansible należy użyć. ansible-inventory -i inventory.ini --host web2 wyświetla obowiązujące zmienne, w tym ansible_user oraz ansible_port.
  7. Jeśli SSH działa, ale moduły nie, sprawdź interpreter za pomocą ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. Moduł raw uruchamia polecenie przez powłokę i nie wymaga zainstalowanego python na hoście docelowym.

Dopiero po wykonaniu tych kroków ignorowanie hosta staje się świadomą decyzją, a nie nawykiem.

Podsumowanie traktuje hosty nieosiągalne oddzielnie, a systemy CI zazwyczaj pomijają ten fakt

ansible-playbook kończy się kodem 0 w przypadku powodzenia, 2 gdy przynajmniej jeden host zawiódł oraz 4, gdy przynajmniej jeden host był nieosiągalny. Te dwie wartości są flagami bitowymi w kodzie źródłowym, więc uruchomienie, w którym wystąpił zarówno błąd hosta, jak i nieosiągalność, kończy się kodem 6. Polecenie ansible zwraca te same kody. Zostały one zweryfikowane względem kodu źródłowego ansible-core w sierpniu 2026 roku.

Teraz ustaw ignore_unreachable: true i uruchom ten sam playbook składający się z siedmiu zadań przeciwko temu samemu niedziałającemu hostowi:

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 raportuje unreachable=0 oraz siedem zadań ok, a wykonanie kończy się kodem 0. Gdy to słowo kluczowe jest ustawione, Ansible zwiększa liczniki ok oraz ignored dla danego hosta zamiast licznika, który nazywa dark, czyli tego, który wypełnia kolumnę unreachable. Czerwone linie UNREACHABLE! są nadal drukowane, więc dziennik pozostaje rzetelny, podczas gdy podsumowanie i kod wyjścia już nie.

Zadanie CI, które uruchamia playbook i sprawdza tylko $?, uznaje takie wykonanie za poprawne, a w jego podsumowaniu nie ma informacji, że maszyna nie została osiągnięta. Sprawdzanie dostępności należy wydzielić jako osobny krok, przed uruchomieniem playbooka:

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

To polecenie wypisuje jedną linię na hosta i kończy się kodem 4, jeśli którykolwiek host jest nieosiągalny, co pozwala potokowi (pipeline) na przerwanie pracy i dostarcza nazwy hostów w dzienniku. ping wymaga działającego interpretera Python na hoście docelowym, więc sprawdza nieco więcej niż tylko samo połączenie, co zazwyczaj jest pożądanym zachowaniem. Następnie należy uruchomić playbook z flagą ignore_unreachable, aby hosty, które są dostępne, nadal mogły przyjąć zmiany.

any_errors_fatal oraz max_fail_percentage w ramach partii

Te dwa słowa kluczowe decydują o działaniu po wystąpieniu błędu w części infrastruktury i odmiennie traktują hosty nieosiągalne.

any_errors_fatal: true reaguje na nieosiągalny host. Ansible kończy bieżące zadanie na pozostałych hostach w partii, a następnie przerywa wykonywanie play dla wszystkich hostów w tej grupie. Należy stosować to ustawienie, gdy operacja ma sens tylko w przypadku pełnego powodzenia, na przykład przy skoordynowanej zmianie schematu bazy danych.

max_fail_percentage: 30 nie reaguje na nieosiągalny host. Mechanizm ten dzieli liczbę hostów, na których wystąpił błąd, przez rozmiar partii, natomiast hosty nieosiągalne są przechowywane na oddzielnej liście i nie wpływają na ten wynik. Dziesięć hostów, z których cztery są nieosiągalne, kontynuuje pracę przy max_fail_percentage: 10, podczas gdy dwa hosty, na których zadanie zakończyło się niepowodzeniem, przerywają wykonywanie play. Dokumentacja wskazuje na dodatkową pułapkę: „Wartość procentowa musi zostać przekroczona, a nie osiągnięta”. Przy serial: 4, aby zatrzymać proces po dwóch błędach na cztery hosty, należy wpisać 49, a nie 50.

Istnieje przypadek, w którym nieosiągalne hosty samodzielnie przerywają działanie. Jeśli każdy host w partii zakończył pracę błędem lub jest nieosiągalny, Ansible nie ma już żadnych hostów do obsługi i kończy play z komunikatem NO MORE HOSTS LEFT.

serial: wdrażanie zmian w całej flocie

- 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 wykonuje cały playbook na dwóch hostach, kończy pracę, a następnie uruchamia kolejne dwa. serial: "25%" skaluje się wraz z rozmiarem grupy. Lista, serial: [1, 5, 10], definiuje schemat canary: najpierw jeden host, potem pięć, następnie dziesięć, a pozostałe hosty są przetwarzane w partiach o rozmiarze ostatniej grupy. max_fail_percentage jest mierzone dla każdej partii, więc oba parametry współpracują ze sobą. Awaria pierwszej maszyny powoduje zatrzymanie procesu, zanim wystąpią problemy na czterdziestu kolejnych. To właśnie sprawia, że zarządzanie flotą serwerów Linux z jednej maszyny sterującej jest bezpieczne przy użyciu pojedynczego polecenia.

Kiedy ignorować nieosiągalne hosty, a kiedy tego nie robić

Należy je ignorować w przypadku zadań oportunistycznych. Uruchomienie zbierania faktów lub godzinna kontrola dryfu konfiguracji nie tracą nic na pominięciu hosta, który jest wyłączony, ponieważ kolejne uruchomienie go uwzględni. Właściwym rozwiązaniem jest tutaj poziom play ignore_unreachable: true w połączeniu z krokiem ping, dzięki czemu pominięte nazwy zostaną odnotowane w miejscu widocznym dla administratora.

Nigdy nie należy ich ignorować podczas wdrażania poprawek bezpieczeństwa. Wartość takiego uruchomienia polega na gwarancji, że każdy host posiada poprawkę, a tłumienie stanu nieosiągalności zmienia komunikat „jeden serwer jest nadal podatny” w czyste, zielone podsumowanie. Host, który był nieosiągalny przez dwa tygodnie, jest najbardziej narażony na znaczne zaległości w aktualizacjach. Należy pozwolić takiemu uruchomieniu zakończyć się kodem 4 i umożliwić weryfikację przez człowieka.

W obu przypadkach obowiązuje jedna zasada: należy tłumić zatrzymanie, ale nigdy rejestrowanie zdarzenia. Jeśli host został pominięty, informacja o tym musi pojawić się w podsumowaniu, dzienniku CI lub alercie monitoringu. Ansible wie o istnieniu hosta tylko przez sekundy, w których wykonuje na nim zadania, więc jest to niewłaściwe miejsce do wykrywania, że serwer nie działa od wtorku. To zadanie należy do systemu monitoringu, a playbook Ansible instalujący Zabbix pozwala uzyskać wgląd w całą infrastrukturę w ciągu jednego popołudnia.

FAQ

Jaka jest różnica między ignore_errors a ignore_unreachable w Ansible?

ignore_errors: true dotyczy zadania, które zostało uruchomione na hoście i zwróciło błąd, na przykład polecenie zakończone kodem innym niż zero. ignore_unreachable: true dotyczy hosta, z którym Ansible nie zdołał nawiązać połączenia, przez co żaden moduł nie został wykonany. Parametry te odczytują różne pola w wyniku zadania i żaden z nich nie obejmuje przypadku drugiego. Dokumentacja Ansible podaje, że ignore_errors „nie powoduje ignorowania przez Ansible błędów niezdefiniowanych zmiennych, awarii połączenia, problemów z wykonaniem (na przykład brakujących pakietów) ani błędów składni”, a zamknięty port SSH jest awarią połączenia.

Czy ignore_unreachable ukrywa hosta w podsumowaniu play?

W praktyce tak. Po ustawieniu tego słowa kluczowego Ansible przestaje liczyć danego hosta w sekcji unreachable, a zamiast tego zalicza go do ok i ignored dla każdego zadania, co skutkuje kodem wyjścia 0. Linie fatal: [host]: UNREACHABLE! są nadal wyświetlane, więc dziennik pozostaje dokładny, mimo że podsumowanie i kod wyjścia są inne. Należy monitorować kolumnę ignored lub uruchomić ansible all -m ansible.builtin.ping -o jako osobny krok, aby nieosiągalny host nadal generował kod wyjścia różny od zera.

Jaki kod wyjścia zwraca ansible-playbook, gdy host jest nieosiągalny?

Zwraca kod 4. Uruchomienie, w którym wystąpił przynajmniej jeden błąd hosta, zwraca 2. Ponieważ obie wartości są flagami bitowymi, uruchomienie z błędem i nieosiągalnym hostem zwraca 6. Poprawne wykonanie zwraca 0. Kody te zostały zweryfikowane w kodzie źródłowym ansible-core w sierpniu 2026 roku. Ustawienie ignore_unreachable: true usuwa kod 4, dlatego potok (pipeline) sprawdzający tylko kod wyjścia nie wykryje pominiętej maszyny.

Jak pominąć resztę play dla hosta, który nie odpowiedział?

Należy wykonać pierwsze zadanie ansible.builtin.ping z parametrami ignore_unreachable: true oraz register: reachable, a następnie użyć ansible.builtin.meta: end_host z warunkiem when: reachable.unreachable | default(false). end_host kończy play dla danego hosta bez oznaczania go jako nieudanego. Należy ustawić gather_facts: false na poziomie play, aby to ping był zadaniem, które napotka zerwane połączenie. Bez tego wzorca nieosiągalny host pozostaje w play, a każde kolejne zadanie ponownie czeka na upłynięcie limitu czasu połączenia.

Czy należy ignorować nieosiągalne hosty podczas instalacji poprawek bezpieczeństwa?

Nie. Instalacja poprawek jest wykonywana w celu uzyskania gwarancji, że każdy host posiada aktualizację, a ignorowanie nieosiągalnych hostów zastępuje tę gwarancję zielonym podsumowaniem. Należy pozwolić, aby proces zakończył się kodem 4, odczytać nazwy hostów, które nie odpowiedziały, i naprawić je. Pomijanie błędów jest dopuszczalne jedynie w powtarzalnych, oportunistycznych uruchomieniach, gdzie kolejne przejście wyłapie to, co zostało pominięte.

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