存在不可达主机时Ansible循环执行失败的解决问询
问题
我正在编写一个Ansible Playbook,用于创建本地文件记录所有运行Docker的主机,Playbook内容如下:
- name: Retrieve list of hosts that match certain attributes hosts: all gather_facts: true become: true tasks: - name: Check servers where Docker is running systemd: name: docker register: is_docker_service_running - name: Write to file all servers where docker is running copy: content: | {% for host in groups['all'] %} {% if hostvars[host].is_docker_service_running.status.ActiveState == 'active' %}{{ hostvars[host].inventory_hostname }} {% endif -%} {% endfor %} dest: /tmp/servers_with_docker delegate_to: localhost
该Playbook在正常情况运行良好,但当Inventory中有主机宕机/不可达时,整个Play会失败(即使其他200多台主机正常),所有可达主机都会报变量未定义的错误:
fatal: [alive-host1 -> localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running'. 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running' fatal: [alive-host2 -> localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running'. 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running' fatal: [alive-host3 -> localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running'. 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running' fatal: [alive-host4 -> localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running'. 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'is_docker_service_running' [...] [...]
将不可达主机从Inventory移除后,Playbook恢复正常。我想知道Ansible为何未忽略不可达主机,以及如何让循环完全忽略这类主机。
根本原因分析
- 静态主机组特性:
groups['all']是Inventory的静态主机集合,不管主机是否可达、是否能执行任务,都会被包含在内。Ansible不会自动剔除不可达主机,除非你明确配置过滤逻辑。 - 变量注册逻辑:第一个任务(检查Docker服务)只会在可达主机上执行并注册
is_docker_service_running变量;不可达主机根本没机会执行这个任务,自然不会生成该变量。 - 模板循环的致命错误:第二个任务的Jinja2循环遍历了
groups['all']的所有主机,当循环到不可达主机时,尝试读取不存在的is_docker_service_running变量,触发未定义变量的致命错误,直接导致整个任务失败,所有可达主机的结果也无法输出。
解决方案
方案一:过滤不可达主机+变量存在性检查
修改Playbook,让任务跳过不可达主机,并在循环中先检查变量是否存在:
- name: Retrieve list of hosts that match certain attributes hosts: all gather_facts: true become: true max_fail_percentage: 100 # 允许所有主机失败,确保Play执行到最后 tasks: - name: Check servers where Docker is running systemd: name: docker register: is_docker_service_running ignore_unreachable: true # 主机不可达时标记任务为跳过,而非失败 ignore_errors: true # 可选:处理Docker服务不存在等错误场景 - name: Write to file all servers where docker is running copy: content: | {% for host in groups['all'] %} {% set host_data = hostvars[host] %} {% if host_data.get('is_docker_service_running') is defined and host_data.is_docker_service_running.status.ActiveState == 'active' %} {{ host_data.inventory_hostname }} {% endif -%} {% endfor %} dest: /tmp/servers_with_docker delegate_to: localhost
方案二:使用动态主机集合简化逻辑
直接使用Ansible内置的ansible_play_hosts变量,这个集合只包含成功执行了Play中任务的主机,天然排除不可达或任务失败的主机:
- name: Retrieve list of hosts that match certain attributes hosts: all gather_facts: true become: true max_fail_percentage: 100 tasks: - name: Check servers where Docker is running systemd: name: docker register: is_docker_service_running ignore_unreachable: true ignore_errors: true - name: Write to file all servers where docker is running copy: content: | {% for host in ansible_play_hosts %} {% if hostvars[host].is_docker_service_running.status.ActiveState == 'active' %} {{ hostvars[host].inventory_hostname }} {% endif -%} {% endfor %} dest: /tmp/servers_with_docker delegate_to: localhost
关键修改说明
ignore_unreachable: true:让不可达主机的任务标记为跳过,而非终止整个Play。max_fail_percentage: 100:确保即使大量主机不可达,Play依然能执行到生成文件的步骤。ansible_play_hosts:替代静态的groups['all'],自动过滤掉未完成任务的主机,避免变量不存在的错误。
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

