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

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


相关文章推荐阅读:

Django实现WebSSH操作Kubernetes Pod的更多相关文章

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

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

  2. Python Django撸个WebSSH操作Kubernetes Pod

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

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

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

  4. Kubernetes Pod故障归类与排查方法

    Pod概念 Pod是kubernetes集群中最小的部署和管理的基本单元,协同寻址,协同调度. Pod是一个或多个容器的集合,是一个或一组服务(进程)的抽象集合. Pod中可以共享网络和存储(可以简单 ...

  5. 解决Kubernetes Pod故障的5个简单技巧

    在很多情况下,你可能会发现Kubernetes中的应用程序没有正确地部署,或者没有正常地工作.今天这篇文章就提供了如何去快速解决这类故障以及一些技巧. 在阅读了这篇文章之后,你还将深入了解Kubern ...

  6. Django之Model操作

    Django之Model操作 本节内容 字段 字段参数 元信息 多表关系及参数 ORM操作 1. 字段 字段列表 AutoField(Field) - int自增列,必须填入参数 primary_ke ...

  7. Python之路【第二十二篇】:Django之Model操作

    Django之Model操作   一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...

  8. 调用Kubernetes API操作Kubernetes

    准备工作 首先要准备一个1.5+版本的Kubernetes,并且开放了API Server的http访问端口8080.本文使用的是1.10的版本,没有环境的可以参考我上一篇文章<在CentOS ...

  9. Django之ORM操作

    Django之ORM操作 前言 Django框架功能齐全自带数据库操作功能,本文主要介绍Django的ORM框架 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计 ...

随机推荐

  1. (转载)非常完善的Log4net配置详细说明

    一.前言 在项目中,对项目的日志收集是非常重要的,这里我就说说代码的异常日志收集.收集异常日志,有很多第三方成熟的框架,我这里就介绍一下我常用的Log4net. Log4Net介绍 Log4net 是 ...

  2. MySQL的安装与配置——详细教程

    免安装版的Mysql MySQL关是一种关系数据库管理系统,所使用的 SQL 语言是用于访问数据库的最常用的 标准化语言,其特点为体积小.速度快.总体拥有成本低,尤其是开放源码这一特点,在 Web 应 ...

  3. group by语法

    group by 用法解析 group by语法可以根据给定数据列的每个成员对查询结果进行分组统计,最终得到一个分组汇总表. SELECT子句中的列名必须为分组列或列函数.列函数对于GROUP BY子 ...

  4. UGUI_游戏菜单场景切换

    事件委托 GameManger(空物体)+GameManger脚本——重要的方式 public class GameManger : MonoBehaviour { public void OnSta ...

  5. 致初学者(四):HDU 2044~2050 递推专项习题解

    所谓递推,是指从已知的初始条件出发,依据某种递推关系,逐次推出所要求的各中间结果及最后结果.其中初始条件或是问题本身已经给定,或是通过对问题的分析与化简后确定.关于递推的知识可以参阅本博客中随笔“递推 ...

  6. Vue学习之todolist功能开发

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. C++基础之泛型算法

    标准库并未给每个容器添加大量功能,因此,通过大量泛型算法,来弥补.这些算法大多数独立于任何特定的容器,且是通用的,可用于不同类型的容器和不同的元素. 迭代器使得算法不依赖容器,但是算法依赖于元素的类型 ...

  8. SVN检出后文件没有图标显示

    SVN检出后文件没有图标显示   "Win + R"打开运行框,输入"regedit"打开注册表 在注册表编辑界面按"Ctrl + F"快捷 ...

  9. Scrapy项目 - 源码工程 - 实现豆瓣 Top250 电影信息爬取的爬虫设计

    一.项目目录结构 spiders文件夹内包含doubanSpider.py文件,对于项目的构建以及结构逻辑,详见环境搭建篇. 二.项目源码 1.doubanSpider.py # -*- coding ...

  10. Apache和Tomcat 配置负载均衡(mod-proxy方式)-无session共享、无粘性session

    转:https://blog.csdn.net/wangjunjun2008/article/details/38268483 mod-proxy方式实现负载均衡是利用了Apache 2.x版本自带的 ...