本篇將說明Kubernetes exec API的運作方式,並以簡單范例進行開發在前后端上。雖然Kubernetes提供了不同資源的RESTful API來進行CRUD操作,但是部分API並非單純的回傳一個資料,有些是需要透過SPDY或WebSocket建立長連線串流,這種API以exec,attach為主,目標是對一個Pod執行指定指令,或者進入該Pod進行互動等等。
Exec API端點
首先了解一下Kubernetes exec API端點,由於Kubernetes官方文件並未提供相關資訊,因此這邊透過kubectl指令來了解API的結構:
$ cat <<EOF | kubectl create -f - |
從上述得知exec API結構大致如下圖所示:
其中API中的查詢又可細分以下資訊:
- command:將被執行的指令。若指令為
ping 8.8.8.8
,則API為command=ping&command=8.8.8.8
。類型為string
值。 - container:哪個容器將被執行指令。若Pod只有一個容器,一般會用API找出名稱塞到該參數中,若多個則選擇讓人輸入名稱。類型為
string
值。 - stdin:是否開啟標准輸入,通常由使用者決定是否開啟。類型為
bool
值。 - stdout:是否開啟標准輸出,通常是
預設開啟
。類型為bool
值。 - stderr:是否開啟標准錯誤輸出,通常是
預設開啟
。類型為bool
值。 - tty:是否分配一個終端設備(Pseudo TTY,PTY)。ㄒ為
bool
值。
協議
執行是利用SPDY與WebSocket協定進行串流溝通的API,其中SPDY在Kubernetes官方的client-go已經有實現(參考遠程命令),而kubectl正是使用SPDY,但是是SPDY目前已經規划在未來將被移棄,因此建議選擇使用WebSocket來作為串流溝通。但而無論是使用哪一個協定,都要注意請求的Header必須有Connection: Upgrade
,Upgrade: xxx
等,不然API Server會拒絕存取請求。
HTTP標頭
除了SPDY與WebSocket所需要的Headers(如升級等)外,使用者與開發者還必須提供兩個Headers來確保能夠正確授權並溝通:
- 授權:該Header是用來提供給API Server做認證請求的資訊,通常會是以
Authorization: Bearer <token>
的形式。 - 接受:指定客戶端能夠接收的內容類型,一般為
Accept: application/json
,若輸入不支持的類型將會被API以406 Not Acceptable
拒絕請求。
溝通協定
一旦符合上述所有資訊后,WebSocket(或SPDY)就能夠建立連線,並且與API服務器進行溝通。而當寫入WebSocket時,資料將被傳送到標准輸入(stdin),而WebSocket的接收將會是標准輸出(stdout)與輸出錯誤(stderr).Kubernetes API服務器簡單定義了一個協定來復用stdout與stderr。因此可以理解當WebSocket建立連線后,傳送資料時需要再緩沖的第一個字元定義為stdin(buf [0] = 0),而接收資料時要判斷stdout(buf [0] = 1)與stderr(buf [0] = 2)。其資訊如下:
碼 | 標准串流 |
---|---|
0 | 標准輸入 |
1 | 標准輸出 |
2 | 標准錯誤 |
簡單下面以發送ls
指令為例:
#傳送`ls`指令,必須buf [0]自行塞入0字元來表示stdin.buf |
最后需要注意Timeout問題,由於可能對WebSocket設置TCP Timeout,因此建議每一段時間發送一個stdin空訊息來保持連線。
透過WebSocket 實現Kubernetes Exec Terminal
一般我們在操作K8S (Kubernetes) 都是透過kubectl 命令,其實kubectl 所有操作都是呼叫K8S 提供的標准WebService API,然而有時候需要進Container Debug 的時候就需要透過exec 功能。有用過K8S Dashboard 應該也知道,管理者可以任意進入某一個Container Terminal,其實就是透過exec sh command 來實現。我一直很好奇K8S Dashboard 要如何在Web 實現這樣的功能,查一下果然不出所料有一個WebSocket API 可以使用,於是開始研究如何自己實現像是Dashboard Terminal 這樣的功能。
K8S Exec Command API WebSocket 串接
我今天的目的是要在Web做出跟K8S Dashboard Terminal一樣的功能,其中必須透過WebSocker API進行串接。這一個Exec API官方說明相當少,其實有一個Kubernetes Project,叫做container-terminal 已經有實做一樣的功能,但是我實際測試的時候並不work,因為K8S API呼叫的Token必須透過HTTP Header傳遞,然而標准的HTML5並沒有夾帶自訂Header的方法,container-terminal所使用在Query String夾帶access_token並不是標准作法。
我按下F12 光明正大偷看了K8S Dashboard 的作法,發現是透過Cookie 自行驗證WebSocket 權限,看來要透過HTML5 標准來呼叫API 就要自己實做Server 了。
開始以前,我們先看看只有一條IO特性的WebSocket如何處理exec呢?其實exec就是命令呼叫,所有的程式在Linux執行一個程序都是一樣的,程序啟動時會分配三個檔案描述子(File Descriptor),分別是StdIn (標准輸入), Std Out (標准輸出), Std Error Out (標准錯誤輸出),當我們的WebSocket成功Upgrade為TCP/IP之后,K8S API必須提供一個協定來處理這三種標准描述子的收發工作。區分方法就是在資料最前端加入一個Byte,如下:
Byte 0 : 標准輸入
Byte 1 : 標准輸出
Byte 2 : 標准錯誤輸出
有了這些資訊我們就可以開始實做了WebSocket 串接了。
透過WebSocket 串接K8S Exec API 實現Terminal
由於剛剛提到HTML5 WebSocket標准並不提供自訂的HTTP Header,因此我的想法是在Server端透過NodeJS夾帶Token來連線K8S Exec WebSocket API,因為不在Browser的NodeJS就可以自訂Request Header好通過K8S的驗證機制。然后另外啟動一個WebSocket Server提供瀏覽器進行連線,當然這里的認證機制要自己實做了,可以用Cookie/Session控制即可,這樣一來也不需要暴露API Token。總而言之就是一個左手接右手傳的機制,這里的終端機介面移植container-terminal用了xterm.js 套件,跑起來的畫面如下:
這樣一來就可以在瀏覽器操作你的Container,由於是命令模式並非VM 那種傳整個畫面的終端機,所以用起來貌似飛快,速度就跟平常使用SSH 差不多速度,滿酷炫的。
范例程式碼在GitHub 有興趣請自己玩看看啰。
https://github.com/samejack/web-k8s-exec
此外,上述提到的K8S API WebSocket 協定,其實用在Log API 也是一樣的,就可以在Web 做出logs 畫面的即時輸出,也是很方便。閃電分享結束.......下台一鞠躬~