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
| 字段名字 | 字段类型 | 默认值 | 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返回信息的更多相关文章
- python读取hdfs并返回dataframe教程
不多说,直接上代码 from hdfs import Client import pandas as pd HDFSHOST = "http://xxx:50070" FILENA ...
- 使用Python读取Mp3的标签信息
什么是ID3 MP3是音频文件最流行的格式,它的全称是 MPEG layer III.但是这种格式不支持对于音频内容的描述信息,包括歌曲名称.演唱者.专辑等等. 因此在1996年,Eric Kemp在 ...
- 利用Python读取图片exif敏感信息
众所周知,现在很多的照相机等软件,拍摄会有选项,是否包含位置信息等. 当然有的人会说,我在微信中查看图片exif信息并没有啊,这是因为你发送到微信服务器的时候,微信帮你完成了保密工作. 常见的图片中包 ...
- 使用python读取京东pdf发票信息导出到excel表格中
代码 #!/usr/bin/env python # -*- coding: utf-8 -*- """ pip install pdfminer3k pip insta ...
- python读取excel,返回dic列表
def get_xls_sheets_as_dic(pro_name, xls_name): dic_list = [] xls_path = os.path.join(BASE_PATH, &quo ...
- Python调用ansible API系列(一)获取资产信息
你想让ansible工作首先就需要设置资产信息,那么我们如何通过使用Python调取Ansible的API来获取资产信息呢? 要提前准备一个hosts文件 获取组或者主机 #!/usr/bin/env ...
- python读取excel一例-------从工资表逐行提取信息
在工作中经常要用到python操作excel,比如笔者公司中一个人事MM在发工资单的时候,需要从几百行的excel表中逐条的粘出信息,然后逐个的发送到员工的邮箱中.人事MM对此事不胜其烦,终于在某天请 ...
- python 读取机器信息
本人最近新学python ,用到关于机器的相关信息,经过一番研究,从网上查找资料,经过测试,总结了一下相关的方法. # -*- coding: UTF8 -*- import os import wi ...
- python读取excel中单元格的内容返回的5种类型
(1) 读取单个sheetname的内容. 此部分转自:https://www.cnblogs.com/xxiong1031/p/7069006.html python读取excel中单元格的内容返回 ...
- python 读取指定文件信息并拼接
python 读取指定文本并拼接成指定的格式 # -*- coding: utf-8 -*- import os def getHelloWorld(path, fileName): "&q ...
随机推荐
- NativeBuferring,一种零分配的数据类型[下篇]
上文说到Unmanaged.BufferedBinary和BufferedString是NativeBuffering支持的三个基本数据类型,其实我们也可以说NativeBuffering只支持Unm ...
- [kubernetes]集群中部署CoreDNS服务
前言 从k8s 1.11版本开始,k8s集群的dns服务由CoreDNS提供.之前已经使用二进制文件部署了一个三master三node的k8s集群,现在需要在集群内部部署DNS服务. 环境信息 IP ...
- [selenium]相对定位器
前言 Relative Locators,相对定位器,是Selenium 4引入的一个新的定位器,相对定位器根据源点元素去定位相对位置的其它元素. 相对定位方法其实是基于JavaScript的 get ...
- 2.0 Python 数据结构与类型
数据类型是编程语言中的一个重要概念,它定义了数据的类型和提供了特定的操作和方法.在 python 中,数据类型的作用是将不同类型的数据进行分类和定义,例如数字.字符串.列表.元组.集合.字典等.这些数 ...
- 《高级程序员 面试攻略 》RocketMQ 如何保证顺序性
RocketMQ 提供了一种称为顺序消息的机制来确保消息的顺序性.下面是一些关键的方法和概念: 1. 顺序消息:顺序消息是指在发送和消费过程中,消息按照特定的顺序进行处理.RocketMQ 通过将消息 ...
- 20款VS Code实用插件推荐
前言 VS Code是一个轻量级但功能强大的源代码编辑器,轻量级指的是下载下来的VS Code其实就是一个简单的编辑器,强大指的是支持多种语言的环境插件拓展,也正是因为这种支持插件式安装环境开发让VS ...
- Java将MySQL建表语句转换为SQLite的建表语句
Java将MySQL建表语句转换为SQLite的建表语句 源代码: package com.fxsen.platform.core.util; import java.util.HashMap; im ...
- CodeForces 1174D Ehab and the Expected XOR Problem
题意: 给定两个数\(n\)和\(x\),构造一个序列,设为\(a[l]\)(\(l\)不确定) \(1\).\(1\leq a[i]<2^{n}\) \(2\).序列中没有子序列异或和为\(0 ...
- linux tcpdump 使用小结(二)
转载请注明出处: TCPDump是一个功能强大的网络抓包工具,它能够在命令行界面捕获.分析和解析网络数据包.下面是TCPDump命令的使用总结,包括使用语法.常用参数说明等: 使用语法:tcpdump ...
- 文盘Rust——起手式,CLI程序
技术的学习从不会到会的过程是最有意思的,也是体会最多的.一旦熟练了,知识变成了常识,可能就失去了记录学习过程的最佳时机. 在我看来学习一门计算机语言和学习人类语言有很多共通之处.我们学习人类语言是从单 ...