使用Ansible处理程序
有时我们希望任务仅在计算机上进行更改时运行。例如,如果某个任务更新了某个服务的配置,则可能需要重新启动该服务,但如果配置未更改,则不需要重新启动。Ansible使用处理程序来处理这个用例。处理程序是仅在收到通知时运行的任务。每个处理程序都应该有一个全局唯一的名称。
处理程序总是按定义的顺序运行,而不是按notify语句中列出的顺序运行。对于使用listen的处理程序也是这样。
处理程序名称和侦听主题位于全局命名空间中。
处理程序名称是可模板化的,而侦听主题不是。
使用唯一的处理程序名称。如果触发多个同名处理程序,则会覆盖第一个处理程序。只有最后定义的一个将运行。
可以通知在静态include中定义的处理程序。
不能通知动态包含中定义的处理程序。
处理程序示例
我们要写一个 ansible-handlers.yml
安装"httpd"包,然后使用处理程序启动"httpd"服务。此处理程序仅在进行更改时执行,即依赖任务报告"changed=1"或者更高的值。
-- - name: Handlers Example hosts: server1 gather_facts: false tasks: - name: Install httpd latest version yum: name: httpd state: latest become: true notify: restart_httpd handlers: - name: restart_httpd become: true service: name: httpd state: started
说明:
我们必须注意"handlers"的缩进,因为它应该与"tasks"从同一行开始。
在这个剧本中,我创建了一个任务,使用sudo权限将httpd rpm安装到最新的可用版本。
接下来,我创建了一个处理程序,负责启动服务。处理程序将使用处理程序的"name"值执行,即使用"notify"关键字"restart_httpd"。
我使用了'notify'和'handler'的名称,因此这将检查任务的更改模式,并相应地决定执行'handler'。
让我们执行剧本:
[ansible@controller ~]$ ansible-playbook ansible-handlers.yml PLAY [Handlers Example] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Install httpd latest version] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** changed: [server1] RUNNING HANDLER [restart_httpd] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** changed: [server1] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** server1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
从输出中我们知道我们的任务和处理程序都已成功执行。
如果我们重新运行相同的playbook来检查是否执行了处理程序:
[ansible@controller ~]$ ansible-playbook ansible-handlers.yml PLAY [Handlers Example] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Install httpd latest version] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** ok: [server1] PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** server1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
这次没有执行任何更改,因为httpd已经处于安装状态。
在处理程序运行时刷新处理程序和控件
默认情况下,处理程序在特定游戏中的所有任务完成后运行。这种方法非常有效,因为不管有多少任务通知处理程序,处理程序只运行一次。例如,如果多个任务更新配置文件并通知处理程序重新启动Apache,Ansible只会反弹Apache一次,以避免不必要的重新启动。
在这个示例剧本中,我有两个任务,它们将使用调试模块在控制台上打印一些文本。我还添加了一个处理程序,它将提供执行的日期和时间。现在我从两个任务调用此处理程序:
-- - name: Handlers Example hosts: server1 gather_facts: false become: true tasks: - name: print message-1 debug: msg: "First Message" changed_when: true notify: run_handler - name: print message-2 debug: msg: "Second Message" changed_when: true notify: run_handler handlers: - name: run_handler debug: msg: "Today's date and time: {{ '%d-%m-%Y %H:%M:%S' | strftime }}"
让我们验证是否为这两个任务执行了处理程序:
[ansible@controller ~]$ ansible-playbook ansible-handlers.yml PLAY [Handlers Example] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [print message-1] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server1] => { "msg": "First Message" } TASK [print message-2] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server1] => { "msg": "Second Message" } RUNNING HANDLER [run_handler] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server1] => { "msg": "Today's date and time: 25-09-2017 09:55:03" } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** server1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
因此,不管两个任务都在调用处理程序,处理程序只被调用一次。
现在这是默认行为,但"如果需要在播放结束前运行处理程序",请添加一个"task"来使用"meta"模块刷新它们,该模块执行Ansible操作。我已经更新了我的playbook,在远程服务器上执行'sleep',然后观察处理程序的时间,以确保在每个任务之后都有处理程序的新实例被执行:
-- - name: Handlers Example hosts: server1 gather_facts: false become: true tasks: - name: Sleep for 2 seconds command: sleep 2 notify: run_handler - name: Flush handlers meta: flush_handlers - name: Sleep for 5 seconds command: sleep 5 notify: run_handler - name: Flush handlers meta: flush_handlers - name: Sleep for 7 seconds command: sleep 7 notify: run_handler handlers: - name: run_handler debug: msg: "Today's date and time: {{ '%d-%m-%Y %H:%M:%S:%s' | strftime }}"
其中:我的第一个任务将休眠2分钟,然后执行处理程序
然后我们刷新处理程序并调用第二个任务,该任务将休眠5秒并调用处理程序
然后,我们将再次刷新处理程序并在第三次调用处理程序之前再休眠7秒
在处理程序中,我使用了Jinja语法中的"strftime"来获取处理程序的执行时间
让我们执行剧本:
[ansible@controller ~]$ ansible-playbook ansible-handlers.yml PLAY [Handlers Example] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Sleep for 2 seconds] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server1] RUNNING HANDLER [run_handler] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server1] => { "msg": "Today's date and time: 25-09-2017 10:05:45:1601028345" } TASK [Sleep for 5 seconds] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server1] RUNNING HANDLER [run_handler] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server1] => { "msg": "Today's date and time: 25-09-2017 10:05:51:1601028351" } TASK [Sleep for 7 seconds] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** changed: [server1] RUNNING HANDLER [run_handler] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ok: [server1] => { "msg": "Today's date and time: 25-09-2017 10:05:58:1601028358" } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** server1 : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
所以现在我们的处理程序已经执行了三次了,因为我们在每次任务执行之后都会刷新处理程序,而且处理程序执行的时间也不一样,所以生命是美好的。
在处理程序中使用变量
我们可能希望Ansible处理程序使用变量。例如,如果服务的名称因分布而略有不同,则希望输出显示每个目标计算机的重新启动服务的确切名称。避免在处理程序的名称中放置变量。由于处理程序名称是早期模板化的,Ansible可能没有如下处理程序名称可用的值:
handlers: # This handler name Jan cause your play to fail! - name: Restart "{{ web_service_name }}"
如果处理程序名称中使用的变量不可用,"整个播放失败"。更改该变量不会导致新创建的处理程序。
因此,在我在示例剧本中使用的场景中,我们应该始终使用默认变量:
-- - name: Handlers Example hosts: server1 gather_facts: false become: true vars: pkg: httpd tasks: - name: Installing vsftpd debug: msg: "restarting vsftp" changed_when: true notify: restart vsftpd handlers: - name: "restart {{ pkg | default('vsftpd')}}" debug: msg: "Restarting {{ pkg | default('vsftpd')}}"
现在我已经定义了一个'pkg'变量,但是如果由于某种原因处理程序无法获得这个值,那么它将考虑默认变量值,而不是整个游戏失败:
让我们来执行这个剧本:
[ansible@controller ~]$ ansible-playbook ansible-handlers.yml PLAY [Handlers Example] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ** TASK [Installing vsftpd] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** changed: [server1] => { "msg": "restarting vsftp" } RUNNING HANDLER [restart vsftpd] ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** ***** ok: [server1] => { "msg": "Restarting vsftpd" } PLAY RECAP ** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *** server1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
所以我们的剧本成功地执行了任务和处理程序。