一.背景及概要设计

当公司管理维护的服务器到达一定规模后,就必然借助远程自动化运维工具,而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

字段名字 字段类型 默认值 COMMENT
id bigint(20)  NOT NULL AUTO_INCREMENT 主键
manager_ip varchar(100)  NOT NULL 管理节点
clustername varchar(200)  NOT NULL 集群名字
playname varchar(360) NOT NULL Ansible剧本名称
playrecap_serverip varchar(50) NOT NULL Ansible运行节点(受管节点)
playrecap_ok_qty varchar(10) NOT NULL 成功运行的task个数
playrecap_changed_qty varchar(10) NOT NULL 产生效果的task个数
playrecap_unreachable varchar(10) NOT NULL 相应的远程节点是否不可达
playrecap_failed_qty varchar(10) NOT NULL 执行失败的task个数【注意,不可达的情况,即未执行的情况下,失败的个数记为0,此时是为执行的】
playrecap_skipped_qty varchar(10) NOT NULL 跳过的task的个数
playrecap_rescued_qty varchar(10) NOT NULL 抢救的task的个数
playrecap_ignored_qty varchar(10) NOT NULL 忽略的task的个数
create_time datetime(6)  NOT NULL 插入时间
create_user varchar(50) NOT NULL 操作人

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

ansible_task_palydetail

字段名字 字段类型 默认值 COMMENT
id bigint(20)  NOT NULL AUTO_INCREMENT 主键
manager_ip varchar(100)  NOT NULL 管理节点
clustername varchar(200)  NOT NULL 集群名字
playname varchar(360) NOT NULL Ansible剧本名称
task_serverip varchar(50) NOT NULL Ansible运行节点(受管节点)
taskname varchar(360) NOT NULL 任务名称
task_status varchar(50) NOT NULL 任务执行结果
task_result_type varchar(10) NOT NULL 执行结果类型(错误类型)
task_messages mediumtext NOT NULL Task运行返回信息(错误信息)
create_time datetime(6)  NOT NULL 插入时间
create_user varchar(50) NOT NULL 操作人

注意:(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的返回信息的示例,方便理解代码。

示例 1
ok: [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)
示例 3
fatal: [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]
示例 5
changed: [192.168.168.192]
示例 6
ok: [192.168.168.192]

Python读取Ansible playbooks返回信息的更多相关文章

  1. python读取hdfs并返回dataframe教程

    不多说,直接上代码 from hdfs import Client import pandas as pd HDFSHOST = "http://xxx:50070" FILENA ...

  2. 使用Python读取Mp3的标签信息

    什么是ID3 MP3是音频文件最流行的格式,它的全称是 MPEG layer III.但是这种格式不支持对于音频内容的描述信息,包括歌曲名称.演唱者.专辑等等. 因此在1996年,Eric Kemp在 ...

  3. 利用Python读取图片exif敏感信息

    众所周知,现在很多的照相机等软件,拍摄会有选项,是否包含位置信息等. 当然有的人会说,我在微信中查看图片exif信息并没有啊,这是因为你发送到微信服务器的时候,微信帮你完成了保密工作. 常见的图片中包 ...

  4. 使用python读取京东pdf发票信息导出到excel表格中

    代码 #!/usr/bin/env python # -*- coding: utf-8 -*- """ pip install pdfminer3k pip insta ...

  5. python读取excel,返回dic列表

    def get_xls_sheets_as_dic(pro_name, xls_name): dic_list = [] xls_path = os.path.join(BASE_PATH, &quo ...

  6. Python调用ansible API系列(一)获取资产信息

    你想让ansible工作首先就需要设置资产信息,那么我们如何通过使用Python调取Ansible的API来获取资产信息呢? 要提前准备一个hosts文件 获取组或者主机 #!/usr/bin/env ...

  7. python读取excel一例-------从工资表逐行提取信息

    在工作中经常要用到python操作excel,比如笔者公司中一个人事MM在发工资单的时候,需要从几百行的excel表中逐条的粘出信息,然后逐个的发送到员工的邮箱中.人事MM对此事不胜其烦,终于在某天请 ...

  8. python 读取机器信息

    本人最近新学python ,用到关于机器的相关信息,经过一番研究,从网上查找资料,经过测试,总结了一下相关的方法. # -*- coding: UTF8 -*- import os import wi ...

  9. python读取excel中单元格的内容返回的5种类型

    (1) 读取单个sheetname的内容. 此部分转自:https://www.cnblogs.com/xxiong1031/p/7069006.html python读取excel中单元格的内容返回 ...

  10. python 读取指定文件信息并拼接

    python 读取指定文本并拼接成指定的格式 # -*- coding: utf-8 -*- import os def getHelloWorld(path, fileName): "&q ...

随机推荐

  1. 【转自知乎】NLP算法面试必备!史上最全!PTMs:NLP预训练模型的全面总结

    NLP算法面试必备!史上最全!PTMs:NLP预训练模型的全面总结 预训练模型(Pre-trained Models,PTMs)的出现将NLP带入了一个全新时代.2020年3月18日,邱锡鹏老师发表了 ...

  2. 部署安装maven和mvnd

    前言 maven是常见的java构建工具,优点是稳定可靠,缺点是构建太慢,maven-mvnd是maven的强化版,致力于提高构建速度,默认情况下,mvnd 使用多核CPU并行构建. 常见的java构 ...

  3. [pytest]基础

    简介 The pytest framework makes it easy to write small, readable tests, and can scale to support compl ...

  4. SpringBoot3分库分表

    标签:ShardingSphere5.分库.分表: 一.简介 分库分表的设计和实现方式,在之前的内容中总结过很多,本文基于SpringBoot3和ShardingSphere5框架实现数据分库分表的能 ...

  5. xfs文件系统核心架构介绍

    版权声明:本文为CSDN博主「瞧见风」的原创文章,遵循CC 4.0 BY-SA版权协议原文链接:https://blog.csdn.net/scaleqiao/article/details/5209 ...

  6. langchain中的LLM模型使用介绍

    简介 构建在大语言模型基础上的应用通常有两种,第一种叫做text completion,也就是一问一答的模式,输入是text,输出也是text.这种模型下应用并不会记忆之前的问题内容,每一个问题都是最 ...

  7. MIT 6.828 Lab实验记录 —— lab1 Booting PC

    实验参考信息 MIT 6.828 lab1 讲义地址 MIT 6.828 课程 Schedule MIT 6.828 lab 环境搭建参考 MIT 6.828 lab 工具guide Brennan' ...

  8. 线段树hdu-4027

    Smiling & Weeping   ---- 姑娘,倘若,我双手合十的愿望里有你呢 Problem Description A lot of battleships of evil are ...

  9. Note -「virtual tree」shorter vrt

    Part. 1 Preface 没什么 preface. Part. 2 实现 具体来说就是把所有关键点按 \(\text{dfn}\) 排序,去重,然后求出相邻结点的 \(\text{LCA}\), ...

  10. Python - 读取CSV文件发现有重复数据,如何清洗以及保存为CSV文件,这里有完整的过程!!!! 片尾有彩蛋

    语言:Python 功能: 1.清洗CSV文件中重复数据. 2.保存为CSV文件 大体流程: 1.首先观察CSV文件中的数据布局格式如何? 2.通过csv包读取数据.并根据规则使用continue,来 ...