处理程序:在更改时运行操作

有时您希望任务仅在机器上发生更改时运行。例如,您可能希望在任务更新该服务的配置时重新启动服务,但如果配置未更改,则不希望重新启动。Ansible 使用处理程序来解决这种情况。处理程序是仅在收到通知时才运行的任务。

处理程序示例

此剧本,verify-apache.yml,包含一个带有处理程序的单一剧本。

---
- 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

在此剧本示例中,Apache 服务器在剧本中的所有任务完成后由处理程序重新启动。

通知处理程序

任务可以使用 notify 关键字指示一个或多个处理程序执行。该 notify 关键字可以应用于任务并接受一个在任务更改时被通知的处理程序名称列表。或者,也可以提供包含单个处理程序名称的字符串。以下示例演示了如何通过单个任务通知多个处理程序

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

在上面的示例中,处理程序在任务更改时按以下顺序执行:Restart memcachedRestart apache。处理程序按其在 handlers 部分中定义的顺序执行,而不是按在 notify 语句中列出的顺序执行。多次通知同一个处理程序会导致处理程序仅执行一次,无论有多少任务通知它。例如,如果多个任务更新配置文件并通知处理程序重新启动 Apache,Ansible 只会重新启动 Apache 一次,以避免不必要的重新启动。

通知和循环

任务可以使用循环通知处理程序。这与变量结合使用时特别有用,可以触发多个动态通知。

请注意,如果任务整体发生更改,则会触发处理程序。当使用循环时,如果任何循环项发生更改,则会设置更改状态。也就是说,任何更改都会触发所有处理程序。

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 都将重新启动;如果没有任何文件更改,则两者都不会重新启动。

命名处理程序

处理程序必须命名,以便任务可以使用 notify 关键字通知它们。

或者,处理程序可以使用 listen 关键字。使用此处理程序关键字,处理程序可以监听主题,这些主题可以将多个处理程序分组,如下所示

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 主题会导致执行监听该主题的所有处理程序,无论这些处理程序如何命名。

此用法使触发多个处理程序变得容易得多。它还使处理程序与其名称分离,从而更容易在剧本和角色之间共享处理程序(尤其是在使用来自共享源(例如 Ansible Galaxy)的第三方角色时)。

每个处理程序都应该具有全局唯一的名称。如果定义了多个具有相同名称的处理程序,则只有最后一个加载到剧本中的处理程序可以被通知和执行,有效地遮蔽所有先前具有相同名称的处理程序。

无论处理程序在何处定义,处理程序只有一个全局范围(处理程序名称和监听主题)。这还包括在角色中定义的处理程序。

控制处理程序运行时间

默认情况下,处理程序在特定剧本中的所有任务完成后运行。已通知的处理程序在以下每个部分后按以下顺序自动执行:pre_tasksroles/taskspost_tasks。这种方法效率很高,因为无论有多少任务通知处理程序,处理程序都只运行一次。例如,如果多个任务更新配置文件并通知处理程序重新启动 Apache,Ansible 只会重新启动 Apache 一次,以避免不必要的重新启动。

如果您需要在剧本结束之前运行处理程序,请添加一个使用 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 任务会触发在剧本中该点已通知的任何处理程序。

一旦处理程序执行完成(无论是自动执行的(在每个上述部分之后)还是手动执行的(通过 flush_handlers 元任务)),它们可以在剧本的后续部分中再次被通知并运行。

定义任务更改的时间

您可以使用 changed_when 关键字控制何时通知处理程序关于任务更改的信息。

在以下示例中,每次复制配置文件时,处理程序都会重新启动服务

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 的更多信息,请参阅 定义“已更改”

在处理程序中使用变量

您可能希望 Ansible 处理程序使用变量。例如,如果服务的名称因发行版略有不同,您希望输出显示每个目标机器上重新启动服务的准确名称。避免在处理程序名称中放置变量。由于处理程序名称在早期就被模板化了,因此 Ansible 可能没有为像这样的处理程序名称提供值

handlers:
# This handler name may cause your play to fail!
- name: Restart "{{ web_service_name }}"

如果在处理程序名称中使用的变量不可用,则整个剧本都会失败。在剧本中间更改该变量 **不会** 导致新创建的处理程序。

相反,请将变量放置在处理程序的任务参数中。您可以使用 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

虽然处理程序名称可以包含模板,但 listen 主题不能。

角色中的处理程序

角色中的处理程序不仅包含在其角色中,而且会与剧本中的所有其他处理程序一起插入到全局范围内。因此,它们可以在定义它们的范围之外使用。这也意味着它们的名称可能会与角色外部的处理程序发生冲突。为了确保角色中的处理程序被通知,而不是使用相同名称的角色外部的处理程序被通知,请使用以下形式的名称通知处理程序:role_name : handler_name

roles 部分中通知的处理程序在 tasks 部分结束时(但在任何 tasks 处理程序之前)自动刷新。

处理程序中的包含和导入

通知动态包含(例如 include_task)作为处理程序会导致执行包含内的所有任务。无法通知在动态包含内定义的处理程序。

如果使用静态包含,例如 import_task 作为处理程序,则在剧本执行之前,该处理程序将被包含在该导入中的处理程序有效地重写。静态包含本身无法收到通知;另一方面,该包含中的任务可以分别收到通知。

元任务作为处理程序

从 Ansible 2.14 开始,元任务 可以用作处理程序并接收通知。但是请注意,flush_handlers 不能用作处理程序,以防止出现意外行为。

限制

处理程序无法运行 import_roleinclude_role。处理程序忽略标签