在使用 Ansible 进行自动化运维时,script 模块是我们推送并执行本地脚本的利器。然而,当目标主机上的脚本运行时间较长(如系统巡检、数据备份、批量处理),或者脚本本身存在阻塞风险(如等待输入、死循环、网络超时)时,Ansible 的默认同步执行模式会导致整个 Playbook 卡住,直到脚本结束——甚至永久等待。
本文将详细介绍如何通过 异步执行 和 超时控制 来解决这些问题,让 script 模块更健壮、更可控。
一、问题场景:当脚本“不听话”时
你可能会遇到这样的情况:
- name: 运行健康检查脚本
script: health_check.sh如果 health_check.sh 在某些主机上需要运行 10 分钟,或者因为网络挂载问题而阻塞,那么 Ansible 会一直停留在该任务上,无法继续处理其他主机,甚至无法响应 Ctrl+C。这就是同步执行的弊端。
为此,Ansible 提供了异步执行机制,以及精细的超时控制参数。
二、异步执行核心参数:async 与 poll
要让一个任务异步运行,只需在任务中添加两个参数:async 和 poll。
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运行逻辑:
- Ansible 将脚本上传并启动一个后台作业。
- 每隔 10 秒询问该作业是否完成。
- 如果 600 秒内完成 → 成功。
- 如果超过 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: 103. 确保远程主机支持后台作业
Ansible 的异步依赖被管理节点的 ssh 会话能脱离终端运行。这通常默认支持,但在某些嵌入式设备或精简系统中可能失败。
4. 谨慎设置超时值
- 设置过小:正常的长脚本会被误杀。
- 设置过大:失去了保护意义。
建议根据脚本的历史执行时间上浮 20%~50%。
5. 结合 ignore_errors 与 any_errors_fatal
如果超时不应中断整个 Playbook,请使用 ignore_errors: yes。若希望任何主机超时就停止全部任务,可设置 any_errors_fatal: true。
六、总结
通过合理使用 async、poll 和 timeout,我们可以将 Ansible 的 script 模块从“同步阻塞”变为“异步可控”,有效避免因个别主机脚本异常而导致的 Playbook 卡死。同时,精细的超时控制也提升了自动化任务的健壮性。
在实际生产环境中,建议对任何运行时间可能超过 1 分钟的脚本任务都考虑添加异步与超时参数。这不仅是技术上的优化,更是保障运维自动化可靠性的重要手段。
希望本文能帮助你写出更健壮的 Ansible Playbook。如果你有更多关于 Ansible 的实践问题,欢迎留言讨论!