Ansible角色中rescue-handler与run_once结合执行异常问询
问题复现
重构Ansible playbook时遇到异常:通过include_role引入包含rescue处理块和run_once任务的角色时,run_once任务会执行两次。
目录结构
|_ showcase.yml \_ sc_role \_ tasks |_ main.yml \_ block-with-rescue.yml
代码内容
- showcase.yml
--- - hosts: [a, b] gather_facts: false tasks: - include_role: name: sc_role
- sc_role/tasks/main.yml
--- - include_tasks: file: block-with-rescue.yml - debug: msg: "run_once task" run_once: true delegate_to: localhost
- sc_role/tasks/block-with-rescue.yml
--- - block: - debug: msg: "Provoke exception {{ u_n_d_e_f_i_n_e_d }}" when: inventory_hostname == "a" rescue: - debug: msg: "rescue task"
执行现象
主机a触发block异常后进入rescue块,主机b先执行run_once任务;待主机a完成rescue处理后,会再次执行run_once任务,导致任务重复执行。
原因分析
这个现象的核心是include_role的执行机制、run_once的跟踪逻辑,以及rescue块对执行流的影响共同作用的结果:
include_role的独立执行流特性
使用include_role时,角色的任务会嵌入到每个主机的独立执行流中,也就是说每个主机都会完整走一遍角色的任务流程,而非像roles关键字那样,先把角色任务合并到play的全局任务列表,再按批次处理所有主机的同一步任务。run_once的全局跟踪依赖执行上下文连续性
run_once: true的作用是让任务在整个play中仅执行一次,由第一个执行到该任务的主机触发,执行后会标记任务为已完成。但这个标记的识别依赖于任务执行流的连续性——如果执行流被异常中断并恢复,跟踪逻辑可能出现偏差。rescue块导致的执行流中断与恢复
主机a执行block时触发异常,进入rescue块处理,此时Ansible会暂停角色任务的正常执行流程。当rescue处理完成后,主机a的执行流回到角色任务序列,继续执行后续的run_once任务。此时由于之前的异常中断,Ansible内部没有正确识别到该任务已经被主机b执行过,而是将其视为主机a执行流中首次遇到的任务,因此再次触发执行。
规避方案的原理补充
已知的两种规避方法本质上都是改变了任务的执行上下文:
- 用roles段引入角色:角色任务会被预先合并到play的全局任务列表,所有主机先完成角色中的
include_tasks(含rescue处理),再统一处理run_once任务,自然只会执行一次。 - 将run_once任务移到include_role之后:该任务成为play的全局任务,不再属于角色内部的主机独立执行流,因此严格遵循
run_once的全局唯一执行逻辑。
内容的提问来源于stack exchange,提问作者dheuvels

