Handlers:在更改时运行操作
有时,您希望一个任务仅在机器上发生更改时运行。例如,如果某个任务更新了服务的配置,您可能希望重新启动该服务,但如果配置未更改,则不希望重新启动。Ansible 使用 Handler 来解决此用例。Handler 是仅在收到通知时才运行的任务。
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 服务器。
通知 Handler
任务可以使用 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 都将被重新启动,如果没有文件更改,则两者都不会重新启动。
命名 Handler
Handler 必须被命名,以便任务能够使用 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。
控制 Handler 何时运行
默认情况下,Handler 在特定 Play 中的所有任务完成后运行。在以下每个部分之后,按以下顺序自动执行通知的 Handler:pre_tasks
、roles
/tasks
和 post_tasks
。这种方法是有效的,因为 Handler 只运行一次,而不管有多少个任务通知它。例如,如果多个任务更新配置文件并通知 Handler 重新启动 Apache,则 Ansible 只会重启 Apache 一次,以避免不必要的重启。
如果您需要在 Play 结束之前运行 Handler,请添加一个任务来使用 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
的更多信息,请参阅 定义“已更改”。
在 Handler 中使用变量
您可能希望 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
主题不能包含模板。
角色中的 Handler
来自角色的 Handler 不仅仅包含在其角色中,而是与来自 Play 的所有其他 Handler 一起插入到全局范围内。因此,它们可以在定义它们的作用域之外使用。这也意味着它们的名称可能与来自角色外部的 Handler 冲突。为了确保通知来自角色的 Handler,而不是来自外部的具有相同名称的 Handler,请使用以下形式通知 Handler:role_name : handler_name
。
在 roles
部分中通知的 Handler 会在 tasks
部分的末尾自动刷新,但在任何 tasks
Handler 之前刷新。
Handler 中的包含和导入
将动态包含(如 include_task
)作为 Handler 通知会导致执行包含中的所有任务。无法通知动态包含内部定义的 Handler。
将静态包含(如 import_task
)作为 Handler 会导致该 Handler 在 Play 执行之前被该导入内的 Handler 有效地重写。静态包含本身无法被通知;另一方面,可以单独通知该包含中的任务。
作为 Handler 的 Meta 任务
自 Ansible 2.14 版本起,允许将元任务(meta tasks)用作处理器(handlers)并进行通知。但请注意,flush_handlers
不能用作处理器,以防止出现意外行为。
限制
处理器不能运行 import_role
或 include_role
。处理器忽略标签(tags)。