Django实现WebSSH操作Kubernetes Pod
优秀的系统都是根据反馈逐渐完善出来的
上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以看这一篇文章:Alodi:为了保密我开发了一个系统
系统上线后,SSH登陆控制台成了一个迫切的需求,Kubernetes的Dashboard控制台虽然有WebSSH的功能,但却没办法跟Alodi系统相结合,决定在Alodi中集成WebSSH的功能,先来看看最后实现的效果吧

涉及技术
- Kubernetes Stream:接收数据执行,提供实时返回数据流
- Django Channels:维持长连接,接收前端数据转给Kubernetes,同时将Kubernetes返回的数据发送给前端
- xterm.js:一个前端终端组件,用于模拟Terminal的界面显示
基本的数据流向是:用户 --> xterm.js --> django channels --> kubernetes stream,接下来看看具体的代码实现
Kubernetes Stream
Kubernetes本身提供了stream方法来实现exec的功能,返回的就是一个WebSocket可以使用的数据流,使用起来也非常方便,代码如下:
from kubernetes import client, config
from kubernetes.stream import stream
class KubeApi:
def __init__(self, namespace='alodi'):
config.load_kube_config("/ops/coffee/kubeconfig.yaml")
self.namespace = namespace
def pod_exec(self, pod, container=""):
api_instance = client.CoreV1Api()
exec_command = [
"/bin/sh",
"-c",
'TERM=xterm-256color; export TERM; [ -x /bin/bash ] '
'&& ([ -x /usr/bin/script ] '
'&& /usr/bin/script -q -c "/bin/bash" /dev/null || exec /bin/bash) '
'|| exec /bin/sh']
cont_stream = stream(api_instance.connect_get_namespaced_pod_exec,
name=pod,
namespace=self.namespace,
container=container,
command=exec_command,
stderr=True, stdin=True,
stdout=True, tty=True,
_preload_content=False
)
return cont_stream
这里的pod name可以通过list_namespaced_pod方法获取,代码如下:
def get_deployment_pod(self, RAND):
api_instance = client.CoreV1Api()
try:
r = api_instance.list_namespaced_pod(
namespace=self.namespace,
label_selector="app=%s" % RAND
)
return True, r
except Exception as e:
return False, 'Get Deployment: ' + str(e)
state, data = self.get_deployment_pod(RAND)
pod_name = data.items[0].metadata.name
list_namespaced_pod会列出namespace下所有pod的详细信息,这里传了两个参数,第一个namespace是必须的,表示我们要列出pod的namespace,第二个label_selector非必须,表示可以通过设置的标签过滤namespace下的pod,由于我们在创建的时候给每个deployment都添加了唯一的app=RAND的标签,所以这里可以过滤出来我们项目所对应的pod
一个deployment可能对应多个pod,获取到的data.items包含了所有的pod信息,为一个list列表,可根据需要取到对应pod的name
Django Channels
之前有两篇文章详细介绍过Django Channels,不了解的可以先查看:Django使用Channels实现WebSocket--上篇和Django使用Channels实现WebSocket--下篇,最重要的两部分代码如下
routing代码:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path, re_path
from medivh.consumers import SSHConsumer
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter([
re_path(r'^pod/(?P<name>\w+)', SSHConsumer),
])
),
})
正则匹配所有以pod开头的websocket连接,都交由名为SSHConsumer的Consumer处理,Consumer代码如下:
from channels.generic.websocket import WebsocketConsumer
from medivh.backends.kube import KubeApi
from threading import Thread
class K8SStreamThread(Thread):
def __init__(self, websocket, container_stream):
Thread.__init__(self)
self.websocket = websocket
self.stream = container_stream
def run(self):
while self.stream.is_open():
if self.stream.peek_stdout():
stdout = self.stream.read_stdout()
self.websocket.send(stdout)
if self.stream.peek_stderr():
stderr = self.stream.read_stderr()
self.websocket.send(stderr)
else:
self.websocket.close()
class SSHConsumer(WebsocketConsumer):
def connect(self):
self.name = self.scope["url_route"]["kwargs"]["name"]
# kube exec
self.stream = KubeApi().pod_exec(self.name)
kub_stream = K8SStreamThread(self, self.stream)
kub_stream.start()
self.accept()
def disconnect(self, close_code):
self.stream.write_stdin('exit\r')
def receive(self, text_data):
self.stream.write_stdin(text_data)
WebSSH可以看作是一个最简单的websocket长连接,每个连接建立后都是独立的,不会跟其他连接共享数据,所以这里不需要用到Group
当连接建立时通过self.scope获取到url中的name,传给Kubernetes API,同时会新起一个线程不断循环是否有新数据产生,如果有则发送给websocket
当websocket接收到数据就直接写入Kubernetes API,当websocket关闭则会发送个exit命令给Kubernetes
前端页面
前端主要用到了xterm.js,整体代码也比较简单
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Alodi | Pod Web SSH</title>
<link rel="Shortcut Icon" href="/static/img/favicon.ico">
<link href="/static/plugins/xterm/xterm.css" rel="stylesheet" type="text/css"/>
<link href="/static/plugins/xterm/addons/fullscreen/fullscreen.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="terminal"></div>
</body>
<script src="/static/plugins/xterm/xterm.js"></script>
<script src="/static/plugins/xterm/addons/fullscreen/fullscreen.js"></script>
<script>
var term = new Terminal({cursorBlink: true});
term.open(document.getElementById('terminal'));
// xterm fullscreen config
Terminal.applyAddon(fullscreen);
term.toggleFullScreen(true);
var socket = new WebSocket(
'ws://' + window.location.host + '/pod/{{ name }}');
socket.onopen = function () {
term.on('data', function (data) {
socket.send(data);
});
socket.onerror = function (event) {
console.log('error:' + e);
};
socket.onmessage = function (event) {
term.write(event.data);
};
socket.onclose = function (event) {
term.write('\n\r\x1B[1;3;31msocket is already closed.\x1B[0m');
// term.destroy();
};
};
</script>
</html>
term.open初始化一个Terminal
term.on会将输入的内容全部实时的传递给后端
xterm.js有一个fullscreen的插件,引入之后可以配置fullscreen,否则可能页面只有一部分terminal窗口
目前仍然遇到一个窗口大小无法调整的问题没有解决,初步判断是后端Kubernetes传回的数据决定的,查询了相关资料,找到kubectl命令可以通过添加COLUMNS和LINES的env来设置
#!/bin/sh
if [ "$1" = "" ]; then
echo "Usage: kshell <pod>"
exit 1
fi
COLUMNS=`tput cols`
LINES=`tput lines`
TERM=xterm
kubectl exec -i -t $1 env COLUMNS=$COLUMNS LINES=$LINES TERM=$TERM bash
但Kubernetes Python API的Stream没有找到配置的地方,如果你知道,麻烦告诉我

