ansible中的剧本(Playbook)详解之handler触发器

概述

handler(触发器):Ansible 中一类特殊任务,默认不会主动执行,只有被 notify 触发时才运行。

典型场景:配置文件修改后重启服务(nginx、mysql、redis 等),避免配置没变也无脑重启。

其关键特性如下:

  • 只有对应 task 状态为 changed(文件 / 配置被修改)才会触发 handler;
  • 所有普通 task 执行完毕后,统一批量执行被触发的 handler;
  • 多个 task 同时 notify 同一个 handler,只会执行一次;
  • handler 内部也可以 notify 其他 handler(链式触发)。

基础语法结构

复制代码
---
- name: 部署nginx配置并自动重启
  hosts: web
  tasks:
    # 任务1:推送nginx配置文件
    - name: 分发nginx.conf
      copy:
        src: ./files/nginx.conf
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      # 配置文件发生变更时,通知名为 restart nginx 的handler
      notify: restart nginx

    # 任务2:推送站点配置
    - name: 分发站点vhost配置
      copy:
        src: ./files/site.conf
        dest: /etc/nginx/conf.d/site.conf
      notify: restart nginx

  # 触发器定义区域,和tasks同级
  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

执行逻辑:

  • 两个 copy 任务只要任意一个文件发生修改(changed);
  • 所有 tasks 跑完后,仅执行一次 restart nginx。

handler示例:

多个task同时触发handler会发生什么?

  • 编写playbook文件

    root@master:/data00/ansible# cat handler1.yaml

    • name: handler使用
      hosts: es
      tasks:
      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
        notify: notify handler

      • name: copy test2文件
        copy:
        src: /data00/ansible/test2.txt
        dest: /tmp/test2.txt
        mode: 600
        notify: notify handler
        handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"

测试执行

发现远端机器被改变,handler被触发了,但是只执行一次

复制代码
root@master:/data00/ansible# ansible-playbook handler1.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.120.9]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
changed: [10.37.120.9]

TASK [copy test2文件] ********************************************************************************************************************************************************************************************
changed: [10.37.120.9]

# 发现这里handler被触发了
RUNNING HANDLER [notify handler] *********************************************************************************************************************************************************************************
ok: [10.37.120.9] => {
    "msg": "handler触发了"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.120.9                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

如果再执行一次会发生什么?

发现handler没有被触发,因为文件状态没有被改变

复制代码
root@master:/data00/ansible# ansible-playbook handler1.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.120.9]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
ok: [10.37.120.9]

TASK [copy test2文件] ********************************************************************************************************************************************************************************************
ok: [10.37.120.9]

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.120.9                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

定义多个handler,没有被notify会怎么样

复制代码
root@master:/data00/ansible# cat handler2.yaml 
- name: handler使用
  hosts: db
  tasks:
    - name: copy test1文件
      copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
      notify: notify handler
  # 定义handler
  handlers:
    - name: notify handler
      debug:
        msg: "handler触发了"
    - name: handler2
      debug:
        msg: "handler2触发了"
    - name: handler3
      debug:
        msg: "handler3触发了"

执行:

发现只有1个handler被触发了,其它的handler并没有被触发

复制代码
root@master:/data00/ansible# ansible-playbook handler2.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.99.63]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
changed: [10.37.99.63]

RUNNING HANDLER [notify handler] *********************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler触发了"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.99.63                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

多个handler之间的执行顺序是怎么样的?

先给结论,多个handler之间的执行顺序依赖文件中定义的先后顺序,不依赖notify的顺序。可以看下面的案例

  • 编写playbook文件

    root@master:/data00/ansible# cat handler3.yaml

    • name: handler使用
      hosts: db
      tasks:

      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
        notify: handler3

      • name: copy test2文件
        copy:
        src: /data00/ansible/test2.txt
        dest: /tmp/test2.txt
        mode: 600
        notify: handler2

      handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"
      • name: handler2
        debug:
        msg: "handler2触发了"
      • name: handler3
        debug:
        msg: "handler3触发了"
  • 执行:

发现上文先notify的handle3,然后notify的handler2。但是下面执行的handler2,然后执行的handler3

复制代码
root@master:/data00/ansible# ansible-playbook handler3.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.99.63]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
changed: [10.37.99.63]

TASK [copy test2文件] ********************************************************************************************************************************************************************************************
changed: [10.37.99.63]

# 先执行的handler2
RUNNING HANDLER [handler2] ***************************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler2触发了"
}
# 再执行handler3
RUNNING HANDLER [handler3] ***************************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler3触发了"
}

一次性notify多个handler

  • 编写playbook文件

    root@master:/data00/ansible# cat handler4.yaml

    • name: handler使用
      hosts: db
      tasks:

      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600

        一次性notify多个handler

        notify:
        • handler3
        • handler2
        • notify handler

      handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"
      • name: handler2
        debug:
        msg: "handler2触发了"
      • name: handler3
        debug:
        msg: "handler3触发了"
  • 执行

