Coding WebIDE 作個國內首個基於 Docker 技術的雲端開發平臺於4月1日正式上線。本文主要和你們分享和探討 Docker 在 Web IDE 中運用的一些經驗。node
隨着雲計算技術的突飛猛進,雲端的代碼倉庫,分工協做,演示運行已經被人們廣爲接受。雲端開發的出現也正是順應了這一趨勢。Docker 做爲一個輕量級的隔離環境,無疑是雲端開發解決資源和效率問題的祕藥良方。linux
記得4月份的杭州 Docker Meetup 有一與會者提問,「做爲一個雲主機的租戶,向主機商購買的計算資源,其得到的配額不是真實值而只是上限,以爲不值。」這個問題彷佛揭露了商家的生意經,可是本人卻有不一樣的見解。正是由於共享技術的發展,才讓雲計算資源變得廉潔而被廣爲接受,Docker 最大的價值也在這裏。git
從技術特性上看,VM 和 Docker 有些重合點。可是Virtual Machines 是基於 Hypervisor 技術的,而 Docker 是基於Container 技術的。Hypervisor 要比 Container 更底層,不是同一層面的競爭關係,真實的場景可能是先 Hypervisor 再 Container,通俗的說法就是在 VM 裏跑Container。github
Hypervisor 技術讓多個操做系統共享一個CPU硬件,這些操做系統獨立運行,並不知道彼此的存在,彷彿獨佔了全部的硬件資源。web
Container 技術讓多個用戶空間共享一個操做系統,這些用戶空間彼此隔絕,彷彿獨佔整個操做系統。docker
咱們都知道,文件是對 I/O 設備的抽象表示,虛擬存儲器是對主存和磁盤 I/O 設備的抽象表示,進程則是對處理器、主存和 I/O 設備的抽象表示。相比之下,虛擬化將操做系統從硬件中抽象出來,容器技術將應用從操做系統抽象出來。shell
一個正在執行的進程,因爲虛擬內存技術,就其視角來看,彷彿擁有了整個操做系統的計算資源。可是 Container 的抽象和進程抽象不是在一個層面的。簡單說,一臺物理設備能夠藉助於 Hypervisor 技術,運行多個 VM;而個操做系統能夠藉助 Container 技術,運行多個 Container;而 Container 裏能夠有多個進程。ubuntu
上面簡單的介紹了一些 Docker 技術的背景,言歸正傳。後端
實現 WebIDE 首先解決的就是環境隔離,多個用戶之間不會相互干擾。物理機是相互隔離的,可是爲每個用戶分配一臺真實的物理機,顯然是不合現實的。api
VM 能夠提供和物理機同樣的隔離效果,因爲 VM 共享硬件,因此更省資源。一個可行的方案是藉助 IaaS 平臺商提供的 OpenAPI 來操做 VM。這樣對物理主機和宿主操做系統的維護工做能夠徹底委託給IaaS平臺商。
相比 Docker Container,VM 有一個很大的技術優點是支持休眠。操做系統在系統級實現了休眠,這樣用戶的工做狀態,內存中的數據能夠完整的持久化。做爲一個常年不關機的開發者,我的以爲這個功能很是實用。惋惜Docker 只提供了睡眠(相似於進程級別的掛起),而作不到休眠。隨着CRIU技術的發展,相信 Docker 很快會支持的。
另外 VM 在不一樣宿主機之間的遷移問題,通過多年社區的積累愈來愈成熟。若是選擇向 IaaS 平臺商購買 VM 服務,這部分工做也不用關心。Docker Container 數據的遷移,面臨着自制。目前Docker 官方提供遷移Container(非image)的命令,只能遷移文件,沒法保留狀態(好比外部mount的目錄)。
考慮到架構的微服務化,如文件服務,Git 服務,Terminal 服務,Runtime 服務。有些服務是單例的,另外一些則會隨着用戶會話狀態而動態地建立和銷燬。當應用實例不少的時候,虛擬化技術的 Overhead 是須要考慮的因素。爲了某個服務而啓動整個操做系統有些負擔不起。除了過分的內存消耗,啓動耗時也存在差別,Container 只是用戶空間的一個或者一組進程,因此啓動耗時基本是毫秒級別,而 VM 至少是秒級,有的甚至是分鐘級(休眠還原的時候)。
作比較的時候老是各有優劣,但最終打動咱們的除了 Docker 的輕量,還有其生機勃勃。咱們相信備受社區關注的技術,許多顧慮的問題終究會有解決方案的。
一個完整的 IDE 須要具有不少功能,文件管理,版本管理,編輯器(語法高亮,自動補全),編譯器,執行環境等等。Rome was not built in a day。初次上線的最小功能集合裏,咱們認爲 Web IDE 區別於 Web Editor 的一個功能亮點就是 Web Terminal。
Web Terminal 和 SSH 的工做原理相似,經過架設在 TCP 之上的應用層協議實現對主機的遠程控制。相信大多數開發者都有 SSH 的使用經驗,理解其工做原理的僅佔少數。開始研究之初,咱們也和大多數人同樣搞不清楚terminal, tty, pty, shell, bash 之間的區別,因此先來理理概念。
從用戶的角度來看,Terminal 是鍵盤和顯示器的組合,也稱爲TTY(TeleTYpewriter,電傳打字機的縮寫)。鍵盤輸入字符,顯示器顯示字符。從進程的角度來看,終端是字符設備,能夠經過 read,write,ioctrl 等系統調用來讀寫和控制該設備。
TTY 早已進入了博物館,桌面系統上字符界面基本被 GUI 界面替代。取而代之是一個稱之爲 Terminal Emulator(終端模擬器)的窗口程序,該程序顯示的字符界面就是曾經物理顯示器裏的完整內容。
Terminal 做爲真實的物理設備已經不復存在了,可是爲了和麪向終端的程序(好比 Bash)進行通訊,因而就了發明了 pty(Pseudoterminal,僞終端)。pty 是一對 master-slave 設備,master 設備表現得像一個文件,slave 設備表現得像一個終端設備,當 Terminal Emulator 做爲一個非面向終端的程序不直接與 pty slave 通信,而是經過文件讀寫流與 pty master 通信,pty master 再將字符輸入通過線路規程的轉換傳送給 slave ,slave 進一步傳遞給 bash。
Bash 一個命令行的解釋器,一般也是進程會話的主進程,其職責是解釋執行終端設備(或者僞終端的 slave 設備)傳遞過來的字符串和控制字符,執行命令。
理解了上面背景知識以後,再看 SSH 的原理圖。
SSH 是一個典型的 server-client 模式架構,用戶經過終端將字符流傳遞給SSH client。SSH client 和 SSH server 之間經過 TCP/IP 協議進行通信。遠端的 server 建立一對 pty,而且 fork+exec 一個 bash 進程,server 進程經過 pty 對與 bash 進行交互。
仿照 SSH 的工做原理,咱們在 HTTP 協議之上設計了 Web Terminal,見下圖
真實實現中,socket.io 是應用層的通信協議。Terminal Emulator 是一個純JS的實現,nodejs 後端使用 pty.js 模塊來建立 pty 對。
當解決了 Web Terminal 的總體架構之後,嵌入 Docker Container 已經是水到渠成。
咱們知道 Docker 因爲缺乏 init 0 而致使殭屍進程沒法回收的問題迄今存在。Terminal 做爲控制終端,會在使用過程當中執行若干命令,這些命令對應進程若是與其父進程脫離父子關係,那殭屍進程問題就來了。
Docker 官方推薦的一個 Container 只跑一個進程。若是 Container 與進程同生共死,殭屍進程的問題基本不會遇到。可是 Web Terminal 所在 Container 裏啓動了bash,而 bash 能夠隨意執行命令啓動進程,殭屍進程問題很難避免。好在社區提供了更好的解決方案:phusion/baseimage。在Dockerfile裏將的 FROM ubuntu
改成FROM phusion/baseimage
,再按照文檔說明作些調整基本就行了。
一般,咱們都是把 App 部署到 Docker 裏去。大體步驟就是編寫 Dockerfile ,再構建成 image,而後藉助 private registry 在分佈式的集羣中分發。因爲開發環境、測試環境和生產環境存在差別,每每構建交付物涉及到大量參數和環境變量的設定,過程很是繁瑣,通常都會腳本化。因此IDE項目基本都是 Dockerfile 旁邊放置了一個 Gemfile 和 Rakefile。經過 Ruby Rake 來驅動整個構建過程。
做爲腳本語言與 Shell 相比,Ruby 的好處是
但 Ruby 不像 Shell 那樣信手拈來,須要進行適當的配置,好比,RVM 安裝指定版本,修改 gem source 之類的。
從前配置這些基礎環境,都是記錄成 Markdown 文檔,一堆 apt-get,sed 指令。可是引入 Docker 之後,有更好的選擇。
咱們的方式以下:
編寫一個配置構建環境的 Dockerfile,構建成 image。
docker build --rm -t="ide-docker-registry.coding.local/ide-builder:0.0.5" .
push 到 registry 裏。
docker push ide-docker-registry.coding.local/ide-builder
在構建服務器建立構建所需的builder,經過mount外部目錄的方式,構建環境和外部環境交互文件。
docker run --name coding_ide_builder -d -t -v $CODING_IDE_HOME:/data/coding-ide-home --net=host --restart=always ide-docker-registry.coding.local/ide-builder
進入構建環境執行命令。
docker exec -i -t coding_ide_builder bash
或者直接構建。
docker exec -i -t coding_ide_builder rake
資源限制主要針對CPU、內存、磁盤和網絡帶寬等共享資源的限制。一方面,咱們提倡共享,事實上不是全部的用戶都須要長時間的佔滿所需的資源配額,不須要的時候能夠釋放出來分享給其餘用戶,由於共享纔會更便宜。另外一方面,也須要對可共享資源設定一個最大的限制配額,以防止某些用戶過分佔用而影響其餘用戶的使用體驗。
Docker 提供了兩個參數來控制 cpu 的分配策略,--cpuset
和 --cpu-shares
。
--cpuset="0" [...]
將Container限定於某幾個CPU核心上。針對這一特性,咱們制定的策略是將重要的 Container 服務分配在獨立的核心上,以保證服務的質量。
--cpu-shares
能夠調節Container得到的時間片。咱們經過這個配置來調節 Web Terminal 所建立進程對CPU的佔用率。
Web Terminal 裏用戶的自由度是很大的,對內存限制能夠減小惡意破壞。Docker 配置內存限制相對簡單。另外,咱們禁用了swap分區,以減小對磁盤的壓力。
因爲用戶能夠徹底自由的訪問磁盤,咱們最但願 Container 磁盤鏡像文件具有 thin provisioning 特性,不須要預分配全部空間也能夠限定其大小。
對於 Container 的磁盤限制分爲兩部分,對最上層可寫 layer 的限制和對被 mount 的可寫目錄的限制。
Docker Daemon 提供了四種 storage-driver: aufs
, devicemapper
, btrfs
, overlay
。aufs 在其被支持的 linux 發行版上,是默認的 storage-driver。不然 Docker 會啓用 devicemapper。aufs 最先被 Docker 支持,並且支持共享二級制文件和動態庫文件所佔用的內存,btrfs 和 overlay 不支持此特性,可是比aufs速度更快。devicemapper 的特色是支持 thin provisioning 和 copy on write。
限制 layer 的大小,devicemapper 是目前惟一的選擇。啓動 devicemapper 後,Docker 會爲全部的 Container 建立一個共享存儲池,其實質上是一個大文件,另外也會限定每一個 Container 的大小。這兩個數字的制定須要慎重,由於考慮到數據遷移,修改很不容易。
Docker run 的時候 mount 進 Container 的可寫目錄是不受 devicemapper 的限制,因此須要額外處理。WebIDE 場景中 workspace 目錄是被多個 Container 實例中共享讀寫的,做爲用戶工做目錄,須要設定一個最大的空間限制。
談到 linux 磁盤空間限制,最早想到 quota。quota 經常使用於ftp服務,限定用戶最大可用空間。但 quota 有一個技術限制,僅僅適用於整個文件系統而沒法針對單個目錄。因此 quota 方案在共享目錄的場景不可行。
linux 支持將一個磁盤鏡像文件 mount 成目錄,磁盤鏡像文件能夠限定大小。當鏡像文件撐滿的時候,目錄就不可寫了。這是咱們目前找到最靠譜的方案。
Docker 沒有直接提供限制網絡帶寬的命令行參數,但藉助 Docker 的底層技術 cgroup 能夠實現。建立一個 Network classifier group,對 cgroup 進行帶寬限制的設定,將 Container 都指定到該組裏去。Traffic Controller(tc) 和 Netfilter(iptables) 都支持針對 cgroup 指定規則。
base 在 Docker 之上,更容易實現架構的微服務化。藉助於 Docker 的 link 特性和 fig 工具,Container 能夠像樂高積木同樣把全部的組件都組合起來。Nginx,Jetty,MySQL,Redis 等一系列服務能夠封裝到獨立的Container 裏去。
全面 Dockerize 的最大好處是整個體系都是一致的,全部的組件都是Container。WebIDE 在架構初期,考慮全面 Dockerize 的方案,好比把 MySQL 分紅兩個 Container ,一個存放安裝文件,另外一個存放數據文件。應用服務器自沒必要說也在 Container 裏。可是當考慮 Nginx 是否也要放進 Container 裏,你們想法有些分歧,Container 的好處在 Nginx 上是否明顯值得探討。也正由於存在不一樣的聲音,咱們放棄了全面 Dockerize。我的的以爲已有的經驗和腳本不該該放棄,節省出更多的精力來作更重要和緊迫的事情。
Vangie Du
未來的你,必定會感謝如今拼命努力的本身!
本文出自 Coding 官方技術博客,如需轉載請註明做者與出處。