使用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
所以我们的剧本成功地执行了任务和处理程序。

