Python读取Ansible playbooks返回信息

一.背景及概要设计

当公司管理维护的服务器到达一定规模后,就必然借助远程自动化运维工具,而ansible是其中备选之一。Ansible基于Python开发,集合了众多运维工具(puppet、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible是借助ssh来和远程主机通讯的,不需要在远程主机上安装client/agents。因为上手容易,配置简单、功能强大、扩展性强,在生产应用中得到了广泛的应用。使用过程中,读取、解析、判断、保存Ansible playbooks 的执行返回信息是重要一坏。本文详细描述如何实现Python读取Ansible playbooks 执行返回信息,并且保存到数据库中。

Ansible playbooks 的返回信息,有相应的格式。

例如:

PLAY [play to setup web server] *****************************************************

TASK [Gathering Facts] **************************************************************
ok: [172.177.117.129]
ok: [172.177.117.130]

TASK [Installed the latest httpd version] ***********************************************
ok: [172.177.117.129]
ok: [172.177.117.130]

TASK [restart service] ***********************************************************
changed: [172.177.117.129]
changed: [172.177.117.130]

PLAY RECAP **************************************************************************
172.177.117.129 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
172.177.117.130 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

从上面的例子可以看出,返回的运行信息还是很丰富的,从中可以得出play、task的名字、每个task执行情况,以及play运行情况的概况。

即:

 When you run a playbook, Ansible returns information about connections, the name lines of all your plays and tasks, whether each task has succeeded or failed on each machine, and whether each task has made a change on each machine. At the bottom of the playbook execution, Ansible provides a summary of the nodes that were targeted and how they performed. General failures and fatal “unreachable” communication attempts are kept separate in the counts.

重点及难点:从结果中找出规律,格式化结果,怎么用正则表达式取得想要的信息。

二.表设计

通过对Ansible playbooks返回信息的分析,可以将其分成两类(或者说两部分),一是play的整体执行情况(主要信息为PLAY RECAP ),另一个是每个task的执行详情。因此,我们设计了两张表。

2.1 设计用来保存【最终执行结果】的表

ansible_play_recap

2.2 设计用来保存【各执行步骤详情】的表

ansible_task_palydetail

注意:

(1)可以根据需要,在表中增加一列ansible_cmd,用来保存执行的ansible的命令。

(2)为什么会有看着奇怪的manager_ip、clustername?因为,这份代码来自于对DB 集群的 部署 和 管理,可根据实际需要,修改取舍(即你的代码可以把他们去掉)。

三.Models设计

3.1 AnsiblePlayRecap的定义

class AnsiblePlayRecap(models.Model):
    """
    保存ansible最终执行结果的表
    """
    id = models.AutoField('自增id', primary_key=True)
    manager_ip = models.CharField('MHA Manager IP', max_length=100)
    clustername = models.CharField('HA 集群名字', max_length=200, default='')
    playname = models.CharField('Ansible剧本名称', max_length=360, default='')
    playrecap_serverip = models.CharField('受管节点', max_length=50, default='')
    playrecap_ok_qty = models.CharField('此节点成功运行的task个数', max_length=10, default='')
    playrecap_changed_qty = models.CharField('产生效果的task个数', max_length=10, default='')
    playrecap_unreachable = models.CharField('相应的远程节点是否不可达', max_length=10, default='')
    playrecap_failed_qty = models.CharField('执行失败的task个数', max_length=10, default='')
    playrecap_skipped_qty = models.CharField('跳过的task的个数', max_length=10, default='')
    playrecap_rescued_qty = models.CharField('抢救的task的个数', max_length=10, default='')
    playrecap_ignored_qty = models.CharField('忽略的task的个数', max_length=10, default='')
    create_time = models.DateTimeField('插入时间', auto_now=True)
    create_user = models.CharField('操作人', max_length=50, default='')

    class Meta:
        db_table = 'ansible_play_recap'
        verbose_name = '保存ansible最终执行结果的表'

AnsibleTaskDetail的定义

class AnsibleTaskDetail(models.Model):
    """
    保存各task执行详情的表
    """
    id = models.AutoField('自增id', primary_key=True)
    manager_ip = models.CharField('MHA Manager IP', max_length=100)
    clustername = models.CharField('HA 集群名字', max_length=200, default='')
    playname = models.CharField('Ansible剧本名称', max_length=360, default='')
    task_serverip = models.CharField('受管节点', max_length=50, default='')
    taskname = models.CharField('任务名称', max_length=360, default='')
    task_status = models.CharField('任务执行结果', max_length=50, default='')
    task_result_type = models.CharField('执行结果的错误类型', max_length=10, default='')
    task_messages = models.TextField('Task运行返回信息')
    create_time = models.DateTimeField('插入时间', auto_now=True)
    create_user = models.CharField('操作人', max_length=50, default='')

    class Meta:
        db_table = 'ansible_task_palydetail'
        verbose_name = '保存各执行步骤详情的表'

四.生成SQL脚本

由model所在的项目名称,通过运行 python manage.py生成

假如项目名称用XXXX代替

---生成脚本

python manage.py makemigrations XXXX

---显示刚才生成的SQL脚本(0006为版本序列号)

python manage.py sqlmigrate XXXX 0006

五. 主要功能代码

调用代码,需传入的参数有三个,

(1)shell_command 餐宿 -----即要执行的Ansible Playbook 命令;

(2)manager_ip参数

(3)cluster_name 参数--- 这两个命令前面已解释了,因为我们的这份代码,其功能是为了维护数据库集群的。在其他场景下,这两个参数可以去掉。

5.1 执行ansible 命令

声明关于正则的模式;连接远程ansible主机;获取ansible 执行结果;

from .ansible import ParamikoHelper
    ##paramiko 是一个用于在Python中执行远程操作的模块,支持SSH协议。它可以用于连接到远程服务器,执行命令、上传和下载文件,以及在远程服务器上执行各种操作。

    ##字符串中关于IP地址的正则表达式
    ## ^:匹配字符串的开头。((25[0-5]|2[0-4]\d|[01]?\d\d?)\.):匹配一个数字和一个点号,这个数字的取值范围是0到255。
    ## {3}:匹配前面的表达式三次。(25[0-5]|2[0-4]\d|[01]?\d\d?):   配一个数字,这个数字的取值范围是0到255。$:匹配字符串的结尾。
    ## 使用正则表达式匹配IP地址
    # 字符串是IP地址
    ip_pattern = r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$'
    ##字符串是IP地址开头的
    ipstart_pattern = r'^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)'
    ##字符串包含IP
    ipcontain_pattern = r'((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)'   
    ##字符串包含IP,并且IP地址是以': ['字符开头,以']'字符结尾
    ipcontain_pattern_plus = r'(\: \[)((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\])' 


    ansible_ip = '你的ansible server IP'
    ssh_port = 你的ssh_port
    ssh_username = '免密登录设置的账号'
    ph = ParamikoHelper(remote_ip=ansible_ip,remote_ssh_port=ssh_port,ssh_username=ssh_username)
    stdin, stdout, stderr = ph.exec_shell(shell_command)
    processor_result = stdout.readlines() #readlines()列表形式返回全文,每行作为一个字符串作为列表元素

5.2 开始逐行解析返回的结果

先判断这一行是否以Server IP开头(是的话,就是 PLAY RECAP 中的内容 ),

还要判断这一行 是否 有 以': ['字符开头,以']'字符结束的Server IP(如果是的话,很可能就是task部分的内容)

两个判断是各自独立的,相互没有关系依赖。

### 先赋值,否则有可能报错:UnboundLocalError: local variable 'XXXXX' referenced before assignment
    rplayname = ''
    rtask_result_type =''
    ###
    for pr_line in processor_result:
      logger.warning(f'{pr_line}')
      ## 判断这个字符串是不是以IP地址开头
      ip_result = re.search(ipstart_pattern, pr_line)
      ## 判断这个字符串是不是包含IP地址,并且IP以': ['字符开头,以']'字符结束  
      ip_plus_result = re.search(ipcontain_pattern_plus, pr_line) 
      ##获取playname start

5.3获取playname 和taskname

根据是否含有'PLAY ['字符、'TASK ['字符进行判断和提取。

##获取playname
      if 'PLAY [' in pr_line:
        ##使用的正则表达式'\[(.*?)\]',其中'\'为转移符,用于表示左右中括号的匹配,'?'表示非贪婪模式,这个模式会匹配最短的符合要求的字符串。
        ## [0],因正则匹配后,放回的是数组,通过[0],转换为字符串。
        rplayname = re.findall(r'\[(.*?)\]', pr_line)[0]##获取task 的名称
      elif 'TASK [' in pr_line:
        rtaskname = re.findall(r'\[(.*?)\]', pr_line)[0]

5.4 获取 paly 执行概况

即PLAY RECAP 部分内容,主要依据是这行的字符是以IP地址开头的。 

## 判断这个字符串是不是以IP地址开头
      elif ip_result: #字符串是IP地址开头的
        ## 此时pr_line的字符串格式如下:
        ## pr_line = '172.173.17.18               : ok=5    changed=2    unreachable=1    failed=0    skipped=6    rescued=7    ignored=8'
        rserverip = ip_result.group() ## 匹配的server IP
        ## print(rserverip) ##打印IP地址

        ## 正则表达式,\s+ ,将一个以多个空格或制表符为分隔符的字符串拆分成一个列表
        pr_line_lst = re.split(r"\s+", pr_line)
        ##分割后为: ['172.173.17.18', ':', 'ok=5', 'changed=2', 'unreachable=1', 'failed=0', 'skipped=6', 'rescued=7', 'ignored=8']
        for pr_arry in pr_line_lst:
          if 'ok=' in pr_arry:
            rplayrecap_ok_qty = pr_arry.split("ok=")[1] ##记得:字符串切割后返回的是数组,所以取第二个元素if 'changed=' in pr_arry:
            rplayrecap_changed_qty = pr_arry.split("changed=")[1]if 'unreachable=' in pr_arry:
            rplayrecap_unreachable = pr_arry.split("unreachable=")[1]if 'failed=' in pr_arry:
            rplayrecap_failed_qty = pr_arry.split("failed=")[1]if 'skipped=' in pr_arry:
            rplayrecap_skipped_qty = pr_arry.split("skipped=")[1]if 'rescued=' in pr_arry:
            rplayrecap_rescued_qty = pr_arry.split("rescued=")[1]if 'ignored=' in pr_arry:
            rplayrecap_ignored_qty = pr_arry.split("ignored=")[1]

5.5 将paly 概况数据插入表中

Django 框架,关于Model数据的写入。

### 开始向表[ansible_play_recap]中插入数据,保存ansible最终执行结果的表
        AnsiblePlayRecap.objects.create(manager_ip=manager_ip,clustername=cluster_name,playname=rplayname,playrecap_serverip=rserverip,
                                                    playrecap_ok_qty=rplayrecap_ok_qty,playrecap_changed_qty=rplayrecap_changed_qty,
                                                    playrecap_unreachable=rplayrecap_unreachable,playrecap_failed_qty=rplayrecap_failed_qty,
                                                    playrecap_skipped_qty=rplayrecap_skipped_qty,playrecap_rescued_qty=rplayrecap_rescued_qty,
                                                    playrecap_ignored_qty=rplayrecap_ignored_qty, create_user='Archery System'
                                                    )

5.6 获取task执行情况,并将数据保存到表中

如果这一行数据包含Server IP地址,并且这个 IP以': ['字符开头,以']'字符结尾的,那么这行记录的就是这个task在某受管节点的执行情况。

## 判断这个字符串是不是包含IP地址,并且IP以': ['字符开头,以']'字符结尾
      elif ip_plus_result: ##字符串包含IP,并且IP地址是以': ['字符开头,以']'字符结尾
        if 'ok: [' in pr_line:
          rtask_status = 'ok'
          rtask_messages = '' ## 赋值空
          ##查找server IP
          result = re.search(ipcontain_pattern, pr_line)
          rserverip = result.group() ## 匹配的server IP  
          ## print(rserverip)

        elif 'changed: [' in pr_line:
          rtask_status = 'changed'
          rtask_messages = '' ## 赋值空##查找server IP
          result = re.search(ipcontain_pattern, pr_line)
          rserverip = result.group() ## 匹配的server IP## 有些 返回的change 中还有其他信息,例如:changed: [192.168.168.192] => (item=perl-Parallel-ForkManager-1.18-2.el7.noarch.rpm)
          ## 此时判断下,是否包含 '] =>',如果包含,赋值给  
          if '] => ' in pr_line:
            rtask_messages= pr_line.split("] => ")[1]elif 'skipping: [' in pr_line:
          rtask_status = 'skipping'
          rtask_messages = '' ## 赋值空
          ##查找server IP
          result = re.search(ipcontain_pattern, pr_line)
          rserverip = result.group() ## 匹配的server IPelif 'fatal: [' in pr_line:
          rtask_status = 'fatal'
          rtask_messages = '' ## 赋值空
          rtask_result_type ='FAILED'##查找server IP
          result = re.search(ipcontain_pattern, pr_line)
          rserverip = result.group() ## 匹配的server IPif 'FAILED! =>' in pr_line:
            rtask_messages= pr_line.split("FAILED! =>")[1]else:
          rtask_status = 'NA'
          rtask_messages = '未知状态,请DBAcheck......' + pr_line
        ### 开始向表中插入数据
        AnsibleTaskDetail.objects.create(manager_ip=manager_ip,clustername=cluster_name,playname=rplayname,playrecap_serverip=rserverip,
                                                    taskname=rtaskname,task_status=rtask_status,
                                                    task_result_type=rtask_result_type,task_messages=rtask_messages,
                                                    create_user='Archery System'
                                                    )

5.7 去除干扰项和无效项

      elif len(pr_line) == 0 or pr_line == '\n' or ('PLAY RECAP *******' in  pr_line): ###判断是否空 或只是 简单的换行符,再或者包含指定字符
           print("这一行为空行 或 说明行,无需记录!")

5.8 补充有效项

当执行task返回OK时,,后面跟个IP,再后面一般不跟啥了;但是有时候还会由跟东西的。啥时候跟呢? 例如:task #debug: # msg: "你想要的返回信息。。。。。。" 这类命令时。

else:
        rtask_status = 'Mostly OK'
        rtask_result_type = 'debug+msg'   ##'经常出现在task中有debug:msg:的时候'
        rtask_messages =  pr_line
        ### 开始向表中插入数据
        AnsibleTaskDetail.objects.create(manager_ip=manager_ip,clustername=cluster_name,playname=rplayname,playrecap_serverip=rserverip,
                                                    taskname=rtaskname,task_status=rtask_status,
                                                    task_result_type=rtask_result_type,task_messages=rtask_messages,
                                                    create_user='Archery System'
                                                    )

        ###这段处理的情形不好想像,比较难懂,举个例子
        ## ok: [192.168.168.192] =>         ##  {
        ##     "msg": "MySQL Replication Health is OK!"
        ## }
        ##需要注意的时,相应的在表中也会保留多行数据。因为我们时逐行获取,逐行解析,逐行报错的。不过庆幸的时,顺序都是对的。

六. 其他说明

6.1 必须说明的是:上面的Python代码针对的是ansible host 文件保存的是Server IP,如果是域名,那么关于IP的正则是不可用的,代码必须调整。

6.2  补充几个task的返回信息的示例,方便理解代码。

示例 1ok: [192.168.168.192] => {\n', '    "msg": "MySQL Replication Health is OK!"\n', '}\n
示例 2
changed: [192.168.168.192] => (item=perl-Parallel-ForkManager-1.18-2.el7.noarch.rpm)示例 3fatal: [192.168.168.192]: FAILED! => {"changed": false, "msg": "No package matching "test" found available, installed or updated", "rc": 126, "results": ["No package matching "test" found available, installed or updated']}""" 
示例 4
skipping: [192.168.168.192]示例 5changed: [192.168.168.192]示例 6 ok: [192.168.168.192]

 6.3 Python读取Ansible playbooks返回信息只是平台的一个小功能,整个系统平台采用的是Django框架。

文章转载自:东山絮柳仔

原文链接:https://www.cnblogs.com/xuliuzai/p/17850437.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/194065.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用opencv的matchTemplate进行银行卡卡号识别

![字体文件](https://img-blog.csdnimg.cn/3a16c87cf4d34aceb0778c4b20ddadb2.png#pic_center import cv2 import numpy as npdef show_img(img, name"temp"):img cv2.resize(img, (0, 0), fx3, fy3)cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()de…

242. 有效的字母异位词

这篇文章会收录到 :算法通关村第十二关-白银挑战字符串经典题目-CSDN博客 242. 有效的字母异位词 描述 : 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t …

VsCode 调试 MySQL 源码

1. 启动 MySQL 2. 查看 MySQL 进程号 [root ~]# ps -ef | grep mysqld root 21479 1 0 Nov01 ? 00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir/usr/local/mysql/data --pid-file/usr/local/mysql/data/mysqld.pid root 26622 21479 0 …

【JDK21】详解虚拟线程

目录 1.概述 2.虚拟线程是为了解决哪些问题 2.1.线程切换的巨大代价 2.2.哪些情况会造成线程的切换 2.3.线程资源是有限的 3.虚拟线程 4.适用场景 1.概述 你发任你发,我用JAVA8?JDK21可能要对这句话say no了。 现在Oracle JDK是每4个版本&#x…

minio分布式存储系统

目录 拉取docker镜像 minio所需要的依赖 文件存放的位置 手动上传文件到minio中 工具类上传 yml配置 config类 service类 启动类 测试类 图片 视频 删除minio服务器的文件 下载minio服务器的文件 拉取docker镜像 拉取稳定版本:docker pull minio/minio:RELEASE.20…

FLASK博客系列6——数据库之谜

我们上一篇已经实现了简易博客界面,你还记得我们的博客数据是自己手动写的吗?但实际应用中,我们是不可能这样做的。大部分程序都需要保存数据,所以不可避免要使用数据库。我们这里为了简单方便快捷,使用了超级经典的SQ…

MySOL常见四种连接查询

1、内联接 &#xff08;典型的联接运算&#xff0c;使用像 或 <> 之类的比较运算符&#xff09;。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如&#xff0c;检索 students和courses表中学生标识号相同的所有行。 2、…

linux下的工具---vim

一、了解vim 1、vim是linux的开发工具 2、vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;而且还有一些新的特性在里面。例如语法加亮&#xff0c;可视化操作不仅可以在终端运行…

前端OFD文件预览(vue案例cafe-ofd)

0、提示 下面只有vue的使用示例demo &#xff0c;官文档参考 cafe-ofd - npm 其他平台可以参考 ofd - npm 官方线上demo: ofd 1、安装包 npm install cafe-ofd --save 2、引入 import cafeOfd from cafe-ofd import cafe-ofd/package/index.css Vue.use(cafeOfd) 3、使…

计算机服务器中了mallox勒索病毒如何处理,mallox勒索病毒解密文件恢复

科技技术的发展推动了企业的生产运营&#xff0c;网络技术的不断应用&#xff0c;极大地方便了企业日常生产生活&#xff0c;但网络毕竟是一把双刃剑&#xff0c;网络安全威胁一直存在&#xff0c;近期&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机…

文件夹重命名技巧:如何整理过长且混乱的文件夹名称

当浏览计算机文件夹时&#xff0c;有时候会遇到一些过长且混乱的文件夹名称&#xff0c;给文件夹管理带来不便。倘若手动修改文件夹名称会出现错误的机率过大&#xff0c;且这样操作太耗费时间和精力。有什么方法能够避免手动修改文件夹名称&#xff0c;提升工作效率的方法呢&a…

万字详解,和你用RAG+LangChain实现chatpdf

像chatgpt这样的大语言模型(LLM)可以回答很多类型的问题,但是,如果只依赖LLM,它只知道训练过的内容,不知道你的私有数据:如公司内部没有联网的企业文档,或者在LLM训练完成后新产生的数据。(即使是最新的GPT-4 Turbo,训练的数据集也只更新到2023年4月)所以,如果我们…

leetCode 841. 钥匙和房间 图遍历 深度优先遍历+广度优先遍历 + 图解

841. 钥匙和房间 - 力扣&#xff08;LeetCode&#xff09; 有 n 个房间&#xff0c;房间按从 0 到 n - 1 编号。最初&#xff0c;除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而&#xff0c;你不能在没有获得钥匙的时候进入锁住的房间。当你进入一个房…

Android 12 打开网络ADB并禁用USB连接ADB

平台 RK3588 Android 12 Android 调试桥 (adb) Android 调试桥 (adb) 是一种功能多样的命令行工具&#xff0c;可让您与设备进行通信。adb 命令可用于执行各种设备操作&#xff0c;例如安装和调试应用。adb 提供对 Unix shell&#xff08;可用来在设备上运行各种命令&am…

保护IP地址不被窃取的几种方法

随着互联网的普及和信息技术的不断发展&#xff0c;网络安全问题日益凸显。其中&#xff0c;保护个人IP地址不被窃取成为了一个重要的问题。IP地址是我们在互联网上的身份标识&#xff0c;如果被他人获取&#xff0c;就可能导致个人隐私泄露、计算机受到攻击等一系列问题。因此…

笔记62:注意力汇聚 --- Nadaraya_Watson 核回归

本地笔记地址&#xff1a;D:\work_file\&#xff08;4&#xff09;DeepLearning_Learning\03_个人笔记\3.循环神经网络\第10章&#xff1a;动手学深度学习~注意力机制 a a a a a a a a a a a a a a a a

常见面试题-Netty中ByteBuf类

了解 Netty 中的 ByteBuf 类吗&#xff1f; 答&#xff1a; 在 Java NIO 编程中&#xff0c;Java 提供了 ByteBuffer 作为字节缓冲区类型&#xff08;缓冲区可以理解为一段内存区域&#xff09;&#xff0c;来表示一个连续的字节序列。 Netty 中并没有使用 Java 的 ByteBuff…

SpringBoot详解

一、介绍 Spring Boot 是一个基于 Spring 框架的开源框架&#xff0c;用于构建微服务和 Web 应用程序。它可以帮助开发者轻松创建独立的、基于 Spring 的应用程序&#xff0c;并在较短的时间内完成项目的开发。 二、核心 1. 约定大于配置 Spring Boot 通过自动化配置、约定优…

【C++】静态成员

静态成员就是在成员变量和成员函数前加上关键字static&#xff0c;称为静态成员。 静态成员分为&#xff1a; 静态成员变量 所有对象共享同一份数据在编译阶段分配内存类内声明&#xff0c;类外初始化 静态成员函数 所有对象共享同一个函数静态成员函数只能访问静态成员变量 …

Java制作“简易王者荣耀”小游戏

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 GameFrame 运行类 package com.sxt;import java.awt.Graphics; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; im…