我想用它替换掉xshell、crt之类的工具

WebSSH操作物理机或虚拟机

Django实现WebSSH操作Kubernetes Pod文章发布后,有小伙伴说咖啡哥,我们现在还没有用上Kubernetes,但我想通过浏览器连接我们的物理机和虚拟机该怎么办?

这就比较简单了,既然我们已经实现了浏览器操作Kubernetes的Pod,那么想想操作Pod和物理机虚拟机有什么区别呢?

整个数据流是一点没变:用户打开浏览器--》浏览器发送websocket请求给Django建立长连接--》Django与要操作的服务器建立SSH通道,实时的将收到的用户数据发送给SSH后的主机,并将主机执行的结果数据返回给浏览器

唯一不一样的地方就是Django与要操作的服务器建立SSH通道的方式,在Kubernetes中是通过Kubernetes提供的API建立的Stream流,而操作物理机或者虚拟机的时候我们可以使用Paramiko模块来建立SSH长连接隧道,Paramiko模块建立SSH长连接通道的方法如下:

# 实例化SSHClient
ssh = paramiko.SSHClient() # 当远程服务器没有本地主机的密钥时自动添加到本地,这样不用在建立连接的时候输入yes或no进行确认
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接SSH服务器,这里以账号密码的方式进行认证,也可以用key
ssh.connect(hostname=host, port=port, username=username, password=password, timeout=8) # 打开ssh通道,建立长连接
transport = ssh.get_transport()
self.ssh_channel = transport.open_session() # 获取ssh通道,并设置term和终端大小
self.ssh_channel.get_pty(term=term, width=cols, height=rows) # 激活终端,这样就可以正常登陆了
self.ssh_channel.invoke_shell()

连接建立,可以通过如下方法给SSH通道发送数据

self.ssh_channel.send(data)

当然SSH返回的数据也可以通过如下方法持续的输出给Websocket

while not self.ssh_channel.exit_status_ready():
# SSH返回的数据需要转码为utf-8,否则json序列化会失败
data = self.ssh_channel.recv(1024).decode('utf-8','ignore')
if len(data) != 0:
message = {'flag': 'success', 'message': data}
self.websocket.send(json.dumps(message))
else:
break

有了这些信息,结合Django实现WebSSH操作Kubernetes Pod的文章,实现WebSSH浏览器操作物理机或者虚拟机就不算困难了,完整的Consumer代码如下:

import io
import json
import paramiko
from threading import Thread
from channels.generic.websocket import WebsocketConsumer
from cmdb.backends.sshargs import args class SSHBridge(object):
def __init__(self, websocket):
self.websocket = websocket def connect(self, host, port, username, authtype, password=None, pkey=None, term='xterm', cols=80, rows=24):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try:
if authtype == 2:
pkey = paramiko.RSAKey.from_private_key(io.StringIO(pkey))
ssh.connect(username=username, hostname=host, port=port, pkey=pkey, timeout=8)
else:
ssh.connect(hostname=host, port=port, username=username, password=password, timeout=8)
except Exception as e:
message = json.dumps({'flag': 'error', 'message': str(e)})
self.websocket.send(message) return False # 打开一个ssh通道并建立连接
transport = ssh.get_transport()
self.ssh_channel = transport.open_session()
self.ssh_channel.get_pty(term=term, width=cols, height=rows)
self.ssh_channel.invoke_shell() # 连接建立一次,之后交互数据不会再进入该方法
for i in range(2):
recv = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
message = json.dumps({'flag': 'success', 'message': recv})
self.websocket.send(message) def close(self):
try:
self.websocket.close()
self.ssh_channel.close()
except BaseException as e:
pass def _ws_to_ssh(self, data):
try:
self.ssh_channel.send(data)
except OSError as e:
self.close() def _ssh_to_ws(self):
try:
while not self.ssh_channel.exit_status_ready():
data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
if len(data) != 0:
message = {'flag': 'success', 'message': data}
self.websocket.send(json.dumps(message))
else:
break
except Exception as e:
message = {'flag': 'error', 'message': str(e)}
self.websocket.send(json.dumps(message)) self.close() def shell(self, data):
Thread(target=self._ws_to_ssh, args=(data,)).start()
Thread(target=self._ssh_to_ws).start() class SSHConsumer(WebsocketConsumer):
def connect(self):
self.pk = self.scope['url_route']['kwargs'].get('id')
self.query = self.scope.get('query_string')
self.user = self.scope['user'] self.accept() # ssh_connect_args为SSH连接需要的参数
ssh_connect_args = args(self.pk, self.user, self.query) self.ssh = SSHBridge(websocket=self)
self.ssh.connect(**ssh_connect_args) def disconnect(self, close_code):
self.ssh.close() def receive(self, text_data=None):
text_data = json.loads(text_data)
self.ssh.shell(data=text_data.get('data', ''))

