利用kubernetes exec接口實(shí)現(xiàn)任意容器的web-terminal

      網(wǎng)友投稿 2179 2025-03-31

      一、?? Kubectl exec命令登錄指定容器

      如果你用過k8s,那么kubectl exec 命令一定不要錯(cuò)過。簡(jiǎn)單的敲上:

      kubectl exec -it pod名 -- /bin/sh

      就可以登錄到任意節(jié)點(diǎn)的指定的容器里面,效果和使用ssh登錄到一臺(tái)機(jī)器進(jìn)行操作一模一樣,非常的方便。

      那有沒有想過:

      這個(gè)功能是怎么實(shí)現(xiàn)的呢?

      能不能在Web網(wǎng)頁(yè)上面,直接擁有這個(gè)功能呢?

      利用kubernetes exec接口實(shí)現(xiàn)任意容器的web-terminal

      接下來,我們一一解讀。

      二、?? Kubectl exec實(shí)現(xiàn)

      1.???? 最底層實(shí)現(xiàn):Docker容器的exec命令

      K8s實(shí)現(xiàn)的“進(jìn)入某個(gè)容器”的功能,底層本質(zhì)是Docker容器通過exec進(jìn)入容器的擴(kuò)展。即從本機(jī)容器,擴(kuò)展為任意節(jié)點(diǎn)的容器。

      所以咱們先看看Docker怎么通過exec進(jìn)入容器的呢?

      docker exec -it 容器id /bin/sh

      通過上面的命令,就可以進(jìn)入到容內(nèi)部。 本質(zhì)是新建了一個(gè)“與目標(biāo)容器,共享namespace的”新的shell進(jìn)程。所以該shell進(jìn)程,看到的世界,就是容器內(nèi)的世界了。

      那么K8s要做的就是,跨節(jié)點(diǎn)利用Docker的這個(gè)功能。

      2.???? Kubectl到容器的超長(zhǎng)路徑

      從kubectl命令行工具,到容器內(nèi)部,這里經(jīng)過的網(wǎng)絡(luò)路徑其實(shí)是很長(zhǎng)的。如下:

      因?yàn)閑xec命令行,是實(shí)時(shí)交互的。即輸入和輸出,實(shí)時(shí)發(fā)生。

      所以K8s選擇了使用 類似Websocket 這種雙向?qū)崟r(shí)通信的協(xié)議,來傳遞輸入/輸出內(nèi)容。

      Kubectl <---(雙向?qū)崟r(shí)協(xié)議)---> Kube-Api-Server <---(雙向?qū)崟r(shí)協(xié)議)--->節(jié)點(diǎn)kubelet

      3.???? Kubectl實(shí)現(xiàn)exec代碼簡(jiǎn)析

      通過簡(jiǎn)單查詢 kubectl 的源碼:

      import "k8s.io/client-go/tools/remotecommand"

      //這里初始化了一個(gè) remote-cmd 的對(duì)象

      exec, err := remotecommand.NewSPDYExecutor(config, method, url)

      if err != nil {

      return err

      }

      //這里開始,將輸入輸出,進(jìn)行實(shí)時(shí)傳遞(Stream)

      return exec.Stream(remotecommand.StreamOptions{

      Stdin:???????????? stdin,

      Stdout:??????????? stdout,

      Stderr:??????????? stderr,

      Tty:?? ????????????tty,

      TerminalSizeQueue: terminalSizeQueue,

      })

      這里可以看到,kubectl使用了一個(gè)叫 SPDY 的協(xié)議去連K8s的API-server。 這里的SPDY協(xié)議是Google公司搞的,基本類似Websocket可以進(jìn)行雙向?qū)崟r(shí)傳輸,但是這個(gè)協(xié)議已經(jīng)被淘汰了,被HTTP2所替代。見k8s的issue:SPDY is deprecated. Switch to HTTP/2。

      (https://github.com/Kubernetes/Kubernetes/issues/7452)

      好在K8s的API-server除了支持 SPDY協(xié)議,也支持Websocket協(xié)議。

      (https://github.com/kubernetes/kubernetes/issues/89163)

      三、?? 網(wǎng)頁(yè)web-terminal直連容器

      有了前面的背景知識(shí),那么如果想在web網(wǎng)頁(yè)中,實(shí)現(xiàn)exec實(shí)時(shí)登錄到容器里面。那么可以有以下思路:

      首先,SPDY是個(gè)淘汰的協(xié)議,所以前端JS代碼可供參考的很少。而前端對(duì)Websocket的支持則很廣泛。所以咱們Web側(cè)選擇使用Websocket。

      于是,實(shí)現(xiàn)的方案有如下幾種:

      1.???? web網(wǎng)頁(yè)使用Websocket直連K8s

      這種場(chǎng)景,雖然看著最直接,但是適用場(chǎng)景反而有限。 因?yàn)闄?quán)限隔離問題,一般情況你不可能讓前端獲得最大的k8s權(quán)限,允許進(jìn)入任意容器中。

      2.???? Web網(wǎng)頁(yè)經(jīng)過一個(gè)后端,中轉(zhuǎn)至K8s

      在前端和K8s的中間,增加一個(gè)自研的Server,可以很好的控制權(quán)限隔離,封裝K8s到業(yè)務(wù)的轉(zhuǎn)換。

      那么對(duì)于自研Server來說,它就是一個(gè)類似Proxy的程序。其中,[Web<---->自研Server]這一段肯定是使用Websocket協(xié)議。但是[自研Server<---->K8s]這一段,則有2種實(shí)現(xiàn):1種是直接使用Websocket協(xié)議。第2種是使用SPDY協(xié)議(即利用 remotecommand代碼實(shí)現(xiàn))。

      下面分2種場(chǎng)景分析。

      3.???? 中轉(zhuǎn)Server通過SPDY與K8s相連

      因?yàn)閗ubectl代碼中有exec的實(shí)現(xiàn)(通過SPDY),所以中轉(zhuǎn)Server直接借鑒,也是很方便。

      這種實(shí)現(xiàn)方案,可以參考:https://github.com/jcops/k8-web-terminal

      整體Server使用GO語(yǔ)言的beego框架,簡(jiǎn)單好用。前段連接使用Websocket,后段連接使用了SPDY協(xié)議。

      不過經(jīng)過代碼分析,感覺后段連接的實(shí)現(xiàn)不如純Websocket轉(zhuǎn)發(fā)簡(jiǎn)潔。所以這里更推薦下一種實(shí)現(xiàn)方式。

      4.???? 中轉(zhuǎn)Server通過Websocket與K8S相連

      因?yàn)镾PDY協(xié)議已經(jīng)被淘汰了,所以直接使用Websocket實(shí)現(xiàn),顯得更高大上,并且代碼也更簡(jiǎn)潔。

      這里沒有找到參考實(shí)現(xiàn)的倉(cāng)庫(kù),直接貼一點(diǎn)我們自己的代碼實(shí)現(xiàn)。使用的包是:

      import " github.com/gorilla/websocket"

      后段連接主要代碼邏輯:

      // Server去連接K8s,得到websocket的連接

      ws, _, err := websocket.DefaultDialer.Dial(addr, h)

      // 與前端的websocket,進(jìn)行proxy橋接

      go k8stoweb(connFrontEnd, connBackEnd, errFrontEnd) go webtok8s(connBackEnd, connFrontEnd, errBackEnd)

      // 其中,橋接函數(shù)如下

      func replicateWebSocket (dst, src *websocket.Conn, errc chan error) { for { msgType, msg, err1 := src.ReadMessage() if err1 != nil { m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err1)) if e, ok := err1.(*websocket.CloseError); ok { if e.Code != websocket.CloseNoStatusReceived { m = websocket.FormatCloseMessage(e.Code, e.Text) } } errc <- err1 _ = dst.WriteMessage(websocket.CloseMessage, m) break } err1 = dst.WriteMessage(msgType, msg) if err1 != nil { errc <- err1 break } } }

      這種實(shí)現(xiàn),后段轉(zhuǎn)發(fā)比SPDY那個(gè)參考倉(cāng)庫(kù)更簡(jiǎn)潔,我們自己選用了此方式。

      四、?? K8s的Websocket協(xié)議,是有擴(kuò)展的!

      中轉(zhuǎn)Proxy說完,我們要好好說道一下這個(gè)前端。因?yàn)榍岸耸鞘褂肳ebsocket,經(jīng)過proxy中轉(zhuǎn),直接到達(dá)K8s。所以相當(dāng)于直接與K8s的Websocket協(xié)議互連。

      所以這里就要引出實(shí)現(xiàn)中,遇到的最大的坑。即:K8s的exec在使用Websocket協(xié)議時(shí),是有擴(kuò)展的,并且擴(kuò)展規(guī)則是K8s自己設(shè)置的規(guī)則。 我們以 用戶敲下“l(fā)s”命令到容器,然后容器list文件列表為例來說明。

      如果你直接發(fā)送”ls”內(nèi)容,那么肯定是不通的。因?yàn)镵8s根本不認(rèn)這種“輸入”。

      K8s認(rèn)為websocket的報(bào)文內(nèi)容,有“頻道”的。不然一條cmd命令行執(zhí)行后,用戶無(wú)法判斷響應(yīng)的內(nèi)容是 stdout,還是 stderr。所以k8s這么約定:

      1.???? websocket報(bào)文內(nèi)容的第一個(gè)字節(jié),用來表示“頻道”:

      第一字節(jié)值

      其余內(nèi)容含義

      0

      標(biāo)準(zhǔn)輸入

      1

      標(biāo)準(zhǔn)輸出

      2

      標(biāo)準(zhǔn)錯(cuò)誤

      3

      服務(wù)端異常信息

      4

      terminal窗口大小調(diào)整resize

      參考:https://www.cnblogs.com/a00ium/p/10905279.html

      不過文章中,調(diào)整窗口的行為,沒有提。最終是在

      https://github.com/kubernetes-ui/container-terminal/blob/ba560d4f715f405beb0a64bab8fb29a21aac2671/container-terminal.js#L152

      看到的。前端調(diào)整窗口時(shí),需要這么發(fā)送信息給K8s。

      2.???? 頻道的內(nèi)容,需要使用Base64進(jìn)行編碼!

      即要發(fā)送“l(fā)s”命令,需要向K8s發(fā)送的內(nèi)容為:

      sendMsg := "0" + base64.Encoding("ls")

      這樣發(fā)送才行,K8s才認(rèn)為是收到”ls”命令。

      收到響應(yīng),要先去掉第一個(gè)字節(jié),然后再進(jìn)行base64解碼。

      Ps:這里推薦一個(gè)websocket調(diào)試網(wǎng)站:http://coolaf.com/tool/chattest

      用來連自己的中轉(zhuǎn)Proxy,比較方便。

      3.???? 前端JS的實(shí)現(xiàn)

      因?yàn)橹虚g的“自研Server”主要是進(jìn)行中轉(zhuǎn)Proxy,所以剛才提到的K8s接口內(nèi)容中,首字節(jié)頻道,以及響應(yīng)的編碼,其實(shí)都是交由前端來處理的。

      這里可以直接參考k8s的web-ui的實(shí)現(xiàn):https://github.com/kubernetes-ui/container-terminal

      其中的container-terminal.js文件中,主要實(shí)現(xiàn)如下:

      //發(fā)送內(nèi)容

      ws.send("0" + utf8_to_b64(data));

      //接收內(nèi)容

      switch(ev.data[0]) {

      case '1':

      case '2':

      case '3':

      term.write(b64_to_utf8(data));

      break;

      }

      五、?? 總結(jié)

      到此,如果你想自己實(shí)現(xiàn)K8s的web-terminal,并且增加各種權(quán)限控制之類的業(yè)務(wù)邏輯,應(yīng)該是沒有障礙了。還有哪里需要補(bǔ)充的也歡迎交流。

      Docker Kubernetes web前端

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:如何使用excel花名冊(cè)隨機(jī)分組座位?(excel隨機(jī)排座位)
      下一篇:WPS怎么設(shè)計(jì)花朵小圖標(biāo)? wps做花朵icon標(biāo)志的技巧
      相關(guān)文章
      亚洲成人午夜电影| 亚洲av日韩av天堂影片精品| 亚洲第一中文字幕| 亚洲日韩人妻第一页| 2017亚洲男人天堂一| 亚洲人和日本人jizz| 亚洲国产精品福利片在线观看 | 亚洲AV永久无码精品一百度影院 | 亚洲一级黄色视频| 亚洲免费日韩无码系列| 亚洲精品美女久久久久99小说| 亚洲av无码成人精品区在线播放| 国产亚洲精品国产福利在线观看 | 亚洲熟妇色自偷自拍另类| 亚洲高清中文字幕综合网| 亚洲视频一区在线| 亚洲成人黄色在线| 亚洲精品国产精品国自产网站 | 亚洲熟妇少妇任你躁在线观看| 亚洲自国产拍揄拍| 亚洲码和欧洲码一码二码三码| 亚洲狠狠婷婷综合久久| 国产成人亚洲精品电影| 亚洲日韩涩涩成人午夜私人影院| 自拍偷自拍亚洲精品第1页 | 亚洲AV无码久久精品色欲| 亚洲自偷自拍另类12p| 亚洲日韩中文字幕| 亚洲妇女熟BBW| 精品亚洲视频在线| 国产成人精品日本亚洲专区 | 久久精品国产亚洲AV天海翼| 亚洲美女在线国产| 亚洲va无码手机在线电影| 精品亚洲成a人片在线观看| 波多野结衣亚洲一级| 亚洲1区2区3区精华液| 亚洲中文字幕无码爆乳av中文| 国产亚洲精品资源在线26u| 老色鬼久久亚洲AV综合| 亚洲成a人片在线观看中文!!!|