关注公众号,将获取更多Python干货
背景
要批量执行Linux命令很简单,比如Ansible自动化运维工具,使用ssh协议就可以自动化管理,再如Saltstack,通过部署Saltstack,可以在成千上万台服务器批量执行命令,今天我们自己简单实现一个基于WEB界面的批量执行命令的功能,利用Python的paramiko模块的ssh协议到远程服务器进行批量命令执行.
上在一篇文件中已经简单实现了作业中心的页面,《运维平台: 作业中心前端页面设计及填坑经验》
技术简介
接口: commandexec_api
描述: 获取前端传输过来的主机,批量执行命令,并响应给前端。
模块:paramiko
描述: python写的一个模块,基于SSH协议远程连接Linux服务器,可以查看日志,批量命令,文件上传和文件下载等。
模块: threading
描述: 实现多线程执行,更加轻量化。
自定义模块:exec
方法1:exec_api
描述: 利用paramiko模块远程连接主机,并执行命令
方法2:command_Thread
描述: 使用threading模块自定义一个多线程类,使用多线程并行执行多台主机
实现过程
在同级app下面新建exec.py,定义执行命令的方法
import paramiko
from threading import Thread
#多线程执行exec_api接口
class command_Thread(Thread):
#重写父类的构造方法
def __init__(self,func,args=()):
#加载父类init
super(command_Thread, self).__init__()
#将函数和参数封装到本类中
self.func = func
self.args = args
def run(self): #start分到一个子线程函数已经就绪;当cpu执行到这个子线程的时候,自动执行run方法
#执行带有参数的函数(就是exec_api)
self.result = self.func(*self.args)
def get_result(self): #返回每个线程结果
try:
return self.result #子线程 需要设置join方法,等子线程全部执行完后,才执行主线程
except Exception as e:
return None
#执行shell命令
def exec_api(host_ip,host_port,sys_user_name,sys_user_passwd,command):
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
try:
ssh_client.connect(host_ip,host_port,sys_user_name,sys_user_passwd)
#stdout为正确输出,stderr为错误输出
stdin, stdout, stderr = ssh_client.exec_command(command)
# 输出返回信息,如果stdout输出为空,说明指令有误,输出stderr信息
msg = stdout.read().decode('utf-8')
if len(msg) == 0:
msg = stderr.read().decode('utf-8')
if len(msg) == 0:
msg = stdin.read().decode('utf-8')
ssh_client.close()
except Exception as e:
msg = str(e)
return msg
回到view.py
引入刚自定义的方法
from .exec import exec_api,command_Thread
在view.py中的commandexec_api接口实现
def commandexec_api(request):
if request.method == "POST":
host = request.POST.get("host_name")
command = request.POST.get("command")
host_name = json.loads(host) #转换为列表
msglist = []
msghost_ip = []
for h in range(len(host_name)):
if len(host_name[h]['children']):
children = host_name[h]['children'] #拿到主机名
for hh in range(len(children)):
host_info = host_name[h]['children'][hh]['title']
host = Asset_host.objects.filter(host_name=host_info).first()
sys_username = host.sys_name.sys_username
sys_passwd = host.sys_name.sys_passwd
host_ip = host.host_ip
host_port = host.host_port
#使用自定义command_Thread线程对象执行exec_api函数
exec_info = command_Thread(exec_api,args=(host_ip,host_port,sys_username,sys_passwd,command))
exec_info.start() #启动线程
exec_info.join() #一定要加join,等待子线程全部跑完在跑主线程,不然主线程比子线程跑的块,会获取不到输出日志
msglist.append(exec_info.get_result()) #执行多台机器,将每个线程执行结果添加到列表中
msghost_ip.append(host_ip)
code = 0
msg = msglist
msghost_ip = msghost_ip
res = {"code":code,"msg":msg,"msghost_ip":msghost_ip}
return JsonResponse(res)
前端代码
利用ajax和commandexec_api交互,来接收主机资产和命令
$.ajax({
url: '{% url 'host_type_tree_api' %}',
type: "GET",
async:false,
success: function (res) {
if (res.code ==0){
//树形主机树状使用tree组件
tree.render({
elem: '#treeHost'
,data: getData()
,showCheckbox: true //是否显示复选框
,id: 'treeType'
,isJump: true //是否允许点击节点时弹出新窗口跳转
,click: function(obj){
var data = obj.data; //获取当前点击的节点数据
layer.msg('状态:'+ obj.state + '<br>节点数据:' + JSON.stringify(data));
}
//oncheck复选框被点击的时候回调
,oncheck: function (obj){
checkedData = tree.getChecked('treeType') //获取选中节点中的所有数据
var oldhtml = document.getElementById("terminal");
//jquery的方法,清除该元素的所有内容
$("#terminal").empty();
oldhtml.innerHTML = "已选资产:"
$.each(checkedData,function (index,value){ //遍历选中的主机类别,其中index是指数组的下标,value指相对应的值,obj.data.children是数组
if (value.children.length > 0){ //如果为节点为空,父节点下没有子节点(主机),所以不参与子节点遍历
$.each(value.children,function (index,value){ //遍历子节点中的列表(主机)
oldhtml.append(value.title + ",")
})
}
})
}
});
//执行按钮事件
util.event('lay-demo', {
getChecked: function(othis){
var checkedData = tree.getChecked('treeType'); //获取选中节点的数据
task_info = {
'host_name': JSON.stringify(checkedData),
'command': $("#command").val()
};
$.ajax({
url: '{% url 'commandexec_api' %}',
type: "POST",
data: task_info,
dataType: "json",
async:false,
success: function (res) {
if (res.code == 0){
var oldhtml = document.getElementById("terminal")
$.each(res.msg,function (index,value){ //res.msg传回多台主机的结果,所以要用循环
var preTitle = document.createElement('pre'); //展示结果前先换行
var preExecinfo = document.createElement('pre'); //执行结果 需要用pre标签换行
preExecinfo.style.color = "green" //设置执行结果字体
preExecinfo.innerHTML = value //遍历循环的value结果赋值到preExecinfo标签
oldhtml.append(preTitle)
oldhtml.append("----------------------------" + res.msghost_ip[index] + " " + "执行结果----------------------------------")
oldhtml.append(preExecinfo) //将结果添加id为terminal的 div
})
}
}
});
}
});
}else{
layer.msg(res.msg,{icon:5})
}
},
error: function () {
layer.msg("服务器接口异常",{icon: 5})
}
});
如果有新增的结果,我们希望滚动条自动下拉。
var old_length = $('#terminal').text().length;
setInterval(function(){
var new_length = $('#terminal').text().length;
if( new_length != old_length ){
old_length = new_length;
// 设置滚动条到底部;scrollHeight文档实际高度,scrollTop滚动条滚动距离
var terminal_div = document.getElementById("terminal");
terminal_div.scrollTop = terminal_div.scrollHeight
}
},200);
效果
更多文章请扫一扫
扫描下面二维码关注公众号,获取更多学习资源