运行Ansible处理程序只需一次,适用于整个playbook

7

我希望在整个playbook中仅运行一次处理程序。

我尝试在playbook文件中使用include语句,但这导致处理程序多次运行,每个play运行一次:

- name: Configure common config
  hosts: all
  become: true
  vars:
        OE: "{{ ansible_hostname[5] }}"
  roles:
    - { role: common }
  handlers:
    - include: handlers/main.yml

- name: Configure metadata config
  hosts: metadata
  become: true
  vars:
        OE: "{{ ansible_hostname[5] }}"
  roles:
    - { role: metadata }
  handlers:
    - include: handlers/main.yml

以下是handlers/main.yml的内容:
- name: restart autofs
  service:
    name: autofs.service
    state: restarted

这是一个通知处理程序的任务示例:
- name: Configure automount - /opt/local/xxx in /etc/auto.direct
  lineinfile:
     dest: /etc/auto.direct
     regexp: "^/opt/local/xxx"
     line: "/opt/local/xxx   -acdirmin=0,acdirmax=0,rdirplus,rw,hard,intr,bg,retry=2  nfs_server:/vol/xxx"
  notify: restart autofs

我该如何让playbook只针对整个playbook执行处理程序一次?

你的playbook看起来有点混乱。这个playbook的一般想法是什么,为什么要在这里包含handler?handler应该由任务通知,因此它是角色的一部分。请查看文档以了解如何使用handler:https://docs.ansible.com/ansible/playbooks_intro.html。另一种只运行特定任务一次的方法如下:https://docs.ansible.com/ansible/playbooks_delegation.html#run-once - flxPeters
不确定 wild 是什么意思。Playbook 是一系列 plays,例如这个例子中有两个 plays。我想为多个 plays 使用一个单独的 handler,并且希望它在整个 playbook 中只执行一次。希望我的意思表达清楚了。我不是只需要运行一个任务一次,而是需要只运行一个 handler 一次。 - user3155618
@techraf 在游戏中有多个任务通知处理程序。我编辑了问题并添加了其中一个任务。 - user3155618
1
@user3155618 我认为你可能想要检查一下我提出的新建议方式来实现这个。它比以前的更简单。 - techraf
4个回答

13

答案

标题中的问题的字面答案是:不行。

Playbook 是一组剧本。Playbook 没有命名空间、变量或状态。所有配置、逻辑和任务都在剧本中定义。

Handler 是一个具有不同调用计划(非顺序,但是条件性的,在播放结束时一次,或由 meta: flush_handlers 任务触发)的任务。

处理程序属于播放而不是 playbook,并且没有办法在播放之外触发它(即在 playbook 结束时)。


解决方案

可以不引用处理程序来解决问题。

您可以使用group_by模块根据每个剧本底部任务的结果创建一个即席组。

然后,您可以在 playbook 结束时定义一个单独的剧本,以重新启动属于上述即席组的目标上的服务。

参考下面的存根了解想法:

- hosts: all
  roles:
    # roles declaration
  tasks:
    - # an example task modifying Nginx configuration
      register: nginx_configuration

    # ... other tasks ...

    - name: the last task in the play
      group_by:
        key: hosts_to_restart_{{ 'nginx' if nginx_configuration is changed else '' }}

# ... other plays ...

- hosts: hosts_to_restart_nginx
  gather_facts: no
  tasks:
    - service:
        name: nginx
        state: restarted

2

可能的解决方案

使用处理程序将主机添加到内存库中。然后添加play以仅针对这些主机运行重新启动服务。 请参考以下示例:

如果任务更改,则会通知标记重启以设置事实,即主机需要重新启动服务。

第二个处理程序添加主机非常特殊,因为add_host任务即使在处理程序中也只运行一次,详见文档。但是,如果被通知,则会在处理程序顺序所暗示的标记完成后运行。 处理程序循环遍历已运行任务的主机,并检查是否需要重新启动主机服务,如果是,则添加到特殊的hosts_to_restart组。

因为事实跨播放是持久的,所以通知受影响主机的第三个处理程序清除标记

通过将处理程序移动到单独的文件并包含它们,可以隐藏很多行。

库存文件

10.1.1.[1:10]
[primary]
10.1.1.1
10.1.1.5

test.yml

---

- hosts: all
  gather_facts: no
  tasks:
      - name: Random change to notify trigger
        debug: msg="test"
        changed_when: "1|random == 1"
        notify:
            - mark to restart
            - add host
            - clear mark
  handlers:
      - name: mark to restart
        set_fact: restart_service=true
      - name: add host
        add_host:
            name: "{{item}}"
            groups: "hosts_to_restart"
        when: hostvars[item].restart_service is defined and hostvars[item].restart_service
        with_items: "{{ansible_play_batch}}"
      - name: clear mark
        set_fact: restart_service=false

- hosts: primary
  gather_facts: no
  tasks:
      - name: Change to notify trigger
        debug: msg="test"
        changed_when: true
        notify:
            - mark to restart
            - add host
            - clear mark
  handlers:
      - name: mark to restart
        set_fact: restart_service=true
      - name: add host
        add_host:
            name: "{{item}}"
            groups: "hosts_to_restart"
        when: hostvars[item].restart_service is defined and hostvars[item].restart_service
        with_items: "{{ansible_play_batch}}"
      - name: clear mark
        set_fact: restart_service=false


- hosts: hosts_to_restart
  gather_facts: no
  tasks:
      - name: Restart service
        debug: msg="Service restarted"
        changed_when: true

1
我认为你可能想要检查一下我的新建议方式,因为在旧的方式上进行修复变得非常麻烦。 - techraf

1
< p >在post_tasks中触发的处理程序将在其他所有操作之后运行。而且可以将处理程序设置为run_once: true。< /p >

0

我不清楚你的处理程序应该做什么。无论如何,关于官方文档,处理程序

在play中每个任务块结束时触发处理程序, 并且即使被多个不同的任务通知,也只会被触发一次 [...] 自Ansible 2.2以来,处理程序还可以“监听”通用主题,任务可以按如下方式通知这些主题:

因此,处理程序会在每个任务块中被通知/执行一次。 也许你可以在“all”目标主机后保留处理程序来实现目标,但这似乎不是处理程序的清洁使用方法。 .


如果处理程序仅包含在针对“所有”目标主机的play中,则当在其他plays中被通知时,它将不会运行。 - user3155618
你说得对。但是你的 "handlers/main.yml" 文件在做什么? - gile
已编辑原问题,请查看帖子中“handlers/main.yml”的内容。 - user3155618

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接