动态调整终端窗口大小

看了Kubernetes WebSSH终端窗口自适应Resize文章,小伙伴又说了,你这只能在连接建立时确定终端窗口的大小,如果我中途调整了浏览器的大小,显示就乱了,这该怎么办?

不要着急,接下来就让我们看看怎么让终端窗口随着浏览器大小的调整而改变,上边的文章中已经说过,终端窗口的大小需要浏览器和后端返回的Terminal大小保持一致,单单调整页面窗口大小或者后端返回的Terminal窗口大小都是不行的,那么从这两个方向来说明该如何动态调整窗口的大小

首先Paramiko模块建立的SSH通道可以通过resize_pty来动态改变返回Terminal窗口的大小,使用方法如下:

def resize_pty(self, cols, rows):
self.ssh_channel.resize_pty(width=cols, height=rows)

然后Django的Channels每次接收到前端发过来的数据时,判断一下窗口是否有变化,如果有变化则调用上边的方法动态改变Terminal输出窗口的大小

我在实现时会给传过来的数据加个flag,如果flag是resize,则调用resize_pty的方法动态调整窗口大小,否则就正常调用执行命令的方法,代码如下:

def receive(self, text_data=None):
text_data = json.loads(text_data) if text_data.get('flag') == 'resize':
self.ssh.resize_pty(cols=text_data['cols'], rows=text_data['rows'])
else:
self.ssh.shell(data=text_data.get('data', ''))

后端都搞定了,那么来看看前端如何处理吧

首先有一个terminal_size的方法根据浏览器窗口大小除以每个字符所占用的大小计算出cols和rows的值,无论是xterm.js还是Paramiko都是根据这两个值来调整窗口大小的

function terminal_size() {
return {
cols: Math.floor($('#terminal').width() / 9),
rows: Math.floor($(window).height() / 17),
}
}

然后通过$(window).resize()来检测浏览器窗口的变化,一旦发生变化,则发送一个带resize标记的数据给Django,同时传递的数据还有新的cols和rows

// terminal resize
$(window).resize(function () {
let cols = terminal_size().cols;
let rows = terminal_size().rows; send_data = JSON.stringify({
'flag': 'resize',
'cols': cols,
'rows': rows
}); socket.send(send_data);
term.resize(cols, rows)
})

最后通过term.resize来调整xterm渲染的窗口的大小

这样一个完整的动态调整窗口大小的方案就完成了

演示与源码

我写了个简单的Demo来实现上边的功能,Demo写完发现还挺好用,我就扩展了一下添加了内网的物理机和虚拟机,历史原因,有些是账号密码认证,有些是密钥认证,我都给兼容了一下,最终实现的效果如上图所示

项目里边要记录主机的密码,为了安全这个密码是通过RSA加密存放在数据库的,每次使用的时候进行解密,加解密的实现,可参考这篇文章 Django开发密码管理表实例【附源码】

最后,如果你对这个简单的小玩意感兴趣,想要自己实现,却遇到了一些问题,可以通过公众号后台加我微信获取源码


相关文章推荐阅读:

