Ansible使用CallBack插件分析Playbook执行性能

2023-01-30 15:26:58 浏览数 (2)

写在前面

  • 和小伙伴们分享一些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这三个回调

代码语言:javascript复制
[ defaults]
inventory=inventory 
remote_user=devops 
callback_whitelist=timer, profile_tasks, cgroup_perf_recap

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

代码语言:javascript复制
$ 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这个插件

代码语言:javascript复制
$ 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的最后输出中。来简单看一下怎么实现的。

代码语言:javascript复制
[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,覆盖了需要回调的方法。

代码语言:javascript复制
$ 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的注释

代码语言:javascript复制
$ 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的执行性能。

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

代码语言:javascript复制
$ 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

执行测试一下

代码语言:javascript复制
$ 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修饰的变量为强制需要,所以我们还需要定义变量用于执行中那个控制组下执行

代码语言:javascript复制
$ 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所在的安装包

代码语言:javascript复制
$ 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 命令创建专用控制组:

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

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

代码语言:javascript复制
[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命令:

代码语言:javascript复制
$ 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指令:

代码语言:javascript复制
[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

执行的输出:

代码语言:javascript复制
$ 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,所以这个插件的的功能就是在剧本执行完,在浏览器打开我的博客,…………

代码语言:javascript复制

# -*- 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/ 目录下

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

代码语言:javascript复制
$ vim disblog.py
$ ansible-doc -t callback  -l | grep disblog
disblog              执行完剧本浏览器打开我的博客
代码语言:javascript复制
$ 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
$

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

代码语言:javascript复制
[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》

0 人点赞