这里发现不依赖notify的定义顺序,只依赖handlers定义的先后顺序

复制代码
root@master:/data00/ansible# ansible-playbook handler4.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.99.63]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
changed: [10.37.99.63]

RUNNING HANDLER [notify handler] *********************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler触发了"
}

RUNNING HANDLER [handler2] ***************************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler2触发了"
}

RUNNING HANDLER [handler3] ***************************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler3触发了"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.99.63                : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

handler 链式触发(handler 内部 notify)

  • 编写playbook文件

    root@master:/data00/ansible# cat handler5.yaml

    • name: handler使用
      hosts: db
      tasks:

      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
        notify:
        • handler3

      handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"
      • name: handler2
        shell:
        cmd: "echo handler2"
        notify: notify handler
      • name: handler3
        shell:
        cmd: "echo handler3"
        notify: handler2
  • 执行

    root@master:/data00/ansible# ansible-playbook handler5.yaml

    PLAY [handler使用] ***********************************************************************************************************************************************************************************************

    TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
    ok: [10.37.99.63]

    TASK [copy test1文件] ********************************************************************************************************************************************************************************************
    changed: [10.37.99.63]

    RUNNING HANDLER [handler3] ***************************************************************************************************************************************************************************************
    changed: [10.37.99.63]

    RUNNING HANDLER [handler2] ***************************************************************************************************************************************************************************************
    changed: [10.37.99.63]

    PLAY RECAP *******************************************************************************************************************************************************************************************************
    10.37.99.63 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

强制立即执行 handler

默认 handler 在所有 task 末尾执行,若需要中途立刻触发,使用 meta 模块:

  • 编写playbook文件

    root@master:/data00/ansible# cat handler6.yaml

    • name: handler使用
      hosts: db
      tasks:

      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
        notify: handler3

      立刻执行上面触发的handler

      • meta: flush_handlers
      • name: copy test2文件
        copy:
        src: /data00/ansible/test2.txt
        dest: /tmp/test2.txt
        mode: 600
        notify: notify handler

      handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"
      • name: handler2
        debug:
        msg: "handler2触发了"
      • name: handler3
        debug:
        msg: "handler3触发了"
  • 执行:

    root@master:/data00/ansible# ansible-playbook handler6.yaml

    PLAY [handler使用] ***********************************************************************************************************************************************************************************************

    TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
    ok: [10.37.99.63]

    TASK [copy test1文件] ********************************************************************************************************************************************************************************************
    changed: [10.37.99.63]

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

    RUNNING HANDLER [handler3] ***************************************************************************************************************************************************************************************
    ok: [10.37.99.63] => {
    "msg": "handler3触发了"
    }

    TASK [copy test2文件] ********************************************************************************************************************************************************************************************
    changed: [10.37.99.63]

    RUNNING HANDLER [notify handler] *********************************************************************************************************************************************************************************
    ok: [10.37.99.63] => {
    "msg": "handler触发了"
    }

    PLAY RECAP *******************************************************************************************************************************************************************************************************
    10.37.99.63 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

handler中添加when判断

handler中添加上when判断,当条件表达式不成立时,handler也不会被触发。

  • 编写playbook文件

    root@master:/data00/ansible# cat handler7.yaml

    • name: handler使用
      hosts: db
      vars:
      env: prod
      tasks:

      • name: copy test1文件
        copy:
        src: /data00/ansible/test1.txt
        dest: /tmp/test1.txt
        mode: 600
        notify: handler3

      handlers:

      • name: notify handler
        debug:
        msg: "handler触发了"
        when: env is defined and env == "dev"
      • name: handler2
        debug:
        msg: "handler2触发了"
        when: env is defined and env == "test"
      • name: handler3
        debug:
        msg: "handler3触发了"
        when: env is defined and env == "prod"
  • 执行:

发现只有handler3被触发了

复制代码
root@master:/data00/ansible# ansible-playbook handler7.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.99.63]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
ok: [10.37.99.63]

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.99.63                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

root@master:/data00/ansible# echo 222 > test1.txt 
root@master:/data00/ansible# ansible-playbook handler7.yaml 

PLAY [handler使用] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [10.37.99.63]

TASK [copy test1文件] ********************************************************************************************************************************************************************************************
changed: [10.37.99.63]

RUNNING HANDLER [handler3] ***************************************************************************************************************************************************************************************
ok: [10.37.99.63] => {
    "msg": "handler3触发了"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************
10.37.99.63                : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

总结,handler常见坑点

  • handler name 必须完全匹配 notify 字符串(大小写、空格敏感)
  • tasks任务被触发时,只有当状态为changed时handler才会被触发,为ok时handler不会被触发
  • 多个任务 notify 同一个 handler,只会执行一次,无需担心重复重启服务。
  • handler 不会自动执行,无 notify 永远不运行若想强制运行 handler,可用 meta: flush_handlers
  • handler支持when判断条件,当when的条件表达式不成立时,handler不会被触发
  • handler中的任务执行顺序只依赖定义的先后顺序,不依赖notify的先后顺序