SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor

Ansible: ignorar hosts inacessíveis

Saiba por que ignore_errors não resolve o erro UNREACHABLE no Ansible e como usar ignore_unreachable, serial e max_fail_percentage sem perder hosts.

Um host inacessível não é uma tarefa falhada

Para ignorar hosts inacessíveis no Ansible, defina ignore_unreachable: true. A opção funciona. O importante é saber quando a utilizar, porque o Ansible trata dois problemas diferentes de duas formas diferentes. Uma tarefa que foi executada no host e devolveu um erro é uma falha. Um host ao qual o Ansible não conseguiu ligar-se de todo está inacessível. ignore_errors aplica-se apenas ao primeiro caso. ignore_unreachable aplica-se apenas ao segundo caso.

Esta é a diferença no resumo da execução.

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

O Ansible ligou-se a web1 e executou sete tarefas. web2 mostra unreachable=1 e failed=0, o que significa que nada foi executado nesse host. O Ansible nunca conseguiu estabelecer uma ligação, por isso removeu o host da execução e continuou com os restantes. Se essa execução estava a instalar uma atualização de segurança, um dos seus servidores não a tem.

O que torna um host inacessível

Inacessível significa que a ligação falhou antes de qualquer módulo chegar ao host. Não existe saída de módulo para consultar, apenas um erro de ligação, que aparece na primeira tarefa que contacta a máquina.

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}

O campo msg contém a causa real. Estes são os casos que irá encontrar:

  • Connection refused: a ligação TCP foi rejeitada, portanto não existe nada a escutar nessa porta. O sshd está parado, ou o SSH foi movido para outra porta e o inventário ainda indica 22.
  • Connection timed out: não houve qualquer resposta. Uma firewall está a descartar os pacotes, ou o servidor está desligado. Cada tentativa consome todo o tempo limite da ligação, que é de 10 segundos por predefinição.
  • Host key verification failed.: a chave em ~/.ssh/known_hosts não corresponde à chave apresentada pelo servidor. Um VPS reconstruído mantém o endereço IP e recebe uma nova chave de host, portanto isto é esperado depois de uma reinstalação e grave em qualquer outra situação.
  • Permission denied (publickey): o SSH respondeu e rejeitou a sua chave. A porta está a funcionar, portanto trata-se de um problema de autenticação, normalmente o ansible_user incorreto ou uma chave que não está carregada.
  • Timeout (12s) waiting for privilege escalation prompt: a ligação funcionou, mas become não. O sudo está à espera de uma palavra-passe que nunca chega.

A falta de um interpretador Python é a causa que muitas pessoas esperam ver nessa lista, mas não pertence aí. O SSH liga-se, portanto o host está acessível. O módulo fica sem nada onde possa ser executado:

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}

Essa linha indica FAILED! e o resumo contabiliza-a em failed, portanto ignore_unreachable nunca irá contactá-lo. Defina ansible_python_interpreter para esse host ou instale python3 nele.

Como ignorar hosts inacessíveis numa execução

Ao nível da tarefa, a palavra-chave fica ao lado do módulo:

- 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

Ao nível da execução, define o valor predefinido para todas as tarefas dessa execução, e uma tarefa individual pode repô-lo:

- 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

É importante compreender o que acontece internamente. Com ignore_unreachable definido, o host já não é removido da execução. Por isso, todas as tarefas seguintes tentam ligar-se novamente e falham da mesma forma. Cada tentativa aguarda o tempo limite da ligação, 10 segundos por predefinição, a menos que altere timeout em ansible.cfg. Uma execução com vinte tarefas contra um servidor inativo acrescenta cerca de 200 segundos à execução e vinte linhas de erro ao log.

Por isso, faça uma verificação única e depois interrompa corretamente o processamento desse host:

- 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

Assim, há uma tentativa de ligação por host inativo, em vez de uma por tarefa. end_host, adicionada no Ansible 2.8, termina a execução para o host atual sem o marcar como falhado. A chave unreachable só existe no resultado registado quando a ligação falha. Por isso, default(false) mantém a condição válida em todos os hosts que responderam. A recolha de factos está desativada ao nível da execução porque a tarefa implícita Gathering Facts seria, caso contrário, a tarefa que encontraria a ligação interrompida. O objetivo é que essa verificação seja feita pelo seu próprio ping.

ignore_unreachable é uma palavra-chave de execução e de tarefa. Mantenha-a no playbook, num local visível para quem o lê, em vez de a colocar dentro de uma role, porque ela determina quais os hosts que uma execução pode não conseguir contactar. A separação entre playbooks e roles explica qual camada deve ser responsável por uma definição deste tipo.

Por que ignore_errors é a ferramenta errada neste caso

A documentação do Ansible é clara quanto a esta limitação. ignore_errors "só funciona quando a tarefa pode ser executada e devolve um valor de 'failed'. Não faz o Ansible ignorar erros de variáveis indefinidas, falhas de ligação, problemas de execução (por exemplo, pacotes em falta) ou erros de sintaxe."

