一種新的進入容器的方式: 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裸機的基礎上直接提供應用開發,集成,部署,運維的一攬子解決方案。

該方案的總體架構圖以下:
 git

圖片_1.png



從圖中能夠看出,在LAIN中實現容器遠程登陸支持須要如下兩個組件:github

  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的工做流程

經過命令行客戶端遠程登陸容器的過程及其實現以下:golang

  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登陸所遇到的問題:web

  • Entry經過調用console的接口完成了身份驗證工做,因爲全部的權限都被console統一管理,所以Entry不須要本身維護權限信息,即Entry自己是無狀態應用。這種應用的優點在於能夠低成本擴容,用以應對多併發的場景。
  • Entry經過Docker Remote API鏈接容器,這樣只要被鏈接的容器內能夠啓動bash進程,用戶就能夠經過客戶端鏈接到該容器。容器無需啓動sshd進程,也就無需再以supervisord等進程做爲entrypoint。更多的容器就能夠以單進程的形式運行,下降了容器自己的維護成本。

 

Entry的設計細節

俗話說,細節決定成敗。爲了提升使用體驗,Entry應用在設計與實現時考慮到了不少細節,在這裏拿出來與你們分享。docker

  1. 鏈接保持:當WebSocket鏈接在一段時間內沒有數據傳輸後,會自動斷開。這給用戶的使用帶來了極大的不便。Entry在設計時,對每個創建的WebSocket鏈接,會有一個單獨的goroutine每隔10秒發送一個PING類型的Message(不是WebSocket協議中的PingMessage),這樣保證了在不主動斷開的狀況下,用戶和容器能夠一直保持鏈接。
  2. 使用protobuf3制定消息格式並實現序列化與反序列化:使用protobuf3能夠方便地定義與擴展本身的消息格式,同時在傳輸時能減少必定的帶寬佔用。


Entry的消息格式有兩類,RequestMessage和ResponseMessage。客戶端發送的請求都屬於RequestMessage,服務端返回的數據都封裝在ResponseMessage中。其中:bash

  • RequestMessage類型包括:PLAIN和WINCH。PLAIN就是用戶經過鍵盤的輸入。WINCH則是終端窗口大小改變的消息,內容中會攜帶新窗口的rows和cols。
  • ResponseMessage類型包括:STDOUT, STDERR,PING和CLOSE。STDOUT和STDERR表明了該消息內容是來自於標準輸出仍是標準錯誤。PING則表明是鏈接保持專用的信息。CLOSE則是鏈接將要斷開前Entry返回的信息,會包含錯誤緣由或者正常退出的信息。
  1. 監聽終端窗口大小改變:默認的終端窗口大小都是80 * 24,但該標準在當前的平常使用中早已過期。若是在一個全屏的terminal中仍然使用該大小顯然是不合理的。所以客戶端在成功鏈接到容器後,客戶端會首先根據當前的terminal大小發送一個WINCH類型的RequestMessage,Entry收到後會調用ExecResize接口,這樣以後全部的stdout和stderr都會按照新的終端大小顯示。客戶端還會監聽窗口大小改變的事件,若是發生改變,一樣還會發送WINCH到Entry。
  2. 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),歡迎一塊兒討論交流學習。websocket

相關文章
相關標籤/搜索