Ansible最佳实践之使用CallBack插件分析Playbook执行性能

一个好的剧本,执行起来会很是丝滑,良好的执行体验让你甚至感觉不到执行了很久,哈…。——–山河已无恙

写在前面


  • 和小伙伴们分享一些分析Ansible回调插件的笔记
  • 一个好的剧本,执行起来会很是丝滑,良好的执行体验让你甚至感觉不到执行了很久,哈…
  • Ansible提供了CallBack插件来处理playbook中的回调事件。我们可以通过回调插件分析剧本资源利用率、消耗时间,从而优化剧本。
  • 博文涉及内容:
    • 查看Callback插件以及插件说明
    • 分析控制节点执行剧本CPU和内存的消耗
    • 统计任务和角色剧本的执行时间
    • 自定义一个callBack插件实现执行完剧本打开博客
  • 食用方式
    • 了解 Ansible 基础知识
  • 理解不足小伙伴帮忙指正

一个好的剧本,执行起来会很是丝滑,良好的执行体验让你甚至感觉不到执行了很久,哈…。——–山河已无恙


对这方面感兴趣的小伙伴可以到官网看下:https://docs.ansible.com/ansible/2.8/plugins/callback.html

什么是Ansible Callback插件

关于回调插件,官网文档中这样讲,Ansible的回调插件可以在响应事件时向 Ansible 添加新行为。默认情况下,回调插件控制在运行命令行程序时看到的大部分输出,但也可用于添加额外的输出、与其他工具集成以及将事件编组到存储后端。如有必要,也可以创建自定义回调插件

开发的方式理解,

  • 从细粒度编码角度理解,可以理解为钩子,回调函数,类比的话,类似后端JVM中的钩子进程,在JVM进程结束时运行的进程。处理一些资源释放。前端VUE的8个生命周期方法,从指令编译到数据加载、模板渲染,DOM挂载等不同时期都会触发对应的回调函数。(Ansible 的回调也同样基于剧本生命周期方法实现)
  • 从粗粒度编程思想理解,类似面向切面编程(AOP),把代码的执行逻辑块之间的连接点看做是一个个切入点,把一些不重要,但是需要的东西做成切面,在必要时织入到逻辑块内。

运维的方式理解:

类似Linux开机,启动的第一个进程systemd要引导一些系统必要的启动项,当然内核版本不同,对应的启动规则不同,但是如果你配置的服务设置了开启自启,会在启动级别的target目录下建一个指向服务service文件的软连接,即服务一定是某个启动级别target的正向依赖。这里的配置开启自启的服务可以理解为我们给Ansible配置回调插件。

亦或者我们之前讲的 剧本中的任务控制指令pro_task和post_taks,K8s中的 pod hook,通过poststart和prestop,配置在pod创建和死亡的回调处理等等。

那么在Ansible中通过CallBack插件调整对各种事件的响应来扩展 Ansible。其中一些插件也会修改命令行工具(如ansible-playbook 命令)的输出,以提供额外的信息。

不只是剧本可以使用,临时命令的方式也可以使用回调。感兴趣小伙伴可以看看官网

需要说明的是Ansible附带的大多数回调默认情况下是禁用的,需要在ansible.cfg文件中列入白名单才能正常工作,通过 callback_whitelist 指令在ansible.cfg中启用这些插件,这里2.8和2.9的版本还有些区别。我们主要看下2.8的版本

ansible.cfg 的配置,下面的配置中,在插件白名单里添加了timer, profile_tasks, cgroup_perf_recap这三个回调

1
2
3
4
[ defaults]
inventory=inventory
remote_user=devops
callback_whitelist=timer, profile_tasks, cgroup_perf_recap

使用ansible-doc -t callback -l命令可以列出可用的插件

1
2
3
4
5
6
7
8
9
10
11
$ ansible-doc -t callback -l
actionable shows only items that need attention
aws_resource_actions summarizes all "resource:actions" completed
cgroup_memory_recap Profiles maximum memory usage of tasks and full execution using cgroups
cgroup_perf_recap Profiles system activity of tasks and full execution using cgroups
context_demo demo callback that adds play/task context
counter_enabled adds counters to the output items (tasks and hosts/task)
debug formatted stdout/stderr display
.....
.....
$

