在使用 Ansible 进行自动化运维时,script 模块是我们推送并执行本地脚本的利器。然而,当目标主机上的脚本运行时间较长(如系统巡检、数据备份、批量处理),或者脚本本身存在阻塞风险(如等待输入、死循环、网络超时)时,Ansible 的默认同步执行模式会导致整个 Playbook 卡住,直到脚本结束——甚至永久等待。

本文将详细介绍如何通过 异步执行超时控制 来解决这些问题,让 script 模块更健壮、更可控。


一、问题场景:当脚本“不听话”时

你可能会遇到这样的情况:

- name: 运行健康检查脚本
  script: health_check.sh

如果 health_check.sh 在某些主机上需要运行 10 分钟,或者因为网络挂载问题而阻塞,那么 Ansible 会一直停留在该任务上,无法继续处理其他主机,甚至无法响应 Ctrl+C。这就是同步执行的弊端。

为此,Ansible 提供了异步执行机制,以及精细的超时控制参数。


二、异步执行核心参数:asyncpoll

要让一个任务异步运行,只需在任务中添加两个参数:asyncpoll

1. async —— 最大允许运行时长

  • 作用:指定任务在被管理节点上允许运行的最大秒数。超时后任务会被标记为失败。
  • 必选条件:一旦使用 async,就必须同时指定 poll(除非 poll=0 有特殊含义)。
  • 示例async: 600 表示任务最长运行 10 分钟。

2. poll —— 轮询间隔

  • 作用:Ansible 控制节点每隔多少秒去检查一次远程任务是否完成。
  • 取值

    • poll > 0:Ansible 会在后台轮询,直到任务结束或超时。此时 Playbook 会“停”在该任务,但不会阻塞 SSH 连接,控制节点可以定期获取状态。
    • poll = 0:启动任务后立即继续执行后续任务(“发射后不管”)。此时需要用 async_status 模块手动查询结果。
  • 示例poll: 10 表示每 10 秒检查一次。

3. 经典组合

- name: 异步执行脚本,每10秒检查一次,最长10分钟
  script: long_task.sh
  async: 600
  poll: 10

运行逻辑:

  1. Ansible 将脚本上传并启动一个后台作业。
  2. 每隔 10 秒询问该作业是否完成。
  3. 如果 600 秒内完成 → 成功。
  4. 如果超过 600 秒仍未完成 → 失败(async 超时)。

三、更严格的保护:timeout 参数

async 控制的是远程脚本的运行时长,而 Ansible 任务本身还可能受到网络抖动、控制机负载等因素的影响。timeout 参数(任务级超时)提供了第二道防线。

  • 作用:规定整个 Ansible 任务(包括文件传输、轮询等待、网络通信)的最大执行时间,超时后强制中断任务。
  • async 的区别
参数控制范围超时后影响
async远程脚本的实际执行时间仅标记任务失败,不中断 SSH 连接
timeout整个 Ansible 任务生命周期强制杀死任务进程,抛出异常
  • 建议:当使用异步时,将 timeout 设置为比 async 稍大一些的值(考虑轮询开销和网络延迟)。

完整示例:

- name: 安全运行长脚本
  script: heavy_check.sh
  async: 1200          # 脚本最多跑20分钟
  poll: 30             # 每30秒查一次
  timeout: 1500        # 整个任务最多等待25分钟
  ignore_errors: yes   # 可选:即使超时也不停止整个Playbook

四、实战案例:批量服务器巡检

假设我们有一个巡检脚本 collect_info.sh,在某些老旧服务器上可能运行超过 5 分钟。我们希望:

  • 最多等待 10 分钟;
  • 每 15 秒检查一次进度;
  • 如果某台主机卡死,不阻塞其他主机。

Playbook 片段:

- hosts: all
  gather_facts: no
  tasks:
    - name: 分发并执行巡检脚本(异步+超时)
      script: collect_info.sh
      async: 600
      poll: 15
      timeout: 720
      register: result
      ignore_errors: true

    - name: 显示执行结果
      debug:
        msg: |
          主机 {{ inventory_hostname }} 脚本输出:
          {{ result.stdout | default('无输出') }}
      when: result is defined

如果某个节点 600 秒后仍未完成,Ansible 会将其标记为失败,但继续执行其他主机的后续任务。


五、注意事项与最佳实践

1. 不要对所有任务都使用异步

异步会增加控制节点的轮询开销。对于几秒钟就能完成的脚本,同步执行更简单高效。

2. poll: 0 的特殊用法

poll: 0 时,任务会立即返回一个作业 ID。你需要后续用 async_status 模块获取结果。适用于希望并行触发多个脚本的场景。

- name: 启动脚本,不等待
  script: deploy.sh
  async: 3600
  poll: 0
  register: job

- name: 稍后检查结果
  async_status:
    jid: "{{ job.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 30
  delay: 10

3. 确保远程主机支持后台作业

Ansible 的异步依赖被管理节点的 ssh 会话能脱离终端运行。这通常默认支持,但在某些嵌入式设备或精简系统中可能失败。

4. 谨慎设置超时值

  • 设置过小:正常的长脚本会被误杀。
  • 设置过大:失去了保护意义。
    建议根据脚本的历史执行时间上浮 20%~50%。

5. 结合 ignore_errorsany_errors_fatal

如果超时不应中断整个 Playbook,请使用 ignore_errors: yes。若希望任何主机超时就停止全部任务,可设置 any_errors_fatal: true


六、总结

通过合理使用 asyncpolltimeout,我们可以将 Ansible 的 script 模块从“同步阻塞”变为“异步可控”,有效避免因个别主机脚本异常而导致的 Playbook 卡死。同时,精细的超时控制也提升了自动化任务的健壮性。

在实际生产环境中,建议对任何运行时间可能超过 1 分钟的脚本任务都考虑添加异步与超时参数。这不仅是技术上的优化,更是保障运维自动化可靠性的重要手段。

希望本文能帮助你写出更健壮的 Ansible Playbook。如果你有更多关于 Ansible 的实践问题,欢迎留言讨论!

最后修改:2026 年 04 月 16 日
如果觉得我的文章对你有用,请随意赞赏