SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

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=0

Ansible 已连接到 web1 并运行了 7 个任务。web2 显示了 unreachable=1failed=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 时编写的代码。

在抑制报错前进行调试

如果抑制报错成为常态,会导致集群配置漂移,因为无法连接的主机往往也处于无人维护的状态。请优先按以下顺序排查。此处列出的所有命令均为只读操作。

  1. ansible web2 -i inventory.ini -m ansible.builtin.ping -o 针对单台主机运行单个模块,并输出一行结果。
  2. 在同一命令中添加 -vvvv。Ansible 会打印其构建的完整 ssh 命令,包括目标用户、端口、私钥以及传递的选项。
  3. 使用 -v 手动运行该 ssh 命令。如果普通的 ssh 无法连接,说明问题出在 Ansible 之下,任何 Playbook 关键字都无法解决。
  4. 读取 msg 字符串并对照上述列表。Connection refusedConnection timed out 指向两个不同的位置,前者指向 SSH 服务,后者指向网络路径。
  5. 对于 Host key verification failed.,查看 ssh-keygen -F web2.example.com 中存储的内容。如果服务器已重建,请使用 ssh-keygen -R web2.example.com 删除旧条目,并在通过服务商控制台核对后接受新密钥。在 ansible.cfg 中设置 host_key_checking = False 虽然能消除错误,但也移除了校验机制,导致无法发现是否有其他机器正在使用该地址响应。
  6. 对于 Permission denied (publickey),确认 Ansible 实际使用的配置。ansible-inventory -i inventory.ini --host web2 会打印当前生效的变量,包括 ansible_useransible_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 命令返回相同的代码。上述内容已于 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=7

web2 报告 unreachable=0 以及七个任务 ok,运行退出码为 0。设置该关键字后,Ansible 会增加该主机的 okignored 计数器,而不是增加它所称的 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: true

serial: 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,而是将其计为 okignored(每个任务一次),随后运行以 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: trueregister: reachable,随后在 when: reachable.unreachable | default(false) 条件下执行 ansible.builtin.meta: end_hostend_host 会结束该主机的剧本执行,且不会将其标记为失败。在剧本上设置 gather_facts: false,以便让 ping 任务成为检测连接中断的环节。如果不使用此模式,死机的主机将保留在剧本中,后续每个任务都会再次等待连接超时。

在执行安全补丁更新时,应该忽略不可达主机吗?

不应该。执行补丁更新的价值在于确保每台主机都已更新;忽略不可达主机只会用绿色的摘要掩盖这一事实。应让运行以 4 退出,读取未响应的主机名并进行修复。抑制报错仅适用于重复的随机运行,因为下一次尝试会捕获之前遗漏的内容。

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