最近想着看看ansible的api,然后写一个日常报表的生成脚本,这样就不用每天人力一个个去检查了,毕竟涉及的服务器有上百台。
Python的安装就不说了,这里使用的是python3.8版本,windows装ansible实在是麻烦,我直接在linux弄了,使用pip安装就好:
pip3 install ansible==2.10.5 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip安装后,并不会创建ansible相关命令的软连接,如果需要可以手动创建,跑一个for就可以了,如下:
for i in `ls /usr/local/python3/bin/|grep ansible`;do ln -s /usr/local/python3/bin/$i /usr/bin/$i;done
一切就绪后,我们自己创建配置文件并测试一下,经典的ping模块和Hello World:
导入类完整路径 | 功能用途 |
---|---|
from ansible.module_utils.common.collections import ImmutableDict | 用于添加选项。比如: 指定远程用户remote_user=None |
from ansible.parsing.dataloader import DataLoader | 读取 json/ymal/ini 格式的文件的数据解析器 |
from ansible.vars.manager import VariableManager | 管理主机和主机组的变量管理器 |
from ansible.inventory.manager import InventoryManager | 管理资源库的,可以指定一个 inventory 文件等 |
from ansible.playbook.play import Play | 用于执行 Ad-hoc 的类 ,需要传入相应的参数 |
from ansible.executor.task_queue_manager import TaskQueueManager | ansible 底层用到的任务队列管理器 |
ansible.plugins.callback.CallbackBase | 处理任务执行后返回的状态 |
from ansible import context | 上下文管理器,他就是用来接收 ImmutableDict 的示例对象 |
import ansible.constants as C | 用于获取 ansible 产生的临时文档。 |
from ansible.executor.playbook_executor import PlaybookExecutor | 执行 playbook 的核心类 |
from ansible.inventory.host import Group | 对 主机组 执行操作 ,可以给组添加变量等操作,扩展 |
from ansible.inventory.host import Host | 对 主机 执行操作 ,可以给主机添加变量等操作,扩展 |
借鉴网上的样例,先写了一个回调函数:
class MyPlaybookCallback(CallbackBase):
def __init__(self, *args):
super(MyPlaybookCallback, self).__init__(display=None)
self.status_ok = json.dumps({})
self.status_fail = json.dumps({})
self.status_unreachable = json.dumps({})
self.status_playbook = ""
self.status_no_hosts = False
self.host_ok = {}
self.host_failed = {}
self.host_unreachable = {}
def v2_runner_on_ok(self, result):
host = result._host.get_name()
self.runner_on_ok(host, result._result)
self.host_ok[host] = result
def v2_runner_on_failed(self, result, ignore_errors=False):
host = result._host.get_name()
self.runner_on_failed(host, result._result, ignore_errors)
self.host_failed[host] = result
def v2_runner_on_unreachable(self, result):
host = result._host.get_name()
self.runner_on_unreachable(host, result._result)
self.host_unreachable[host] = result
def v2_playbook_on_no_hosts_matched(self):
self.playbook_on_no_hosts_matched()
self.status_no_hosts = True
这个回显运行ad-hoc没有问题,但是如果运行playbook,只会显示最后一次运行任务结果,比如下面这个playbook:
- hosts: myhost
remote_user: root
gather_facts: false
tasks:
- name: 磁盘只读及坏盘巡检
shell: touch /data*
ignore_errors: true
- name: 磁盘容量巡检
shell: df -hP|awk 'NR>1 && int($5) > 80'
ignore_errors: true
- name: 内存巡检
shell: export usag=`free | awk '/-\/+/{print $3}'`;free | awk -v test="$usag" '/Mem/{printf("%.2f%"), test/$2*100}'
ignore_errors: true
- name: CPU巡检
shell: cat /proc/stat | awk '/cpu/{printf("%.2f%\n"), ($2+$4)*100/($2+$4+$5)}' | awk '{print $0}' | head -1
ignore_errors: true
打印最终结果的TASK会发现只有最后一个TASK的结果,这显然不是我想要的:
笔者代码能力一般,不知道这样的写法是否适用于项目,仅仅做个记录,根据ansible的源码可以知道这几个信息:
因此,重写回调类的时候,可以直接在v2_runner_on_ok进行编写,failed状态的可以参考编写:
def v2_runner_on_ok(self, result):
host = result._host.get_name()
self.runner_on_ok(host, result._result)
self.host_ok[host] = result
t = {
"hostname": host,
"taskname": result._task,
"result": result._result
}
self.result.append(t)
最终只需要实际的playbook执行类中获取结果并打印就好,比如:
def get_result(self):
self.result_all = {'success': {}, 'fail': {}, 'unreachable': {}}
print(self.results_callback.result)
因为需要出报表,所以对服务器ip进行了分组,包括了应用,gis等,为了能够在报表生成时自动对分组内容填写,找到一种方法可以直接打印出组名:
pbex = PlaybookExecutor(playbooks=[self.playbook_path],
inventory=self.inv_obj,
variable_manager=self.variable_manager,
loader=self.loader,
passwords=self.passwords)
pbex._tqm._stdout_callback = self.results_callback # 使用自己的回调函数
print(pbex._tqm.get_variable_manager().get_vars()['groups'])
实际使用肯定还是需要进一步处理的,这样获取的返回值如下:
那么,能不能在Callback中返回结果的时候,进行组名的获取呢,答案是肯定的,v2_playbook_on_play_start方法会在每个剧本开始的时候执行,那么只需要在这个方法去写就好:
def v2_playbook_on_play_start(self, play):
self.playbook_on_play_start(play.name)
self.playbook_path = play.name
variable_manager = play.get_variable_manager()
hostvars = variable_manager.get_vars()
print(hostvars)
这里直接打印出来看看:
完美,再从中处理处自己要的信息就好,最终的产出的数据结构如下:
首先自己根据需要使用openpyxl库写一个简单的类,主要用于创建sheet等基本操作:
# -*- coding:utf-8 -*-
#!/usr/bin/python3
import openpyxl
from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter
class ExcelMake():
def __init__(self, name):
self.name = name # excel文件的名称
def create_excel_obj(self):
self.workbook = openpyxl.Workbook() # 打开的工作簿
self.worksheet = self.workbook.active
def create_excel_sheet(self, sheet):
# 创建一个工作表
if sheet not in self.workbook.sheetnames:
self.workbook.create_sheet(sheet)
def rename_excel_sheet(self, oldname, newname):
ws = self.workbook[oldname] # 不需要用active和get_sheet_by_name
ws.title = newname
def remove_excel_sheet(self, sheet):
ws = self.workbook[sheet]
self.workbook.remove(ws) # 删除sheet页
def save_excel_file(self):
self.workbook.save(self.name)
def active_excel_sheet(self, sheet):
self.worksheet = self.workbook[sheet]
这样已经具备基本的excel操作能力了,不过产出的表格很丑很丑,挤在一起,因此增加了两个用来调整表格的方法。
首先是要自动调整列宽和行高,因为表格结构本身比较简单,而且生成数据的时候就处理了空值,所以这里不需要考虑空值的处理,设置行高相对简单点,因为没有对字号进行设置,所以每一个单元格的高度基本一致,只需要全局设置成想要的高度就可以,列宽的设置相对来说复杂一些,需要去判断每一列的最大文字长度,有意思的是,不能简单的用len来判断字符串,因为中文和英文的len都其取决于个数,所以我这里转成utf-8以后在判断长度:
def auto_set_height(self, height): # 设置行高,传入需要的行高值
row = self.worksheet.max_row
for r in range(1, row+1):
self.worksheet.row_dimensions[r].height = height
def auto_set_width(self): # 设置列宽,会根据本列内容自动设置
row = self.worksheet.max_row
for col in range(1, self.worksheet.max_column+1):
width = 0 # 初始化宽度
for i in range(1, row+1): # 遍历这一列的每个单元格
v = self.worksheet.cell(row=i, column=col).value
if '\n' in str(v):
for msg in v.split('\n'):
if len(msg.encode('utf-8')) > width:
width = len(msg.encode('utf-8'))
elif len(str(v).encode('utf-8')) > width:
width = len(str(v).encode('utf-8')) # 找到最大的width
self.worksheet.column_dimensions[get_column_letter(
col)].width = width*1.2
def auto_set_style(self):
# 对单个sheet设计样式
alignment_center = Alignment(horizontal='center', vertical='center') # 居中
for row in range(1, self.worksheet.max_row+1):
for col in range(1, self.worksheet.max_column+1):
self.worksheet.cell(row, col).alignment =alignment_center