Uma falha de ligação nunca se transforma num resultado de tarefa com failed: true. É comunicada como um sinalizador separado, e o Ansible processa primeiro esse sinalizador: o host é colocado na lista de hosts inacessíveis e sai do play. Mesmo que coloque ignore_errors: true nas doze tarefas de um play, um host com a porta SSH fechada continua a parar na primeira tarefa. Esta é a confusão mais comum nesta área. Vale a pena procurar isto com grep nos seus playbooks antigos, sobretudo nos que foram escritos enquanto aprendia a escrever o primeiro playbook para uma VPS.

Depure antes de suprimir

Uma supressão que se torna permanente faz a frota divergir, porque o host a que ninguém consegue aceder também é o host que ninguém atualiza. Siga primeiro esta ordem. Cada comando aqui apenas lê dados.

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o executa um módulo num host e mostra uma linha.
  2. Adicione -vvvv ao mesmo comando. O Ansible mostra o comando ssh completo que constrói, incluindo o utilizador de destino, a porta, a chave privada e as opções que passa.
  3. Execute esse comando ssh manualmente com -v. Se o ssh simples não conseguir estabelecer ligação, o problema está abaixo do Ansible e nenhuma palavra-chave do playbook o resolverá.
  4. Leia a cadeia msg e compare-a com a lista acima. Connection refused e Connection timed out apontam para locais diferentes: um para o serviço SSH e outro para o caminho de rede.
  5. Para Host key verification failed., consulte o que tem guardado com ssh-keygen -F web2.example.com. Se o servidor foi recriado, remova a entrada antiga com ssh-keygen -R web2.example.com e aceite a nova chave depois de a confirmar na consola do fornecedor. Definir host_key_checking = False em ansible.cfg elimina o erro, mas também remove a verificação que avisaria se outra máquina estivesse agora a responder nesse endereço.
  6. Para Permission denied (publickey), confirme o que o Ansible considera que deve utilizar. ansible-inventory -i inventory.ini --host web2 mostra as variáveis em vigor, incluindo ansible_user e ansible_port.
  7. Se o SSH funcionar mas os módulos não, verifique o interpretador com ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'. O módulo raw executa um comando através da shell e não precisa de python no destino.

Só depois disso ignorar o host passa a ser uma decisão, e não um hábito.

A recapitulação contabiliza os hosts inacessíveis separadamente, e o CI normalmente não deteta isso

ansible-playbook termina com 0 em caso de sucesso, com 2 quando pelo menos um host falha e com 4 quando pelo menos um host está inacessível. Esses dois valores são flags de bits no código-fonte, por isso uma execução com um host que falhou e outro inacessível termina com 6. O comando ansible devolve os mesmos códigos. Estes valores foram confirmados no código-fonte do ansible-core em agosto de 2026.

Agora defina ignore_unreachable: true e execute o mesmo play com sete tarefas contra o mesmo host inativo:

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 indica unreachable=0 e sete tarefas ok, e a execução termina com 0. Quando a palavra-chave está definida, o Ansible incrementa os contadores ok e ignored desse host, em vez do contador que chama dark, que é o contador apresentado na coluna unreachable. As linhas vermelhas UNREACHABLE! continuam a ser apresentadas, por isso o log é honesto, mas a recapitulação e o código de saída não são.

Um job de CI que executa o playbook e verifica apenas $? considera essa execução bem-sucedida, e nada no resumo indica que uma máquina nunca foi contactada. Torne a verificação de acessibilidade uma etapa própria, antes do play:

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

Isto apresenta uma linha por host e termina com 4 se algum host estiver inacessível. Assim, o pipeline tem uma condição que pode provocar a falha, e os nomes dos hosts ficam registados no log. ping requer um interpretador Python funcional no destino, por isso confirma um pouco mais do que apenas a ligação, que normalmente é o comportamento pretendido. Em seguida, execute o playbook com ignore_unreachable, para que os hosts acessíveis continuem a receber a alteração.

any_errors_fatal e max_fail_percentage num lote

Estas duas palavras-chave do play definem o que acontece depois de algo falhar em parte da frota. Cada uma trata os hosts inacessíveis de forma diferente.

any_errors_fatal: true reage a um host inacessível. O Ansible termina a tarefa atual nos restantes hosts do lote e, em seguida, interrompe o play para todos os hosts desse lote. Use esta opção quando a execução só fizer sentido como uma operação integral, como numa alteração coordenada de schema.

max_fail_percentage: 30 não reage a um host inacessível. A verificação divide o número de hosts com falha pelo tamanho do lote. Os hosts inacessíveis ficam numa lista separada e nunca alteram esse número. Dez hosts, com quatro inacessíveis, continuam a execução com max_fail_percentage: 10, enquanto dois hosts com falha numa tarefa interrompem o play. A documentação acrescenta outra armadilha: "A percentagem definida tem de ser ultrapassada, não igualada." Com serial: 4, para interromper depois de duas falhas em quatro hosts, escreva 49, não 50.

