1. 程式人生 > >一種新的進入容器的方式: WebSocket + Docker Remote API

一種新的進入容器的方式: WebSocket + Docker Remote API

白漸

眾所周知,容器是基於作業系統核心的一種輕量級的虛擬化技術。其可以類比於虛擬機器,但其本身並不是虛擬機器。在傳統的虛擬機器使用場景中,每個使用者都會通過堡壘機,根據自己被分配的許可權,登入某些機器的某些賬號。當應用部署逐漸轉移到基於容器技術的PaaS平臺上後,讓使用者進入容器進行觀察、除錯應用已經成為了PaaS平臺的一個重要且必備的功能。

遠端進入容器功能的傳統實現方式是基於虛擬機器的思想,在每個容器中啟動一個sshd程序。由於容器PID為1的程序的特殊性,為了保證容器不停,容器的ENTRYPOINT需要設定為類似於Supervisord這樣的程序管理程式。在這種多程序容器的使用場景中,使用者通過ssh-client指定容器的IP遠端連線到容器,讓使用者感覺到自己好像就在使用虛擬機器。但是,這種方案會帶來以下問題:

  1. 許可權管理。如何控制哪些使用者能夠登入哪些容器?如何和平臺已有的許可權管理系統整合?這種情況往往都需要通過堡壘機系統控制。而在PaaS中,引入單獨的堡壘機系統會增加PaaS的複雜度以及維護成本。
  2. 登入方式選擇。無論使用密碼還是私鑰驗證登入,容器內的密碼或者authorized_keys的管理都需要通過加入額外的程式解決,無疑會增加容器的複雜度。同時還要面對同許可權容器的密碼或authorized_keys的一致性問題。

