Handlers:在变更时运行操作
有时,您希望仅在机器上进行更改时才运行任务。例如,如果某个任务更新了服务的配置,您可能希望重新启动该服务,但如果配置未更改,则不希望重新启动。Ansible 使用 Handlers 来解决这种情况。Handlers 是仅在收到通知时才运行的任务。
Handler 示例
此 Playbook verify-apache.yml
包含一个带有 handler 的 Play。
---
- name: Verify apache installation
hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
notify:
- Restart apache
- name: Ensure apache is running
ansible.builtin.service:
name: httpd
state: started
handlers:
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
在此示例 Playbook 中,在 Play 中的所有任务完成后,handler 会重新启动 Apache 服务器。
通知 Handlers
任务可以使用 notify
关键字指示一个或多个 handler 执行。notify
关键字可以应用于任务,并接受在任务更改时通知的 handler 名称列表。或者,也可以提供包含单个 handler 名称的字符串。以下示例演示了如何通过单个任务通知多个 handler。
tasks:
- name: Template configuration file
ansible.builtin.template:
src: template.j2
dest: /etc/foo.conf
notify:
- Restart apache
- Restart memcached
handlers:
- name: Restart memcached
ansible.builtin.service:
name: memcached
state: restarted
- name: Restart apache
ansible.builtin.service:
name: apache
state: restarted
在上面的示例中,handler 在任务更改时按以下顺序执行:Restart memcached
, Restart apache
。handler 按照它们在 handlers
部分中定义的顺序执行,而不是按照 notify
语句中列出的顺序执行。多次通知同一个 handler 将导致该 handler 只执行一次,而不管有多少任务通知它。例如,如果多个任务更新配置文件并通知一个 handler 来重新启动 Apache,Ansible 只会重启 Apache 一次,以避免不必要的重启。
通知和循环
任务可以使用循环来通知 handler。当与变量结合使用以触发多个动态通知时,这尤其有用。
请注意,如果任务整体发生更改,则会触发 handler。当使用循环时,如果任何循环项发生更改,则会设置已更改状态。也就是说,任何更改都会触发所有 handler。
tasks:
- name: Template services
ansible.builtin.template:
src: "{{ item }}.j2"
dest: /etc/systemd/system/{{ item }}.service
# Note: if *any* loop iteration triggers a change, *all* handlers are run
notify: Restart {{ item }}
loop:
- memcached
- apache
handlers:
- name: Restart memcached
ansible.builtin.service:
name: memcached
state: restarted
- name: Restart apache
ansible.builtin.service:
name: apache
state: restarted
在上面的示例中,如果任何一个模板文件发生更改,memcached 和 apache 都会被重新启动,如果没有文件更改,则都不会被重新启动。
命名 Handlers
必须命名 Handlers,以便任务能够使用 notify
关键字通知它们。
或者,handler 可以使用 listen
关键字。使用此 handler 关键字,handler 可以监听可以分组多个 handler 的主题,如下所示:
tasks:
- name: Restart everything
command: echo "this task will restart the web services"
notify: "restart web services"
handlers:
- name: Restart memcached
service:
name: memcached
state: restarted
listen: "restart web services"
- name: Restart apache
service:
name: apache
state: restarted
listen: "restart web services"
通知 restart web services
主题会导致执行所有监听该主题的 handler,而不管这些 handler 如何命名。
这种使用方式使得触发多个 handler 更加容易。它还将 handler 与它们的名称分离,从而更容易在 Playbook 和角色之间共享 handler(尤其是在使用来自共享源(如 Ansible Galaxy)的第三方角色时)。
每个 handler 都应该有一个全局唯一的名称。如果定义了多个具有相同名称的 handler,则只能通知和执行最后一个加载到 Play 中的 handler,有效地屏蔽所有先前具有相同名称的 handler。
无论 handler 在哪里定义,handler(handler 名称和监听主题)都只有一个全局范围。这还包括在角色中定义的 handler。
控制 Handlers 何时运行
默认情况下,handler 在特定 Play 中的所有任务完成后运行。在以下每个部分之后,通知的 handler 会按照以下顺序自动执行:pre_tasks
、roles
/tasks
和 post_tasks
。这种方法很有效,因为 handler 只运行一次,而不管有多少任务通知它。例如,如果多个任务更新配置文件并通知一个 handler 来重新启动 Apache,Ansible 只会重启 Apache 一次,以避免不必要的重启。
如果需要 handler 在 Play 结束之前运行,请添加一个任务,使用 meta 模块刷新它们,该模块执行 Ansible 操作
tasks:
- name: Some tasks go here
ansible.builtin.shell: ...
- name: Flush handlers
meta: flush_handlers
- name: Some other tasks
ansible.builtin.shell: ...
meta: flush_handlers
任务会触发在该 Play 中该点已被通知的所有 handler。
一旦 handler 执行,无论是在每个提到的部分之后自动执行,还是通过 flush_handlers
meta 任务手动执行,它们都可以在 Play 的后续部分再次被通知和运行。
定义任务何时变更
您可以使用 changed_when
关键字来控制何时通知 handler 关于任务更改。
在以下示例中,handler 在每次复制配置文件时都会重新启动服务
tasks:
- name: Copy httpd configuration
ansible.builtin.copy:
src: ./new_httpd.conf
dest: /etc/httpd/conf/httpd.conf
# The task is always reported as changed
changed_when: True
notify: Restart apache
有关 changed_when
的更多信息,请参阅定义“已更改”。
在 Handlers 中使用变量
您可能希望您的 Ansible handler 使用变量。例如,如果服务的名称因发行版而略有不同,您希望您的输出显示每个目标机器重新启动的服务的确切名称。避免将变量放在 handler 的名称中。由于 handler 名称会提前进行模板化,因此 Ansible 可能无法为像这样的 handler 名称提供值:
handlers:
# This handler name may cause your play to fail!
- name: Restart "{{ web_service_name }}"
如果 handler 名称中使用的变量不可用,则整个 Play 将失败。在 Play 中更改该变量不会导致新创建的 handler。
相反,将变量放置在 handler 的任务参数中。您可以使用 include_vars
加载值,如下所示:
tasks:
- name: Set host variables based on distribution
include_vars: "{{ ansible_facts.distribution }}.yml"
handlers:
- name: Restart web service
ansible.builtin.service:
name: "{{ web_service_name | default('httpd') }}"
state: restarted
虽然 handler 名称可以包含模板,但 listen
主题不能。
角色中的 Handlers
来自角色的 handler 不仅仅包含在其角色中,而是与来自 Play 的所有其他 handler 一起插入到全局范围中。因此,它们可以在定义它们的角色之外使用。这也意味着它们的名称可能会与角色外部的 handler 冲突。为了确保通知来自角色的 handler 而不是来自角色外部的同名 handler,请使用以下形式的名称通知 handler:role_name : handler_name
。
在 roles
部分内通知的 handler 会在 tasks
部分的末尾自动刷新,但在任何 tasks
handler 之前。
Handlers 中的包含和导入
将诸如 include_task
这样的动态包含作为处理器通知会导致执行该包含中的所有任务。无法通知在动态包含内部定义的处理器。
将诸如 import_task
这样的静态包含作为处理器,会导致在剧本执行之前,该处理器被该导入中的处理器有效重写。静态包含本身无法被通知;另一方面,可以单独通知该包含中的任务。
元任务作为处理器
自 Ansible 2.14 起,允许将 元任务 用作处理器并进行通知。但请注意,flush_handlers
不能用作处理器,以防止出现意外行为。
限制
处理器不能运行 import_role
或 include_role
。处理器忽略标签。