ansible中notify和handler使用“跳坑”记

notify:关键字,紧跟模块之后,用于通知handler(基于name进行通知),若写了notify,那么模块执行的结果为changed 状态将会触发所指定的handler.
handlers:关键字,和tasks是一个层级,里面可以包含有很多的task, 每个task在这里叫做handler,每个handler也是支持并且仅仅支持一个模块. 但是一个handlers 可以包含有很多个handler.
notify配合handler的使用方法以及小小的坑
notify 和handler是配合在一起使用的,但是: handler 并不会在notify 通知后立即执行,而是在运行到handlers的时候,最终执行一次,所以handlers 通常是写在play的最后,handlers里面的每个handler都需要一个name 属性. handlers中的handler 只能被notify 触发,如果没有notify触发,handlers 不会被执行.
这时候就导致了一个问题
如果play中的某个task带有notify属性,在当前的运行过程中,这个task的运行结果状态是changed, 这样就触发了notify, 但是在执行到相应的handler之前遇到了错误导致play 失败,此时我们重新运行play, 前面带有notify属性的task 返回的结果状态是“OK“, 不是changed, 所以不会触发 notify. 此时后面执行到 相应handler的时候 并不会执行对应的task. 这与我们所期望的结果是不一致的. 下面做一个简单的验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#首先编写一个playbook,内容如下:
ocalhost ~]# cat my.yml
---
- name: local test only.
  hosts: localhost
  tasks:
   - copy: "src=/etc/passwd dest=./passwd.bak"
     notify: start restart sshd.service
   - shell: "lsblkid"   #该task会报错而导致退出. 然后handler 无法成功执行
  handlers:
   - name: start restart sshd.service
     shell: "systemctl restart sshd.service"
...
[root@localhost ~]# ansible-playbook ./my.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [local test only.] *********************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [localhost]
TASK [copy] *********************************************************************************************************************************************************************************
changed: [localhost]
TASK [shell] ********************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "lsblkid", "delta": "0:00:00.005293", "end": "2020-11-03 14:23:54.802252", "msg": "non-zero return code", "rc": 127, "start": "2020-11-03 14:23:54.796959", "stderr": "/bin/sh: lsblkid: command not found", "stderr_lines": ["/bin/sh: lsblkid: command not found"], "stdout": "", "stdout_lines": []}
RUNNING HANDLER [start restart sshd.service] ************************************************************************************************************************************************
PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

上面的结果中,有两个状态是ok, 一个是failed, 还有一个状态是changed。 其中 :
gating facts 也是一个task, 结果是ok
copy task返回的状态是changed, changed状态必然也是 ok 状态,但是返回ok状态一定不会是changed 状态.
shell task对应的是failed的结果
在上面的结果中,其实是 触发了handler的,所以可以看到有一条: RUNNING HANDLER [start restart sshd.service] 的提示,但是对应的handler 却没有得到执行,这是因为默认情况下,一旦task失败,那么后续的task 都会停止运行, 而task 本质上都是module的执行,所以 handler也就因为前面task的失败而无法获得运行, 要避免这个失败导致的task 退出,可以在特定的task 中添加 ignore_errors: True 关键字.

下面修改my.yml 文件,使其可以正常的运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@localhost ~]# cat my.yml
---
- name: local test only.
  hosts: localhost
  tasks:
   - copy: "src=/etc/passwd dest=./passwd.bak"
     notify: start restart sshd.service
   - shell: "lsblk"
  handlers:
   - name: start restart sshd.service
     shell: "systemctl restart sshd.service"
...
[root@localhost ~]# ansible-playbook ./my.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [local test only.] *********************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************
ok: [localhost]
TASK [copy] *********************************************************************************************************************************************************************************
ok: [localhost]
TASK [shell] ********************************************************************************************************************************************************************************
changed: [localhost]
PLAY RECAP **********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

这一次,playbook成功执行完成,但是并没有执行handler,这是因为 copy task返回的结果状态为 ok, 所以不会触发 notify. 其返回的结果解释如下:
3个状态是ok的task分别是:gathering facts, copy, shell.
1个状态是changed的task 是: shell
handler 没有获得运行,因为根本就没有获得触发条件

总结一下就是:

A.
触发handler 和 handler获得执行是两回事,成功触发后,会在playbook的运行结果中看到 RUNNING HANDLER 字样的提示,但是不代表就执行了.
B.
handler要获得执行,除了满足触发条件外,需要前面的task 无误的执行到 handler ,如果前面有错误导致有task 失败,那么默认情况下, playbook会终止在目标机器上执行,从而导致handler 即使被触发,那么也无法获得执行.
C.
对于上述的问题,一个解决办法是 : 在前面可能失败的task里面,增加 ‘ignore_errors: True’ 属性. 这样playbook就可以无视该task的执行结果而继续向下执行,从而避免成功触发却无法执行的情形.