Existe um caso em que os hosts inacessíveis interrompem a execução por si próprios. Se todos os hosts do lote tiverem falhado ou estiverem inacessíveis, o Ansible deixa de ter hosts disponíveis para trabalhar e termina o play com NO MORE HOSTS LEFT.

serial: aplicando uma alteração progressivamente no parque

- 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 executa o play completo em dois hosts, termina-o e só depois inicia o lote seguinte de dois hosts. serial: "25%" adapta-se ao tamanho do grupo. Uma lista, serial: [1, 5, 10], define a progressão canário: primeiro um host, depois cinco e, em seguida, dez, com os hosts restantes a serem executados em lotes do último tamanho utilizado. max_fail_percentage é medido por lote, pelo que os dois parâmetros funcionam em conjunto. Se a primeira máquina falhar, a execução para antes de afetar quarenta. É isso que torna seguro gerir um parque de servidores Linux a partir de uma única máquina de controlo com um único comando.

Quando ignorar hosts inacessíveis e quando não o fazer

Ignore-os em tarefas oportunistas. Uma execução de recolha de factos ou uma verificação horária de divergências não perde nada ao ignorar um host que está indisponível, porque a execução seguinte volta a processá-lo. ignore_unreachable: true ao nível do play é a resposta correta nesse caso, associada ao passo de ping para que os nomes ignorados apareçam num local que uma pessoa irá consultar.

Nunca os ignore numa execução de aplicação de patches de segurança. O valor dessa execução está em garantir que todos os hosts têm a correção, e suprimir o estado inacessível transforma "um servidor continua vulnerável" num resumo verde sem problemas. O host que está inacessível há duas semanas é o mais provável de estar muito atrasado. Faça essa execução terminar com o código 4 e permita que uma pessoa analise o problema.

Há uma regra válida nos dois casos: suprima a paragem, nunca o registo. Se um host foi ignorado, algo tem de indicar isso, no resumo, no log do CI ou num alerta de monitorização. O Ansible só sabe que um host existe durante os segundos em que um play é executado contra ele, pelo que é um local inadequado para descobrir que um servidor está indisponível desde terça-feira. Essa tarefa pertence à monitorização, e um playbook do Ansible que instala o Zabbix fornece uma visão de toda a frota numa tarde.

FAQ

Qual é a diferença entre ignore_errors e ignore_unreachable no Ansible?

ignore_errors: true aplica-se a uma tarefa que foi executada no host e devolveu uma falha, por exemplo, porque um comando terminou com um código diferente de zero. ignore_unreachable: true aplica-se a um host ao qual o Ansible não conseguiu ligar-se e onde nenhum módulo chegou a ser executado. Estas opções leem campos diferentes do resultado da tarefa, e nenhuma abrange o caso tratado pela outra. A documentação do Ansible afirma que ignore_errors "não faz o Ansible ignorar erros de variáveis não definidas, falhas de ligação, problemas de execução (por exemplo, pacotes em falta) ou erros de sintaxe", e uma porta SSH fechada é uma falha de ligação.

O ignore_unreachable oculta o host do resumo da execução?

Na prática, sim. Com esta palavra-chave definida, o Ansible deixa de contar esse host em unreachable e passa a contá-lo como ok e ignored uma vez por tarefa; a execução termina então com o código 0. As linhas fatal: [host]: UNREACHABLE! continuam a ser apresentadas, pelo que o log permanece correto, embora o resumo e o código de saída não reflitam o problema. Verifique a coluna ignored ou execute ansible all -m ansible.builtin.ping -o como uma etapa separada, para que um host inacessível ainda produza um código de saída diferente de zero em algum ponto.

Que código de saída o ansible-playbook devolve quando um host está inacessível?

Devolve 4. Uma execução com pelo menos um host com falha devolve 2, e os dois valores são flags de bits. Por isso, uma execução com uma falha e um host inacessível devolve 6. Uma execução sem problemas devolve 0. Estes códigos foram confirmados no código-fonte do ansible-core em agosto de 2026. Definir ignore_unreachable: true remove o valor 4. É por isso que um pipeline que testa apenas o código de saída não deteta uma máquina ignorada.

Como ignoro o restante de um play para um host que nunca respondeu?

Torne a primeira tarefa ansible.builtin.ping com ignore_unreachable: true e register: reachable. Em seguida, coloque ansible.builtin.meta: end_host sob a condição when: reachable.unreachable | default(false). end_host termina o play para esse host sem o marcar como falhado. Defina gather_facts: false no play para que o ping seja a tarefa que deteta a ligação interrompida. Sem este padrão, o host indisponível permanece no play, e cada tarefa posterior volta a esperar pelo timeout da ligação.

Devo ignorar hosts inacessíveis durante uma execução de aplicação de patches de segurança?

Não. Uma execução de aplicação de patches é útil porque garante que todos os hosts têm a atualização. Ignorar hosts inacessíveis substitui essa garantia por um resumo verde. Deixe a execução terminar com o código 4, leia os nomes dos hosts que não responderam e corrija-os. A supressão deve ser usada em execuções oportunistas repetidas, nas quais a passagem seguinte detetará o que ficou por fazer.

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