相关文章推荐阅读:
Django实现WebSSH操作Kubernetes Pod的更多相关文章
- Python Django撸个WebSSH操作Kubernetes Pod(下)- 终端窗口自适应Resize
追求完美不服输的我,一直在与各种问题斗争的路上痛并快乐着 上一篇文章Django实现WebSSH操作Kubernetes Pod最后留了个问题没有解决,那就是terminal内容窗口的大小没有办法调整 ...
- Python Django撸个WebSSH操作Kubernetes Pod
优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...
- Django实现WebSSH操作物理机或虚拟机
我想用它替换掉xshell.crt之类的工具 WebSSH操作物理机或虚拟机 Django实现WebSSH操作Kubernetes Pod文章发布后,有小伙伴说咖啡哥,我们现在还没有用上Kuberne ...
- Kubernetes Pod故障归类与排查方法
Pod概念 Pod是kubernetes集群中最小的部署和管理的基本单元,协同寻址,协同调度. Pod是一个或多个容器的集合,是一个或一组服务(进程)的抽象集合. Pod中可以共享网络和存储(可以简单 ...
- 解决Kubernetes Pod故障的5个简单技巧
在很多情况下,你可能会发现Kubernetes中的应用程序没有正确地部署,或者没有正常地工作.今天这篇文章就提供了如何去快速解决这类故障以及一些技巧. 在阅读了这篇文章之后,你还将深入了解Kubern ...
- Django之Model操作
Django之Model操作 本节内容 字段 字段参数 元信息 多表关系及参数 ORM操作 1. 字段 字段列表 AutoField(Field) - int自增列,必须填入参数 primary_ke ...
- Python之路【第二十二篇】:Django之Model操作
Django之Model操作 一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...
- 调用Kubernetes API操作Kubernetes
准备工作 首先要准备一个1.5+版本的Kubernetes,并且开放了API Server的http访问端口8080.本文使用的是1.10的版本,没有环境的可以参考我上一篇文章<在CentOS ...
- Django之ORM操作
Django之ORM操作 前言 Django框架功能齐全自带数据库操作功能,本文主要介绍Django的ORM框架 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计 ...
随机推荐
- Metasploit工具----漏洞利用模块
漏洞利用是指由渗透测试者利用一个系统.应用或者服务中的安全漏洞进行的攻击行为.流行的渗透攻击技术包括缓冲区溢出.Web应用程序攻击,以及利用配置错误等,其中包含攻击者或测试人员针对系统中的漏洞而设计的 ...
- 30 道 Vue 面试题
前言 本文以前端面试官的角度出发,对 Vue 框架中一些重要的特性.框架的原理以问题的形式进行整理汇总,意在帮助作者及读者自测下 Vue 掌握的程度. 本文章节结构以从易到难进行组织,建议读者按章节顺 ...
- Kubernetes学习之应用部署变迁
从物理单机.虚拟化(容器化)到云原生 历史 云原生 ---初期 总结
- thinkphp将上传的临时文件移动到指定目录
thinkphp将上传的临时文件移动到指定目录 新建common.php文件 <?phpuse think\facade\Env; /** 移动上传的临时文件 * * @img_dir stri ...
- Python学习-列表元组字典操作
一.列表 列表是Python的基本数据类型之一,它是以 [] 括起来的,内部成员用逗号隔开.里面可以存放各种数据类型. # 例如: list2 = ['jason', 2, (1, 3), ['war ...
- 夯实Java基础系列10:深入理解Java中的异常体系
目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...
- 要不要学习Git(分布式版本控制系统)
做技术的人,要不要学一学Git呢? 提出这个问题,是因为很多小伙伴还不会使用Git. 对于任何新一代的技术工具,它在业界普及都有一个过程,Git的阻碍是:学习成本.工具迭代的成本. SVN诞生于200 ...
- ASP.NET 服务端接收Multipart/form-data文件
在网络编程过程中需要向服务器上传文件. Multipart/form-data是上传文件的一种方式. /// <summary> /// 上传工程文件 /// </summary&g ...
- JavaScript总结(一)
一.JavaScript 简介 1.1 .什么是 JavaScript? JavaScript 的简称:JS. JavaScript 是一个脚本.(不需要经过编译器编译的语言就叫做脚本) JavaSc ...
- SpringBoot -> @Import使用
@Import 注解出自spring-context包中 package org.springframework.context.annotation; import java.lang.annota ...