Kubernetes WebSSH终端窗口自适应Resize

追求完美不服输的我,一直在与各类问题斗争的路上痛并快乐着前端

上一篇文章Django实现WebSSH操做Kubernetes Pod最后留了个问题没有解决,那就是terminal内容窗口的大小没有办法调整,这会致使的一个问题就是浏览器上可显示内容的区域过小,当查看/编辑文件时很是不便,就像下边这样,红色可视区域并无被用到web

RESIZE_CHANNEL

前文说到kubectl exec有两个参数COLUMNSLINES能够调整tty内容窗口的大小,命令以下:json

kubectl exec -i -t $1 env COLUMNS=$COLUMNS LINES=$LINES bash
复制代码

这实际上就是将COLUMNSLINES两个环境变量传递到了容器内,因为Kubernetes stream底层也是经过kubernetes exec实现的,因此咱们在启动容器时也将这两个变量传递进去就能够了,就像这样后端

exec_command = [
    "/bin/sh",
    "-c",
    'export LINES=20; export COLUMNS=100; '
    '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']
复制代码

添加了export LINES=20; export COLUMNS=100;,能够实现改变tty的输出大小,但这有个问题就是只能在创建连接时指定一次,不能动态的更新,也就是在一次websocket会话的过程当中,若是页面大小改变了,后端输出的LINES和COLUMNS是没法随着改变的api

在解决问题的过程当中发现官方源码中有个RESIZE_CHANNEL的配置,一样能够控制窗口的大小,使用方法以下:浏览器

cont_stream = stream(api_instance.connect_get_namespaced_pod_exec,
                     name=pod_name,
                     namespace=self.namespace,
                     container=container,
                     command=exec_command,
                     stderr=True, stdin=True,
                     stdout=True, tty=True,
                     _preload_content=False
                     )

cont_stream.write_channel(4, json.dumps({"Height": int(rows), "Width": int(cols)}))
复制代码

这样咱们就能够修改stream输出的窗口大小了bash

xterm.js fit

一顿操做后,打开页面,咦?怎么页面不行,原来窗口的调整不只须要调整stream输出数据的窗口大小,前端页面也要跟着一并调整websocket

这里用到了xterm.js的另外一个组件fit,fit能够调整终端大小的colsrows适配父级元素app

首先调整terminal块的宽度和高度为整个页面可视区域的大小,要让整个可视区域为终端窗口socket

document.getElementById('terminal').style.height = window.innerHeight + 'px';
复制代码

而后引入fit组件,在term初始化以后执行fit操做

<script src="/static/plugins/xterm/xterm.js"></script>
<script src="/static/plugins/xterm/addons/fit/fit.js"></script>
<script>
  // 修改terminal的高度为body的高度
  document.getElementById('terminal').style.height = window.innerHeight + 'px';

  var term = new Terminal({cursorBlink: true});
  term.open(document.getElementById('terminal'));

  // xterm fullscreen config
  Terminal.applyAddon(fit);
  term.fit();

  console.log(term.cols, term.rows);
</script>
复制代码

fit以后就能够经过term.colsterm.rows取到xterm.js根据字体大小自动计算过的colsrows的值了,而后把这两个值传递给kubernetes,kubernetes再根据这两个值输出窗口大小,这样先后端匹配就完美了

数据传递

xterm.js能够经过以下的方法动态的将colsrows传递给后端

term.on('resize', size => {
  socket.send('resize', [size.cols, size.rows]);
})
复制代码

但当窗口由大变小时,以前输出的内容会有样式错乱,我为了方便直接在WebSocket链接创建时采用url传参的方式把colsrows两个值传递给后端,kubernetes根据这两个值来设置输出内容的窗口大小,这样作的缺点是不会随着前端页面的变化动态的去调整后端stream输出窗口的大小,不过问题不大,若是页面调整大小,刷新下页面从新创建链接就能够啦,具体实现以下

首先须要修改的就是WebSocket的url地址

前端增长term.colsterm.rows两个参数的传递

var socket = new WebSocket(
'ws://' + window.location.host + '/pod/{{ name }}/'+term.cols+'/'+term.rows);
复制代码

Routing增长两个参数的解析

re_path(r'^pod/(?P<name>\w+)/(?P<cols>\d+)/(?P<rows>\d+)$', SSHConsumer),
复制代码

Consumer解析URL将对应参数传递给Kubernetes stream

class SSHConsumer(WebsocketConsumer):
    def connect(self):
        self.name = self.scope["url_route"]["kwargs"]["name"]
        self.cols = self.scope["url_route"]["kwargs"]["cols"]
        self.rows = self.scope["url_route"]["kwargs"]["rows"]

        # kube exec
        self.stream = KubeApi().pod_exec(self.name, cols=self.cols, rows=self.rows)
        kub_stream = K8SStreamThread(self, self.stream)
        kub_stream.start()

        self.accept()
复制代码

最后Kubernetes stream接收参数并修改窗口大小

def pod_exec(self, RAND, container="", rows=24, cols=80):
        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_name,
                             namespace=self.namespace,
                             container=container,
                             command=exec_command,
                             stderr=True, stdin=True,
                             stdout=True, tty=True,
                             _preload_content=False
                             )

        cont_stream.write_channel(4, json.dumps({"Height": int(rows), "Width": int(cols)}))

        return cont_stream
复制代码

至此,每次WebSocket链接创建,先后端就会有同样的输出窗口大小,问题解决~


扫码关注公众号查看更多实用文章

相关文章推荐阅读:

相关文章
相关标签/搜索