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鏈接創建,先後端就會有同樣的輸出窗口大小,問題解決~


掃碼關注公衆號查看更多實用文章

相關文章推薦閱讀:

相關文章
相關標籤/搜索