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=0O 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_hostsnã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 oansible_userincorreto ou uma chave que não está carregada.Timeout (12s) waiting for privilege escalation prompt: a ligação funcionou, masbecomenã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: trueAo 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: trueAssim, 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.
ansible web2 -i inventory.ini -m ansible.builtin.ping -oexecuta um módulo num host e mostra uma linha.- Adicione
-vvvvao 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. - 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á. - Leia a cadeia
msge compare-a com a lista acima.Connection refusedeConnection timed outapontam para locais diferentes: um para o serviço SSH e outro para o caminho de rede. - Para
Host key verification failed., consulte o que tem guardado comssh-keygen -F web2.example.com. Se o servidor foi recriado, remova a entrada antiga comssh-keygen -R web2.example.come aceite a nova chave depois de a confirmar na consola do fornecedor. Definirhost_key_checking = Falseemansible.cfgelimina o erro, mas também remove a verificação que avisaria se outra máquina estivesse agora a responder nesse endereço. - Para
Permission denied (publickey), confirme o que o Ansible considera que deve utilizar.ansible-inventory -i inventory.ini --host web2mostra as variáveis em vigor, incluindoansible_usereansible_port. - 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ódulorawexecuta 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=7web2 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 -oIsto 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: trueserial: 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.