基於以上問題,在我們的LAIN平臺中,設計出了基於WebSocket協議與Docker Remote API的遠端登入方案。LAIN(https://laincloud.com)是一個基於Docker的PaaS。其面向技術棧多樣尋求高效運維方案的高速發展中的組織,DevOps人力缺乏的startup以及個人開發者。LAIN通過統一高效的開發工作流,降低應用運維複雜度;在IaaS / 私有IDC裸機的基礎上直接提供應用開發,整合,部署,運維的一攬子解決方案。

該方案的整體架構圖如下:

容器

從圖中可以看出,在LAIN中實現容器遠端登入支援需要以下兩個元件:

1.  Entry應用。負責如下工作:

  • 呼叫Docker Remote API
  • 通過WebSocket 傳遞stdin,stdout和stderr。
  • 根據protobuf3協議對各類訊息進行序列化與反序列化。
  • 對使用者登入的鑑權。

    Entry是基於Go語言開發的,並依賴如下程式碼庫:

  • github.com/gorilla/websocket:WebSocket的服務端實現。
  • github.com/fsouza/go-dockerclient:Go語言的Docker客戶端。
  • github.com/golang/protobuf/proto:protobuf協議的支援庫。

  2.  基於命令列的客戶端。負責如下工作:

  • WebSocket連線請求的傳送。
  • 監聽鍵盤輸入、視窗變化事件以及WebSocket返回的stream。
  • 將遠端的stdout,stderr輸出到本地終端的標準輸出和標準錯誤。

Entry的工作流程

通過命令列客戶端遠端登入容器的過程及其實現如下:

  1. 使用者通過客戶端命令向Entry應用傳送WebSocket連線請求。
  2. Entry應用接收到使用者請求,得到請求Header中的access_token以及要進入的容器資訊,通過呼叫LAIN的console介面判斷該使用者是否有許可權進入容器。如果沒有許可權,則直接通知客戶端鑑權失敗,本次連線結束。
  3. 如果通過了許可權驗證,則WebSocket連線會被建立。緊接著Entry會去呼叫 execCreate 這個Docker Remote API。在呼叫時,需要指定Tty,AttachStdin、AttachStdout和AttachStderr引數均為true,Cmd引數為bash,這樣才能獲得bash程序的標準輸入輸出和錯誤。

    如果呼叫execCreate成功,呼叫請求會返回該Exec的ID,Entry會繼續根據這個ID呼叫execStart介面。在呼叫時,需要指定Detach和Tty為false,這樣才能連線到bash程序的標準輸入輸出和錯誤。呼叫execCreate成功後,會返回一個HTTP的stream。在Entry中則通過3個goroutine分別處理stdin,stdout和stderr。

  4. 客戶端會同時監聽WebSocket連線與鍵盤輸入,對於WebSocket返回的Message,客戶端會通過Entry制定的protobuf3訊息格式反序列化出訊息結構,並根據訊息的型別,將資料傳送到本地終端的stdout或stderr。對於鍵盤輸入,客戶端會將輸入內容封裝,經過protobuf3序列化後,通過WebSocket傳送給Entry應用,Entry應用經過反序列化後,將輸入傳送給bash的stdin。

以上就是Entry的工作原理。從中我們可以看出,Entry已經很好地解決了傳統ssh-client登入所遇到的問題:

  • Entry通過呼叫console的介面完成了身份驗證工作,由於所有的許可權都被console統一管理,因此Entry不需要自己維護許可權資訊,即Entry本身是無狀態應用。這種應用的優勢在於可以低成本擴容,用以應對多併發的場景。
  • Entry通過Docker Remote API連線容器,這樣只要被連線的容器內可以啟動bash程序,使用者就可以通過客戶端連線到該容器。容器無需啟動sshd程序,也就無需再以supervisord等程序作為entrypoint。更多的容器就可以以單程序的形式執行,降低了容器本身的維護成本。

Entry的設計細節

俗話說,細節決定成敗。為了提高使用體驗,Entry應用在設計與實現時考慮到了很多細節,在這裡拿出來與大家分享。

1. 連線保持:當WebSocket連線在一段時間內沒有資料傳輸後,會自動斷開。這給使用者的使用帶來了極大的不便。Entry在設計時,對每一個建立的WebSocket連線,會有一個單獨的goroutine每隔10秒傳送一個PING型別的Message(不是WebSocket協議中的PingMessage),這樣保證了在不主動斷開的情況下,使用者和容器可以一直保持連線。

2. 使用protobuf3制定訊息格式並實現序列化與反序列化:使用protobuf3可以方便地定義與擴充套件自己的訊息格式,同時在傳輸時能減小一定的頻寬佔用。

Entry的訊息格式有兩類,RequestMessage和ResponseMessage。客戶端傳送的請求都屬於RequestMessage,服務端返回的資料都封裝在ResponseMessage中。其中:

  • RequestMessage型別包括:PLAIN和WINCH。PLAIN就是使用者通過鍵盤的輸入。WINCH則是終端視窗大小改變的訊息,內容中會攜帶新視窗的rows和cols。
  • ResponseMessage型別包括:STDOUT, STDERR,PING和CLOSE。STDOUT和STDERR代表了該訊息內容是來自於標準輸出還是標準錯誤。PING則代表是連線保持專用的資訊。CLOSE則是連線將要斷開前Entry返回的資訊,會包含錯誤原因或者正常退出的資訊。

3. 監聽終端視窗大小改變:預設的終端視窗大小都是80 * 24,但該標準在當前的日常使用中早已過時。如果在一個全屏的terminal中仍然使用該大小顯然是不合理的。因此客戶端在成功連線到容器後,客戶端會首先根據當前的terminal大小發送一個WINCH型別的RequestMessage,Entry收到後會呼叫ExecResize介面,這樣之後所有的stdout和stderr都會按照新的終端大小顯示。客戶端還會監聽視窗大小改變的事件,如果發生改變,同樣還會發送WINCH到Entry。

4. UTF-8編碼檢查:客戶端和服務端在傳送訊息內容時,都會對緩衝區內要傳送的資料做UTF-8編碼檢查。如果傳送資料不符合編碼規則,則會先發送最長符合的緩衝區字首,後面剩餘的資料則被移到緩衝區的開始,待下次傳送。這種設計是為了處理中文等非latin1字元的顯示問題。避免因為非法的UTF-8編碼造成終端顯示亂碼。

Entry存在的問題

  1. 非正常退出時,bash程序不會結束,而是會以sleep的狀態殘留於容器中。如果一個容器有過多的bash程序,很可能因為cgroup的記憶體限制導致容器退出。目前官方並沒有給出類似execKill的API,只能期待在以後版本的docker中能解決這個問題。
  2. Entry應用依賴特定的LAIN客戶端。之前使用者只能通過lain enter命令進入容器。但是12月份後我們升級了console的前端,增加了web terminal功能。使用者只需要通過點選容器的ID就可以開啟一個含有terminal的web頁面,然後通過該web頁面與容器進行互動,不需要再安裝任何客戶端。在這裡要十分感謝開源專案xterm.js(https://github.com/sourcelair/xterm.js),該專案基於JavaScript與CSS實現了一個近乎完美模擬xterm終端的外掛。目前,console的web terminal可以支援Firefox和Chrome,但是無法支援IE和Safari。

總結

Entry是LAIN中一款設計較為精巧、技術含量較高的應用。其利用了WebSocket全雙工傳輸的特點,在單程序容器的場景下實現了對容器的遠端登入,同時保證了登入許可權的控制。本文希望通過分享LAIN中Entry的設計與實現,為需要開發遠端登入容器功能的PaaS同行提供技術方案參考。Entry已經開源,地址在(https://github.com/laincloud/entry),歡迎一起討論交流學習。

文章來源:宜信大資料創新中心