使用ansible-doc -t callback plugin-name查看指定插件的文档:下面是我们查看的timer这个插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ansible-doc -t callback timer
> TIMER (/usr/lib/python3.6/site-packages/ansible/plugins/callback/timer.py)

This callback just adds total play duration to the play stats.

* This module is maintained by The Ansible Community
REQUIREMENTS: whitelist in configuration

CALLBACK_TYPE: aggregate
METADATA:
status:
- preview
supported_by: community
。。。。。。。。。

可以看到,这个插件用于将总的play执行时间添加到当前play的最后输出中。来简单看一下怎么实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@foundation0 ~]# cat /usr/lib/python3.6/site-packages/ansible/plugins/callback/timer.py
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
callback: timer
callback_type: aggregate
requirements:
- whitelist in configuration
short_description: Adds time to play stats
version_added: "2.0"
description:
- This callback just adds total play duration to the play stats.
'''

from datetime import datetime

from ansible.plugins.callback import CallbackBase


class CallbackModule(CallbackBase):
"""
This callback module tells you how long your plays ran for.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'timer'
CALLBACK_NEEDS_WHITELIST = True

def __init__(self):

super(CallbackModule, self).__init__()

self.start_time = datetime.utcnow()

def days_hours_minutes_seconds(self, runtime):
minutes = (runtime.seconds // 60) % 60
r_seconds = runtime.seconds % 60
return runtime.days, runtime.seconds // 3600, minutes, r_seconds

def playbook_on_stats(self, stats):
self.v2_playbook_on_stats(stats)

def v2_playbook_on_stats(self, stats):
end_time = datetime.utcnow()
runtime = end_time - self.start_time
self._display.display("Playbook run took %s days, %s hours, %s minutes, %s seconds" % (self.days_hours_minutes_seconds(runtime)))
[root@foundation0 ~]#

逻辑很简单,我们可以看到CallbackModule继承了CallbackBase,覆盖了需要回调的方法。

1
2
3
4
5
6
7
8
$ cat /usr/lib/python3.6/site-packages/ansible/plugins/callback/__init__.py | grep -A 2 playbook_on_stats
def playbook_on_stats(self, stats):
pass

--
def v2_playbook_on_stats(self, stats):
self.playbook_on_stats(stats)

playbook_on_statsv2_playbook_on_stats,这两个方法中,后者是在play中分配对象的回调,前者用于在play结束时的回调。 看一下CallbackBase的注释

1
2
3
4
5
6
7
8
9
$ cat /usr/lib/python3.6/site-packages/ansible/plugins/callback/__init__.py  | grep -A 6  CallbackBase | tail -n 7
class CallbackBase(AnsiblePlugin):

'''
This is a base ansible callback class that does nothing. New callbacks should
use this class as a base and override any callback methods they wish to execute
custom actions.
'''
$

这是一个基本的ansible回调类,它什么都不做。新的回调使用这个类作为基类,重写他们希望执行的任何回调方法自定义操作。

如果需要编写一些自定义的回调插件,我们可以以同样的方法来尝试

下面来看看如何通过利用CallBack插件统计资源消耗执行时间来分析Playbook的执行性能。

我们编写一个剧本用于测试,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ cat tags.yaml
---
- name: tags Demo 1
hosts: servera
tags:
- play-tag-1
roles:
- role: tag_role
tags:
- role-tags
tasks:
- name: task 1 tag
shell: echo 'tags to task 1'
tags:
- task-tags-1
- name: include or import a tasks file
include_tasks:
file: tasks_file
tags:
- include-import
- block:
- name: task 1 in block
shell: echo 'task 1 in block'
- name: task 2 in block
shell: echo 'task 2 in block'
tags:
- block-tags
- name: tags Demo 2
hosts: servera
tags:
- play-tag-2
tasks:
- name: task 2 tag
shell: echo 'tags to task 2'
tags:
- task-tag-2

执行测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ansible-playbook copy_task.yaml
PLAY [Deploy the w eb content on the web servers] ******************************************************************
TASK [copy demo] ***************************************************************************************************
ok: [servera]
ok: [serverc]
ok: [serverd]
ok: [serverf]
ok: [serverb]
ok: [servere]
PLAY RECAP *********************************************************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
servere : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverf : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 1 seconds

统计Playbook执行性能(控制节点 CPU 和内存)

cgroup_perf_recap 插件可以分析playbook运行期间的控制节点性能。在playbook执行结束时,它将显示全局摘要和每个任务的摘要。这些摘要包括 CPU 和内存消耗,以及在 playbook 和 tasks 执行期间启动的进程
的最大数量。

来看下插件文档,= is mandatory修饰的变量为强制需要,所以我们还需要定义变量用于执行中那个控制组下执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ ansible-doc -t callback cgroup_perf_recap
> CGROUP_PERF_RECAP (/usr/lib/python3.6/site-packages/ansible/plugins/callback/cgroup_perf_recap.py)

This is an ansible callback plugin utilizes cgroups to profile system activity of
ansible and individual tasks, and display a recap at the end of the playbook
execution

* This module is maintained by The Ansible Community
OPTIONS (= is mandatory):

= control_group
Name of cgroups control group

set_via:
env:
- name: CGROUP_CONTROL_GROUP
ini:
- key: control_group
section: callback_cgroup_perf_recap
......

cgroup_perf_recap CallBack插件依赖于 Linux 控制组(cgroup)功能来监控和分析 ansible-playbook命令。

在 Linux 系统上,可以使用控制组来限制和监控一组进程可以消耗的资源,如内存或 CPU。若要设置这些限值,可以创建⼀个新组,设置限值,然后将进程添加到该组中。

需要安装cgcreate所在的安装包

1
2
3
4
5
6
7
8
$ sudo yum provides */cgcreate
Last metadata expiration check: 0:00:45 ago on Sun 14 Aug 2022 08:35:18 PM CST.
libcgroup-tools-0.41-19.el8.x86_64 : Command-line utility programs, services and daemons for libcgroup
Repo : rhel-8.0-for-x86_64-baseos-rpms
Matched from:
Filename : /usr/bin/cgcreate

$ yum -y install libcgroup-tools-0.41-19.el8.x86_64

使用 root 用户通过 cgcreate 命令创建专用控制组:

1
$ sudo cgcreate-a user:user-t user:user -g cpuacct,memory,pids:ansible_profile
1
2
$ sudo cgcreate -a  student:student -t student:student -g cpuacct,memory,pids:ansible_profile
[sudo] password for student:
  • -a 和 -t 选项显示可以访问和管理控制组的用户和组。
  • -g 选项指定新控制组的名称

下一步,是在ansible.cfg文件中启用插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[defaults]
inventory=inventory
remote_user=devops
roles_path=roles
gathering=explicit
forks=10
callback_whitelist = cgroup_perf_recap

[callback_cgroup_perf_recap]
control_group=ansible_profile
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False

在新控制组中运行ansible-playbook命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
$ ansible-playbook tags.yaml
PLAY [tags Demo 1] ***************************************************************************************
TASK [tag_role : tags roles] *****************************************************************************
changed: [servera]
TASK [task 1 tag] ****************************************************************************************
changed: [servera]
TASK [include or import a tasks file] *******************************************************************
included: /home/student/DO447/labs/task-execution/tasks_file for servera
TASK [task 1] ********************************************************************************************
changed: [servera]
TASK [task 2] ********************************************************************************************
changed: [servera]
TASK [task 1 in block] ***********************************************************************************
changed: [servera]
TASK [task 2 in block] ***********************************************************************************
changed: [servera]
PLAY [tags Demo 2] ***************************************************************************************
TASK [task 2 tag] ****************************************************************************************
changed: [servera]
PLAY RECAP ***********************************************************************************************
servera : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

CGROUP PERF RECAP ****************************************************************************************
Memory Execution Maximum: 17.70MB
cpu Execution Maximum: 0.00%
pids Execution Maximum: 0.00

memory:
tag_role : tags roles (52540000-fa09-85ab-8a5c-000000000010): 17.70MB
task 1 tag (52540000-fa09-85ab-8a5c-000000000012): 17.70MB
include or import a tasks file (52540000-fa09-85ab-8a5c-000000000013): 17.70MB
task 1 (52540000-fa09-85ab-8a5c-000000000033): 17.70MB
task 2 (52540000-fa09-85ab-8a5c-000000000034): 17.70MB
task 1 in block (52540000-fa09-85ab-8a5c-000000000015): 17.70MB
task 2 in block (52540000-fa09-85ab-8a5c-000000000016): 17.70MB
task 2 tag (52540000-fa09-85ab-8a5c-000000000019): 17.70MB

cpu:
tag_role : tags roles (52540000-fa09-85ab-8a5c-000000000010): 0.00%
task 1 tag (52540000-fa09-85ab-8a5c-000000000012): 0.00%
include or import a tasks file (52540000-fa09-85ab-8a5c-000000000013): 0.00%
task 1 (52540000-fa09-85ab-8a5c-000000000033): 0.00%
task 2 (52540000-fa09-85ab-8a5c-000000000034): 0.00%
task 1 in block (52540000-fa09-85ab-8a5c-000000000015): 0.00%
task 2 in block (52540000-fa09-85ab-8a5c-000000000016): 0.00%
task 2 tag (52540000-fa09-85ab-8a5c-000000000019): 0.00%

pids:
tag_role : tags roles (52540000-fa09-85ab-8a5c-000000000010): 0.00
task 1 tag (52540000-fa09-85ab-8a5c-000000000012): 0.00
include or import a tasks file (52540000-fa09-85ab-8a5c-000000000013): 0.00
task 1 (52540000-fa09-85ab-8a5c-000000000033): 0.00
task 2 (52540000-fa09-85ab-8a5c-000000000034): 0.00
task 1 in block (52540000-fa09-85ab-8a5c-000000000015): 0.00
task 2 in block (52540000-fa09-85ab-8a5c-000000000016): 0.00
task 2 tag (52540000-fa09-85ab-8a5c-000000000019): 0.00

$

统计任务和角色阶段耗时

timerprofile_tasksprofile_roles CallBack插件可⽤于确定速度较慢的任务和角色。

  • timer 插件显示playbook执行的持续时间。
  • profile_tasks 添加每个任务的开始时间,并在 playbook 执行结束时显示每个任务所用的时间,按降序排列。
  • profile_roles 在结束时显示每个角色所用的时间,按降序排列。

激活这些插件需要在ansible.cfg文件中添加或更新callback_whitelist指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[defaults]
inventory=inventory
remote_user=devops
roles_path=roles
gathering=explicit
forks=10

callback_whitelist = timer,profile_roles,profile_tasks

[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False

执行的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
$ ansible-playbook tags.yaml

PLAY [tags Demo 1] ***************************************************************************************
TASK [tag_role : tags roles] *****************************************************************************
Monday 15 August 2022 23:46:17 +0800 (0:00:00.021) 0:00:00.021 *********
Monday 15 August 2022 23:46:17 +0800 (0:00:00.021) 0:00:00.021 *********
changed: [servera]

TASK [task 1 tag] ****************************************************************************************
Monday 15 August 2022 23:46:18 +0800 (0:00:01.154) 0:00:01.176 *********
Monday 15 August 2022 23:46:18 +0800 (0:00:01.154) 0:00:01.176 *********
changed: [servera]

TASK [include or import a tasks file] *******************************************************************
Monday 15 August 2022 23:46:19 +0800 (0:00:00.341) 0:00:01.518 *********
Monday 15 August 2022 23:46:19 +0800 (0:00:00.341) 0:00:01.518 *********
included: /home/student/DO447/labs/task-execution/tasks_file for servera

TASK [task 1] ********************************************************************************************
Monday 15 August 2022 23:46:19 +0800 (0:00:00.018) 0:00:01.537 *********
Monday 15 August 2022 23:46:19 +0800 (0:00:00.019) 0:00:01.537 *********
changed: [servera]

TASK [task 2] ********************************************************************************************
Monday 15 August 2022 23:46:19 +0800 (0:00:00.333) 0:00:01.871 *********
Monday 15 August 2022 23:46:19 +0800 (0:00:00.333) 0:00:01.870 *********
changed: [servera]

TASK [task 1 in block] ***********************************************************************************
Monday 15 August 2022 23:46:19 +0800 (0:00:00.345) 0:00:02.216 *********
Monday 15 August 2022 23:46:19 +0800 (0:00:00.345) 0:00:02.216 *********
changed: [servera]

TASK [task 2 in block] ***********************************************************************************
Monday 15 August 2022 23:46:20 +0800 (0:00:00.332) 0:00:02.548 *********
Monday 15 August 2022 23:46:20 +0800 (0:00:00.332) 0:00:02.548 *********
changed: [servera]

PLAY [tags Demo 2] ***************************************************************************************

TASK [task 2 tag] ****************************************************************************************
Monday 15 August 2022 23:46:20 +0800 (0:00:00.344) 0:00:02.893 *********
Monday 15 August 2022 23:46:20 +0800 (0:00:00.344) 0:00:02.893 *********
changed: [servera]

PLAY RECAP ***********************************************************************************************
servera : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Monday 15 August 2022 23:46:20 +0800 (0:00:00.348) 0:00:03.241 *********
===============================================================================
shell ------------------------------------------------------------------- 2.05s
tag_role ---------------------------------------------------------------- 1.15s
include_tasks ----------------------------------------------------------- 0.02s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
total ------------------------------------------------------------------- 3.22s
Monday 15 August 2022 23:46:20 +0800 (0:00:00.348) 0:00:03.241 *********
===============================================================================
tag_role : tags roles ----------------------------------------------------------------------------- 1.15s
task 2 tag ---------------------------------------------------------------------------------------- 0.35s
task 2 -------------------------------------------------------------------------------------------- 0.35s
task 2 in block ----------------------------------------------------------------------------------- 0.34s
task 1 tag ---------------------------------------------------------------------------------------- 0.34s
task 1 -------------------------------------------------------------------------------------------- 0.33s
task 1 in block ----------------------------------------------------------------------------------- 0.33s
include or import a tasks file ------------------------------------------------------------------- 0.02s
Playbook run took 0 days, 0 hours, 0 minutes, 3 seconds
$

我们可以在剧本输出中看到每个任务的执行时间。对于时间长的可以调整剧本优化,关于优化方式,小伙伴可以看看我之前的文章,关于其他的插件,小伙伴可以官网看看。具体的版本不同,插件使用方式略有差异。

自定义一个callBack插件

上面的都是社区或者官方的一些插件,下面我们看看如何自己编写一个插件

这里我们做一个简单Demo,所以这个插件的的功能就是在剧本执行完,在浏览器打开我的博客,…………

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# -*- encoding: utf-8 -*-
"""
@File : disblog.py
@Time : 2022/09/01 00:09:06
@Author : Li Ruilong
@Version : 1.0
@Contact : 1224965096@qq.com
@Desc : ansible callback plugins
执行完剧本浏览器打开我的博客
"""

# here put the import lib

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

DOCUMENTATION = '''
callback: disblog
callback_type: aggregate
requirements:
- whitelist in configuration
short_description: 执行完剧本浏览器打开我的博客
version_added: "2.0"
description:
- 执行完剧本浏览器打开我的博客
'''

from datetime import datetime
import webbrowser
from ansible.plugins.callback import CallbackBase


class CallbackModule(CallbackBase):
"""
执行完剧本浏览器打开我的博客
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'disblog'
CALLBACK_NEEDS_WHITELIST = True

def __init__(self):

super(CallbackModule, self).__init__()
def playbook_on_stats(self, stats):
webbrowser.open('https://liruilong.blog.csdn.net/');

放到:/usr/lib/python3.6/site-packages/ansible/plugins/callback/ 目录下

我们可以通过命令查看插件

1
2
3
$ vim disblog.py
$ ansible-doc -t callback -l | grep disblog
disblog 执行完剧本浏览器打开我的博客
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ansible-doc -t callback disblog
> DISBLOG (/usr/lib/python3.6/site-packages/ansible/plugins/callback/disblog.py)

执行完剧本浏览器打开我的博客

* This module is maintained by The Ansible Community
REQUIREMENTS: whitelist in configuration

CALLBACK_TYPE: aggregate
METADATA:
status:
- preview
supported_by: community
$

配置文件添加插件到白名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@foundation0 resource_stat]# cat ansible.cfg
[defaults]
inventory = ./inventory
remote_user = devops
ask_pass = false

callback_whitelist = disblog

[privilege_escalation]
become = false
become_method = sudo
become_user = root
become_ask_pass = false
[root@foundation0 resource_stat]#

在剧本执行完后,直接打开了我的博客主页,当前这里需要考虑启动级别

博文参考


《Red Hat Ansible Engine 2.8 DO447》

发布于

2022-08-14

更新于

2023-06-21

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×