Django实现WebSSH操作物理机或虚拟机的更多相关文章

  1. vmware实现物理机和虚拟机复制粘贴

    要实现物理机和虚拟机的复制粘贴需要安装VMware Tools. 1.点击菜单栏--虚拟机--安装VMware Tools. 2.打开linux终端,进入/media/VMware Tools目录. ...

  2. linux-Redhat7 windows物理机与虚拟机设置共享目录

    一                   windows物理机与虚拟机设置共享目录 1.1        WMware Workstation点击重新安装WMware Tools 此时会弹出在客户机装载 ...

  3. 物理机连接虚拟机中的数据库及Windows添加防火墙允许端口详细操作步骤

    公司项目中因为会使用到SQL server数据库,但是自己电脑无论安装2008R2或者2014版本都不成功,我想可能是和之前安装的一些Windows的软件存在冲突. 于是便单独创建了一台虚拟机,在虚拟 ...

  4. 吴裕雄--天生自然 HADOOP大数据分布式处理:CenterOS 7 多台物理机、虚拟机相互桥连接ping通,并且能够成功连接外网

    选择用于桥接模式下的虚拟交换机,并且要选择对应的有线或者无线的网卡,如果主机是插网线联网的,那就选择有线网卡,如果主机是连无线网络的就选择无线网卡.Realtek PCIe GBE Family Co ...

  5. 物理机与虚拟机IP互ping通,而互ping主机名不通

    问题描述:虚拟机信息:VMware-workstation 10安装RHEL5.8操作系统.hostname:rhel201.com IP:192.168.1.201 物理机系统:windows 7主 ...

  6. 查看linux系统是运行在物理机还是虚拟机方法

    Windows:在CMD里输入:Systeminfo | findstr /i "System Model"如果System Model:后面含有Virutal就是虚拟机,其他都是 ...

  7. 黑群晖NAS安装方法(收集)/物理机/VMware虚拟机/KVM虚拟机(转)

    群晖NAS系统的特点: 1.正版的群晖分为两部分,启动引导和系统文件,其中启动引导是一个闪盘,镶嵌在群晖的主板上,而系统文件是现成下载然后倒入的pat文件. 2.黑群晖破解的主要是启动引导,其实能兼容 ...

  8. windows,linux,esxi系统判断当前主机是物理机还是虚拟机?查询主机序列号命令

    参考网站:https://blog.csdn.net/yangzhenping/article/details/49996765 查序列号: http://www.bubuko.com/infodet ...

  9. 查看linux系统是物理机还是虚拟机

    物理机,返回机器型号 [root@laocalhost ~]# dmidecode -s system-product-name S910-X31E 虚拟机 [root@dev01-188 ~]# d ...

随机推荐

  1. 常用Form表单正则表达式

    前端常用form表单提交,校验正则表达式奉上!/** * 邮箱 * @param {*} s */ export function isEmail (s) { return /^([a-zA-Z0-9 ...

  2. JavaScript 类型 检测

    前言 ECMAScript中有5种数据类型,分别为Number,Boolean,Null,Undifined和String,以及一种复杂的数据类型Object(由名值对组成,是这门语言所有对象的基础类 ...

  3. 开发电商平台用PHP语言和JAVA语言有什么区别?哪种语言更好?

    现在很多行业都通过电子商务拓展业务,所以商城系统开发成为很多企业的刚性需求.一般有一点技术基础的客户应该知道目前商城系统开发主流语言有两个,PHP和Java.那么很多客户朋友会纠结是选择哪个语言开发好 ...

  4. docker部署运行ES

    拉取镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.2.0 //官方 注意,后面要加上需要的版本号,具体支持的镜像版本查看 ...

  5. Netty中粘包和拆包的解决方案

    粘包和拆包是TCP网络编程中不可避免的,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包和拆包 TCP是个“流”协议,所谓流,就是没有界限的一串 ...

  6. Vue躬行记(2)——指令

    Vue不仅内置了各类指令,包括条件渲染.事件处理等,还能注册自定义指令. 一.条件渲染 条件渲染的指令包括v-if.v-else.v-else-if和v-show. 1)v-if 该指令的功能和条件语 ...

  7. B-概率论-贝叶斯决策

    目录 贝叶斯决策 一.贝叶斯决策理论 二.贝叶斯公式 2.1 从条件概率公式推导贝叶斯公式 2.2 从全概率公式推导贝叶斯公式 三.贝叶斯公式应用 更新.更全的<机器学习>的更新网站,更有 ...

  8. github代码仓库提示:“We found a potential security vulnerability in one of your dependencies”

    问题描述: Github上传代码后出现这样的错误: We found a potential security vulnerability in one of your dependencies. A ...

  9. Z-buffer算法

    1.Z缓冲区(Z-Buffer)算法 1973年,犹他大学学生艾德·卡姆尔(Edwin Catmull)独 立开发出了能跟踪屏幕上每个像素深度的算法 Z-buffer Z-buffer让计算机生成复杂 ...

  10. AOP框架Dora.Interception 3.0 [2]: 实现原理

    和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...