优秀的系统都是根据反馈逐渐完善出来的

上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套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命令可以通过添加COLUMNSLINES的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没有找到配置的地方,如果你知道,麻烦告诉我


相关文章推荐阅读:

Python Django撸个WebSSH操作Kubernetes Pod的更多相关文章

  1. Python Django撸个WebSSH操作Kubernetes Pod(下)- 终端窗口自适应Resize

    追求完美不服输的我,一直在与各种问题斗争的路上痛并快乐着 上一篇文章Django实现WebSSH操作Kubernetes Pod最后留了个问题没有解决,那就是terminal内容窗口的大小没有办法调整 ...

  2. Django实现WebSSH操作Kubernetes Pod

    优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...

  3. Python/Django(CBV/FBV/ORM操作)

    Python/Django(CBV/FBV/ORM操作) CBV:url对应的类(模式) ##====================================CBV操作============ ...

  4. [python][django学习篇][6]操作数据库

    查询(取)数据 >>> Category.objects.all() <QuerySet [<Category: Category object>]> > ...

  5. Python - Django - ORM 多对多操作

    models.py: from django.db import models # 出版社 class Publisher(models.Model): id = models.AutoField(p ...

  6. Python - Django - ORM 外键操作

    models.py: from django.db import models # 出版社 class Publisher(models.Model): id = models.AutoField(p ...

  7. Django实现WebSSH操作物理机或虚拟机

    我想用它替换掉xshell.crt之类的工具 WebSSH操作物理机或虚拟机 Django实现WebSSH操作Kubernetes Pod文章发布后,有小伙伴说咖啡哥,我们现在还没有用上Kuberne ...

  8. 基于Python+Django的Kubernetes集群管理平台

    ➠更多技术干货请戳:听云博客 时至今日,接触kubernetes也有一段时间了,而我们的大部分业务也已经稳定地运行在不同规模的kubernetes集群上,不得不说,无论是从应用部署.迭代,还是从资源调 ...

  9. 使用python+django+twistd 开发自己的操作和维护系统的一个

    许多开源操作系统和维护系统,例nagios.zabbix.cati等等,但是,当他们得到的时间自己的个性化操作和维护需求,始终无力! 最近的一项研究python.因此,我们认为python+djang ...

随机推荐

  1. 双股同时上市!小米IPO是想要玩场大的?

    ​近日,据相关媒体报道,有相关人士称小米IPO最终可能敲定A+H股两地发行.但目前还未得到小米官方证实.此前关于小米要上市的消息已经流传已久了,从雷军刚开始的矢口否认到后来的默认,再到后来相关消息愈来 ...

  2. js事件节流

    背景:在监听浏览器滚动条的scroll事件时该事件会触发很多次,这样当快速滚动时会有很差的性能,所以要限制事件触发的频率,可以防抖和节流,这里我记录简单的节流方法 <!DOCTYPE html& ...

  3. 初入 Ubuntu 的一些操作 · Lei's blog

    查看系统版本 cat /etc/os-release 修改 root 密码 passwd 新建用户 新建用户: adduser username 将新用户加入 sudo 组,这样就可以用 sudo 命 ...

  4. react render渲染的几种情况

    1. 首次加载 2. setState改变组件内部state. 注意: 此处是说通过setState方法改变. 3. 接受到新的props

  5. oppo互联网招聘-各类软件测试

    一.服务端测试专家 关键词:安全测试.白盒测试.性能测试.自动化.持续集成.服务端 岗位职责: 主导多个高日活产品的测试方案: 试点和推广自动化和持续集成: 改善测试相关流程和规范. 职位要求: 计算 ...

  6. 2016/11/10 吃吃喝喝Hacking Thursday Night聚餐活动 at Dunkin Donuts

    店名:Dunkin Donuts 唐恩都乐 点评:http://www.dianping.com/shop/21378231 地址:静安区南京西路1649号静安公园内(近静安公园) 走法:地铁2号线静 ...

  7. flume install

    flume install flume 安装 123456 [root@10 app][root@10 app]# mv apache-flume-1.7.0-bin /mnt/app/flume[r ...

  8. 7-10 jmu-python-异常-学生成绩处理基本版 (15 分)

    小明在帮老师处理数据,这些数据的第一行是n,代表有n行整数成绩需要统计.数据没有错误,则计算平均值(保留2位小数)并输出.数据有错误,直接停止处理,并且不进行计算. 注:该程序可以适当处理小错误,比如 ...

  9. win10执行Tensorflow,总是会报错“DLL load failed: 找不到指定的模块”的解决方式----终极版方式

    win10上运行tensorflow时报错,“DLL load failed: 找不到指定的模块”的解决方式 我只想说,当你们遇到这个问题的时候,以下终极版的方式出来了,非常感谢知乎 leo lv ! ...

  10. VUE实现Studio管理后台(十):OptionBox,一个综合属性输入界面,可以级联重置

    为了便于阅读代码,已经把测试数据分离出来,放在了mock目录下: 阅读代码的话,稍微留意一下就好.本次介绍RXEditor界面最重要的部分,属性输入组件,该组件可以显示是否有数据被修改,还可以批量重置 ...