如果一次播放中的所有主机都失败,Ansible 会停止整个剧本

Ansible stop the whole playbook if all hosts in a single play fail

我很难理解 ansible 的预期行为是什么,以防万一所有主机都在一次播放中失败,但剧本中其他主机上还有其他播放。

例如考虑以下剧本:

---
- name: P1
  hosts: a,b
  tasks:
    - name: Assert 1
      ansible.builtin.assert:
        that: 1==2
      when: inventory_hostname != "c"

- name: P2
  hosts: y,z
  tasks:
    - name: Debug 2
      ansible.builtin.debug:
        msg: 'YZ'

为清楚起见,所有 4 台主机 a,b,y,z 都指向 localhost

发生的事情是 assert 失败,整个剧本停止。然而,它似乎与文档相矛盾,该文档说在发生错误的情况下 ansible 停止在失败的主机上执行但在其他主机上继续执行,请参阅 Error handling

如果我将条件更改为 when: inventory_hostname != 'b',因此 b 不会失败,那么剧本会继续在主机 y,z.

上执行第二个剧本

对我来说,最初的失败似乎并不合理,因为主机 y,z 没有遇到任何错误,因此其他主机上的错误不应阻止对它们的执行。

这是一个错误还是我遗漏了什么?

多重播放的playbook只是顺序的,它无法在前面知道后面的播放中还有其他主机。

因为您的 assert 任务在第一个剧本中已经耗尽了该剧本的所有主持人,所以剧本停在那里是有道理的,因为它与第一个剧本中的任何其他任务没有任何关系P1,请记住,它对 P2 还一无所知,所以它就到此为止了。

这不是错误。这是设计使然(请参阅下面的注释 3,4)。正如在对其他答案的评论中所讨论的,当一个剧本中的所有主机都失败时是否终止整个剧本的决定似乎是 trade-off。用户要么必须处理如何在必要时继续下一个游戏,要么必须在必要时处理如何停止整个剧本。您可以在下面的示例中看到,这两个选项都需要大致相同程度地处理块中的错误。

  • 第一种情况是Ansible实现的:当一个play中的所有主机都失败时,一个playbook将终止。例如,
- hosts: host01,host02
  tasks:
    - assert:
        that: false
- hosts: host03
  tasks:
    - debug:
        msg: Hello
PLAY [host01,host02] *************************************************************************

TASK [assert] ********************************************************************************
fatal: [host01]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed
fatal: [host02]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed

PLAY RECAP ***********************************************************************************
host01                     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
host02                     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
  • 当一次播放中并非所有主机都失败时,剧本将继续进行下一次播放。例如,
- hosts: host01,host02
  tasks:
    - assert:
        that: false
      when: inventory_hostname == 'host01'
- hosts: host03
  tasks:
    - debug:
        msg: Hello
PLAY [host01,host02] *************************************************************************

TASK [assert] ********************************************************************************
fatal: [host01]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed
skipping: [host02]

PLAY [host03] ********************************************************************************

TASK [debug] *********************************************************************************
ok: [host03] => 
  msg: Hello

PLAY RECAP ***********************************************************************************
host01                     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
host02                     : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
host03                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • 当一个播放中的所有主机都失败时,要继续下一个播放,用户必须清除错误,并且可以选择结束播放中的主机。例如,
- hosts: host01,host02
  tasks:
    - block:
        - assert:
            that: false
      rescue:
        - meta: clear_host_errors
        - meta: end_host
- hosts: host03
  tasks:
    - debug:
        msg: Hello
PLAY [host01,host02] *************************************************************************

TASK [assert] ********************************************************************************
fatal: [host01]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed
fatal: [host02]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed

TASK [meta] **********************************************************************************

TASK [meta] **********************************************************************************

TASK [meta] **********************************************************************************

PLAY [host03] ********************************************************************************

TASK [debug] *********************************************************************************
ok: [host03] => 
  msg: Hello

PLAY RECAP ***********************************************************************************
host01                     : ok=0    changed=0    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0   
host02                     : ok=0    changed=0    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0   
host03                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • 更新:end_play2.12.2 中 'fixed' 之后,剧本无法被元 end_play 停止。
在 Ansible 2.12.1 中可以通过 meta *end_play* 结束整个剧本。想象一下,一个剧本中的所有主持人都失败不会终止整个剧本。换句话说,想象它不是那样实现的。然后,用户可能想自行终止剧本。例如,
- hosts: host01,host02
  tasks:
    - block:
        - assert:
            that: false
      rescue:
        - meta: clear_host_errors
        - set_fact:
            host_failed: true
    - meta: end_play
      when: ansible_play_hosts_all|map('extract', hostvars, 'host_failed') is all
      run_once: true
- hosts: host03
  tasks:
    - debug:
        msg: Hello
PLAY [host01,host02] *************************************************************************

TASK [assert] ********************************************************************************
fatal: [host01]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed
fatal: [host02]: FAILED! => changed=false 
  assertion: false
  evaluated_to: false
  msg: Assertion failed

TASK [meta] **********************************************************************************

TASK [set_fact] ******************************************************************************
ok: [host01]
ok: [host02]

TASK [meta] **********************************************************************************

PLAY RECAP ***********************************************************************************
host01                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0   
host02                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=1    ignored=0

注释

  1. meta end_host 表示:'end the play for this host'
- hosts: host01
  tasks:
    - meta: end_host
- hosts: host01,host02
  tasks:
    - debug:
        msg: Hello
PLAY [host01] ********************************************************************************

TASK [meta] **********************************************************************************

PLAY [host01,host02] *************************************************************************

TASK [debug] *********************************************************************************
ok: [host01] => 
  msg: Hello
ok: [host02] => 
  msg: Hello

PLAY RECAP ***********************************************************************************
host01                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host02                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  1. end_play means: 'end the playbook' (this was 'fixed' in 2.12.2. See #76672)
- hosts: host01
  tasks:
    - meta: end_play
- hosts: host01,host02
  tasks:
    - debug:
        msg: Hello
PLAY [host01] ********************************************************************************

TASK [meta] **********************************************************************************

PLAY RECAP ***********************************************************************************

  1. 引用自#37309

If all hosts in the current play batch (fail) the play ends, this is 'as designed' behavior ... 'play batch' is 'serial size' or all hosts in play if serial is not set.

  1. 引用自 source
# check the number of failures here, to see if they're above the maximum
# failure percentage allowed, or if any errors are fatal. If either of those
# conditions are met, we break out, otherwise, we only break out if the entire
# batch failed
failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \
    (previously_failed + previously_unreachable)

if len(batch) == failed_hosts_count:
    break_play = True
    break