Ansible 如何忽略不可达主机 (ignore_unreachable)
Ansible 中不可达主机与任务失败的处理机制不同。本文详细介绍如何使用 ignore_unreachable 关键字,并结合 serial 和 max_fail_percentage 参数有效管理连接故障,确保在主机无法连接时 Playbook 仍能继续执行并准确记录缺失任务。
主机不可达并非任务失败
若要在 Ansible 中忽略不可达的主机,请设置 ignore_unreachable: true,该开关即可生效。关键在于明确何时使用它,因为 Ansible 以两种不同的方式处理两种不同的问题。在主机上运行并返回错误的任务属于失败。Ansible 完全无法连接的主机属于不可达。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=0Ansible 已连接到 web1 并运行了 7 个任务。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 已迁移到其他端口,但你的清单文件仍指向 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。
如何在 Play 中忽略不可达主机
在任务级别,该关键字与模块并列:
- 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 中移除,因此后续的每个任务都会尝试重新连接,并以同样的方式失败。每次尝试都会等待连接超时,除非你在 ansible.cfg 中修改了 timeout,否则默认超时时间为 10 秒。针对一台宕机服务器执行 20 个任务的 Play 会增加约 200 秒的运行时间,并在日志中产生 20 行红色错误。
因此,请检查一次,然后干净利落地停止该主机:
- 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这样每个宕机主机只会进行一次连接尝试,而不是每个任务尝试一次。Ansible 2.8 中引入的 end_host 会结束当前主机的 Play,且不会将其标记为失败。unreachable 键仅在连接失败时才会出现在注册结果中,因此 default(false) 确保了该条件在所有响应的主机上均有效。在 Play 级别关闭事实收集(Fact gathering)是因为隐式的 Gathering Facts 任务会成为遇到连接中断的任务,而你希望该任务是你自定义的 ping 任务。
ignore_unreachable 是一个 Play 关键字,也是一个任务关键字。请将其保留在 Playbook 中以便阅读者查看,而不是放在 Role 内部,因为它决定了运行过程中允许跳过哪些主机。Playbook 与 Role 的拆分 涵盖了哪一层应该负责此类设置。
为什么 ignore_errors 在此场景下不适用
Ansible 文档明确指出了该功能的局限性。ignore_errors “仅在任务能够运行并返回 'failed' 状态时生效。它无法让 Ansible 忽略未定义变量错误、连接失败、执行问题(例如缺少软件包)或语法错误。”
连接失败不会作为任务结果通过 failed: true 处理。它会以独立的标志形式出现,Ansible 会优先处理该标志:主机将被移入 unreachable 列表并退出当前 play。即使在 play 的全部 12 个任务上都加上 ignore_errors: true,如果主机的 SSH 端口关闭,执行依然会在第一个任务处停止。这是该领域最常见的误解,建议检查您旧有的 playbook,特别是那些在 学习编写第一个针对 VPS 的 playbook 时编写的代码。
在抑制报错前进行调试
如果抑制报错成为常态,会导致集群配置漂移,因为无法连接的主机往往也处于无人维护的状态。请优先按以下顺序排查。此处列出的所有命令均为只读操作。
ansible web2 -i inventory.ini -m ansible.builtin.ping -o针对单台主机运行单个模块,并输出一行结果。- 在同一命令中添加
-vvvv。Ansible 会打印其构建的完整 ssh 命令,包括目标用户、端口、私钥以及传递的选项。 - 使用
-v手动运行该 ssh 命令。如果普通的 ssh 无法连接,说明问题出在 Ansible 之下,任何 Playbook 关键字都无法解决。 - 读取
msg字符串并对照上述列表。Connection refused和Connection timed out指向两个不同的位置,前者指向 SSH 服务,后者指向网络路径。 - 对于
Host key verification failed.,查看ssh-keygen -F web2.example.com中存储的内容。如果服务器已重建,请使用ssh-keygen -R web2.example.com删除旧条目,并在通过服务商控制台核对后接受新密钥。在ansible.cfg中设置host_key_checking = False虽然能消除错误,但也移除了校验机制,导致无法发现是否有其他机器正在使用该地址响应。 - 对于
Permission denied (publickey),确认 Ansible 实际使用的配置。ansible-inventory -i inventory.ini --host web2会打印当前生效的变量,包括ansible_user和ansible_port。 - 如果 SSH 连接正常但模块无法运行,请使用
ansible web2 -m ansible.builtin.raw -a 'command -v python3 || echo none'检查解释器。raw模块通过 shell 执行命令,不需要目标主机安装 python。
只有完成上述步骤后,忽略主机才算是一个决策,而不是一种习惯。
汇总统计将不可达主机单独计算,CI 通常会忽略这一点
ansible-playbook 在成功时返回 0,当至少有一台主机失败时返回 2,当至少有一台主机不可达时返回 4。这两个值在源码中是位标志,因此如果运行中既有失败主机又有不可达主机,退出码为 6。ansible 命令返回相同的代码。上述内容已于 2026 年 8 月根据 ansible-core 源码核实。
现在设置 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=7web2 报告 unreachable=0 以及七个任务 ok,运行退出码为 0。设置该关键字后,Ansible 会增加该主机的 ok 和 ignored 计数器,而不是增加它所称的 dark 计数器(即填充 unreachable 列的计数器)。红色的 UNREACHABLE! 行仍然会打印,因此日志是真实的,但汇总统计和退出码则不然。
如果 CI 作业仅运行 Playbook 并检查 $?,它会将该运行视为成功,且摘要中不会提及任何主机未被触达。应将连通性检查作为独立步骤,放在 Play 之前:
ansible all -i inventory.ini -m ansible.builtin.ping -o该命令为每台主机打印一行,若有任何主机不可达则退出码为 4,这使得流水线能够据此失败,并在日志中显示主机名称。ping 需要目标主机上有可用的 Python 解释器,因此它验证的内容比单纯的连接测试更多,这通常正是你所需要的。随后使用 ignore_unreachable 运行 Playbook,以确保在线的主机仍能执行变更。
any_errors_fatal 和 max_fail_percentage 在批次中的应用
这两个 Play 关键字决定了当集群部分节点出错后的行为,它们对不可达主机的处理方式不同。
any_errors_fatal: true 会对不可达主机做出反应。Ansible 会在批次中其余主机上完成当前任务,然后停止该批次中所有主机的 Play。当运行任务必须“要么全成功,要么全失败”(例如协调数据库模式变更)时,请使用此选项。
max_fail_percentage: 30 不会对不可达主机做出反应。该检查将失败主机数量除以批次大小,而不可达主机被归入单独列表,因此它们不会影响该比例。在 max_fail_percentage: 10 设置下,10 台主机中有 4 台不可达时任务会继续,而 2 台主机任务失败则会停止 Play。文档中还提到了一个陷阱:“必须超过设定的百分比,而不是等于。”若要使用 serial: 4 在 4 台主机中有 2 台失败时停止,必须设置为 49,而不是 50。
有一种情况是不可达主机也会导致运行停止。如果批次中的所有主机都处于失败或不可达状态,Ansible 将没有可操作的对象,并以 NO MORE HOSTS LEFT 结束 Play。
序列:在集群中滚动执行变更
- 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 会针对两台主机运行整个剧本,执行完成后,再启动接下来的两台。serial: "25%" 会随组的大小进行扩展。列表 serial: [1, 5, 10] 是金丝雀部署的形态:先运行一台主机,接着是五台,然后是十台,剩余的主机将按最后一批的大小进行分批运行。max_fail_percentage 是按批次计算的,因此两者协同工作。如果第一台机器出现故障,运行会立即停止,从而避免影响到四十台机器。这就是 从一台控制机管理 Linux 服务器集群 能够通过单条命令安全执行的原因。
何时忽略不可达主机,何时不应忽略
对于机会性任务,可以忽略不可达主机。事实收集运行或每小时一次的配置漂移检查,跳过宕机主机并无损失,因为下一次轮询会将其补上。此时使用 Play 级别的 ignore_unreachable: true 是正确的做法,配合 ping 步骤,可以将跳过的主机名记录在案,以便运维人员查阅。
对于安全补丁运行,绝不能忽略不可达主机。此类运行的价值在于确保每台主机都已应用修复。如果抑制了不可达状态,会将“一台服务器仍存在漏洞”的警告掩盖为“运行成功”的绿色汇总。一台已经两周不可达的主机,极有可能是版本滞后最严重的主机。应让此类运行以 exit 4 状态退出,并由人工介入检查。
无论哪种情况,都应遵循一条原则:抑制停止运行,但绝不抑制记录。如果某台主机被跳过,必须在汇总信息、CI 日志或监控告警中有所体现。Ansible 仅在针对主机运行 Play 的几秒钟内感知其存在,因此它不是获知服务器自周二起宕机的理想工具。该任务属于监控系统的职责,通过 安装 Zabbix 的 Ansible playbook,可以在一个下午内实现对整个集群的监控。
FAQ
Ansible 中 ignore_errors 和 ignore_unreachable 有什么区别?
ignore_errors: true 适用于在主机上运行并返回失败的任务,例如命令以非零状态码退出。ignore_unreachable: true 适用于 Ansible 无法连接到的主机,此时没有任何模块被执行。它们读取任务结果中不同的字段,且两者互不覆盖。Ansible 文档指出 ignore_errors “不会让 Ansible 忽略未定义变量错误、连接失败、执行问题(例如缺少软件包)或语法错误”,而 SSH 端口关闭属于连接失败。
ignore_unreachable 会在执行摘要中隐藏主机吗?
实际上会。设置该关键字后,Ansible 不会将该主机计入 unreachable,而是将其计为 ok 和 ignored(每个任务一次),随后运行以 0 状态码退出。fatal: [host]: UNREACHABLE! 行仍然会打印,因此日志是准确的,尽管摘要和退出码并不反映失败。请监控 ignored 列,或将 ansible all -m ansible.builtin.ping -o 作为独立步骤运行,以确保不可达的主机仍能产生非零退出码。
当主机不可达时,ansible-playbook 返回什么退出码?
它返回 4。至少有一个主机失败的运行返回 2。这两个值是位标志,因此同时包含失败和不可达主机的运行返回 6。运行成功则返回 0。这些代码已在 2026 年 8 月通过 ansible-core 源码核实。设置 ignore_unreachable: true 会消除 4,这就是为什么仅测试退出码的流水线无法发现被跳过的主机。
如何跳过对未响应主机的后续剧本任务?
将第一个任务设为 ansible.builtin.ping,并配合 ignore_unreachable: true 和 register: reachable,随后在 when: reachable.unreachable | default(false) 条件下执行 ansible.builtin.meta: end_host。end_host 会结束该主机的剧本执行,且不会将其标记为失败。在剧本上设置 gather_facts: false,以便让 ping 任务成为检测连接中断的环节。如果不使用此模式,死机的主机将保留在剧本中,后续每个任务都会再次等待连接超时。
在执行安全补丁更新时,应该忽略不可达主机吗?
不应该。执行补丁更新的价值在于确保每台主机都已更新;忽略不可达主机只会用绿色的摘要掩盖这一事实。应让运行以 4 退出,读取未响应的主机名并进行修复。抑制报错仅适用于重复的随机运行,因为下一次尝试会捕获之前遗漏的内容。