Docker的發展勢頭一天比一天強勁,它顯然在試圖解決實際的問題。然而,對如今許多的生產環境用戶來說,沒有出現優點壓倒缺點的局面。在開發、測試和持續性集成等環境下,Docker在讓容器吸引廣大開發人員方面確實有上佳的表現,不過它還沒有顛覆生產環境。按照DockerCon 2015的“生產環境下的Docker”這一主題,我想公開討論Docker想在生產環境使用場合下得到廣泛采用還沒有克服的種種挑戰。這里提到的問題沒有一個是新問題,它們都以某種形式出現在GitHub上。大多數問題我已經在大會演講中或與Docker團隊交流中討論過。本文倒不是要明確指出什么不再是問題:比如說,新注冊中心(registry)克服了舊注冊中心的許多不足。本文并沒有提到仍然問題重重的許多方面,不過我認為下面這些問題是近期內需要解決的最重的問題;只有解決了這些問題,更多的企業組織才能夠邁出一大步,在生產環境中運行容器。我在電子商務公司Shopify運行Docker的經歷對本文有很大的影響;一年多來,我們一直在容器上大規模運行核心平臺。由于像Docker這樣發展這么迅猛的技術,不可能一切都保持現狀。如果你發現不正確之處,務必聯系我。
映像構建
為大型應用程序構建容器映像依然是個挑戰。如果我們要依賴容器映像用于測試、持續性集成和緊急部署,就需要在不到1分鐘的時間內將映像準備就緒。Docker文件(Dockerfile)讓這對大型應用程序來說幾乎不可能。雖然Docker文件易于使用,但是位于過高的抽象層,無法支持復雜的使用場合:
帶外緩存,面向特別錯綜復雜的、針對特定應用程序的依賴項;
在構建時訪問密文(密碼、密鑰和相關內容),又不將它們提交給映像
全面控制最終映像中的層
并行處理構建層
大多數人并不需要這些功能,但是對大型應用程序而言,其中許多功能是快速構建映像的先決條件。Chef和Puppet等配置管理軟件使用廣泛,但是讓人覺得用于構建映像過于笨拙。我打賭,在今后十年內,現有形式的這類系統會因容器而逐漸退出歷史舞臺。然而,許多應用程序依賴它們來配置、部署和編排。Docker文件無法真實地記錄下現在由配置管理系統管理的復雜性,但這種復雜性需要在某個地方加以管理。在Shopify,我們最后使用docker commit API,從頭開始構建了自己的系統。這個過程很麻煩,我希望這一幕不會出現在任何人身上,我很想擺脫這種局面,但是我們又不得不掃除障礙。很少有人會花這么大的力氣去管理用于生產環境的容器。
這個領域會出現什么情況不得而知;目前在這個領域,開展的研究工作并不多(一個例子是dockramp,這是另一種打包器)。Docker引擎會在將來有所改進,將構建基本步驟(添加文件和設置入口點等)與客戶端(Docker文件)分開來。為版本1.8所做的合并工作已經讓這變得更容易,為配置管理工具廠商、業余愛好者和公司進行試驗嘗試創造了條件。考慮到配置系統的歷史還很短,認為一種標準有望搞定這個問題(就像運行時標準那樣)是不切實際的。什么時候可以實現可擴展的映像構建,相當不明朗。據我所知,沒人在積極迭代,很遺憾這種現狀已維持一年多了。
垃圾收集
每個部署的重大Docker系統到頭來要編寫垃圾收集器,以便清除主機上的舊映像。使用了各種啟發式方法,比如清除超過X天的舊映像,在主機上最多執行Y個映像。Spotify最近開放了其系統的源代碼。我們還在很久以前就編寫自己的垃圾收集器。我能明白為此設計一種易預測的用戶界面(UI)有多難,但是這又是核心中絕對需要的。當生產環境的機器嚷著要存儲空間時,大多數人無意中發現要求收集垃圾。最后,你會遇到同一映像,Docker注冊中心因龐大映像而溢出,不過這個問題已列在了發行版路線圖上(詳見https://github.com/docker/distribution/blob/master/ROADMAP.md#deletes)。
迭代速度和核心狀態
Docker引擎致力于1.x版本的穩定性。在版本1.5之前,降低準入門檻以便在生產環境得到采用方面所做的工作不多。開發容器的公眾心理模式對Docker的成功而言必不可少,Docker害怕破壞這種模式是有其道理的。如果用戶體驗(UX)方面的每個變化經歷的過程時間過長,迭代速度難免受到影響。自版本1.7起,Docker開始發布試驗性版本,以網絡和存儲插件帶頭。這些功能特性被明確標為“未準備用于生產環境”,可能會從核心中取出或者隨時經歷重大變化。對于早已看好Docker的公司來說,下面這個是好消息:它讓核心開發團隊可以更快速地迭代開發新功能,不用擔心本著最佳設計的精神而破壞次要版本之間的向后兼容性。公司仍然很難改動Docker核心,因為它需要分支――這是導致最終失敗的行為和維護負擔,或者需要得到上流接受;對于值得關注的補丁來說,這常常很耗費人力。自版本1.7起,宣布插件后,解決這個問題的策略就很明確:讓每一個固執己見的的組件都可以插入,最后顯示了“帶電池而且可以更換”這種理念的成果,這種理念最早是在2014年的DockerCon歐洲大會上提出來的(不過相當模糊)。在6月份的DockerCon大會上,很高興聽到這歸入到“管道”(Plumbing)這個大主題來探討,作為核心開發團隊的重中之重。雖然未來終于大有希望,但是這在今天仍然是個痛點,就跟過去兩年一樣。
日志
日志是表明有望得益于之前變化的一個方面的例子。這并不是引起強烈關注的問題,卻是普遍性的問題。目前沒有理想的、普通的解決方案。日志到處都是:尾部日志文件、容器里面的日志、通過掛載發送到主機的日志、發送到主機syslog的日志,通過fluentd(開源數據采集器)等工具來暴露日志,從應用程序直接發送到網絡的日志,或者發送到文件的日志,讓另一個進程將日志發送到Kafka。在版本1.6中,支持日志驅動程序的功能已并入到核心中(https://blog.docker.com/2015/04/docker-release-1-6/);然而,驅動程序在核心中必須得到接受(這并非易事)。在版本1.7中,已并入了試驗性支持進程外插件的功能,但是讓我失望的是,它并不隨帶日志驅動程序。我認為,版本1.8會計劃添加這項功能,但是在官方記錄中找不到這項。到那時,廠商們就能夠編寫自己的日志驅動程序。社區內部的共享將輕而易舉,大型應用程序再也不必求助于設計定制的解決方案。
密文
遷移到容器的人大多數依賴配置管理,在機器上安全地配置密文;然而,繼續沿著配置管理這種老路子配置容器中的密文很笨拙。另一個辦法就是將密文與映像一同分發,但是這帶來了安全風險,而且很難在開發、持續性集成和生產環境之間安全地回收映像。最純粹的解決辦法就是通過網絡訪問密文,讓容器的文件系統保持無狀態。就在不久前,這方面還沒有任何面向容器的機制;不過最近,兩家頗令人關注的密文代理系統Valut(https://vaultproject.io)和Keywhiz(https://github.com/square/keywhiz)開放了源代碼。在Shopify,我們一年半前開發了ejson(ejson是一種簡單的庫,用嵌入在JSON文件中的公鑰加密該文件中的所有值,詳見https://www.shopify.com/technology/26892292-secrets-at-shopify-introducing-ejson),以解決這個問題,從而管理JSON文件中非對稱加密的密文文件;然而,它就所運行的環境有一些假設,因而讓它與密文代理系統相比,不是很理想的一般性解決方案(如果你很好奇,可以參閱這篇文章https://www.shopify.com/technology/26892292-secrets-at-shopify-introducing-ejson)。
文件系統
Docker依賴來自文件系統的寫時拷貝機制(CoW)。這是為了確保如果有100個容器是從一個映像運行的,你就不需要100倍的磁盤空間。相反,每個容器在映像上面創建一個CoW層,只有利用原始映像創建文件時,才使用磁盤空間。容器的“規范市民”對容器里面的文件系統帶來的影響極小,因為這類變化意味著容器具有了狀態,這是絕對禁止的。這類狀態應該存儲在映射到主機或的卷上或通過網絡來存儲。此外,層次技術節省了部署之間的存儲空間,因為映像常常相似,有共同的層。在Linux上支持CoW的文件系統存在的問題是,它們都有點新。我們Shopify在幾百個負載相當大的主機上遇到過幾種文件系統:
AUFS。看到整個分區在我們要重新掛載的地方鎖起來。速度緩慢,耗用大量內存。代碼庫很龐大,難以讀取,這可能就是為什么它沒有被接受、進入到上游,因而需要自定義內核。
BTRFS。面臨學習曲線,需要學用一套新的工具,因為du和ls不管用。與AUFS一樣,我們看到分區凍結,內核鎖隹,盡管玩貓捉老鼠的游戲,希望內核版本保持是最新版本。臨近磁盤空間極限時,BTRFS的行為捉摸不定,如果你有1000個這樣的CoW層(用BTRFS的術語來說是子卷),也是如此。BTRFS耗用大量內存。
OverlayFS。這在3.18版本中已并入到Linux內核,對我們來說已相當穩定、快速。它耗用的內存要少得多,因為它設法在索引節點(inode)之間共享頁面緩存。遺憾的是,它需要你運行未被大多數發行版采用的較新內核,這常常意味著構建自己的內核。
幸好對Docker來說,Overlay文件系統很快就會無所不在,不過在我們看來,運行大量節點時,AUFS這一默認文件系統對生產環境來說仍然很不安全。不過很難說在這里該如何是好,因為大多數發行版也并不隨帶已準備支持Overlay的內核(有人提議Overlay作為默認文件系統,但由于這個原因遭到駁斥),不過這絕對是這個領域的發展方向。看來我們除了等待別無他法。
依賴處于前沿的內核功能
正如Docker依賴最前沿的文件系統那樣,它還充分利用最近為內核添加的大量功能特性,也就是命名空間以及不是太新,又不常使用的控制組(cgroup)。這些特性(尤其是命名空間)還沒有在業界得到廣泛采用,因而還沒有經過考驗。我們偶爾會遇到這些特性存在的不明顯錯誤。我們碰到網絡命名空間在生產環境下被禁用的情況,那是由于我們遇到過相當多的軟死鎖,事后查明這些軟死鎖與實施有關,卻又沒有資源從上流解決問題。內存控制組耗用相當多的內存,我聽到過外頭反映不可靠的情況。隨著容器得到越來越廣泛的使用,大公司可能會率先做好這項穩定工作。
我們在生產環境中碰到的需要加固的一個例子就是僵尸進程(https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)。容器在PID命名空間里面運行,這就意味著容器里面的第一個進程有pid 1。容器里面的init需要執行確認已死的子進程這一特殊任務。某個進程死后,并不立即從內核進程數據結構中消失,而是成為一個僵尸進程。這確保了父進程可以通過wait(2)檢測到子進程已死。然而,如果子進程成為孤兒,其父進程就被設成init。該進程隨后死后,init的任務就是通過wait(2)確認子進程已死――不然,僵尸進程就會永遠存在。這樣一來,內核進程數據結構會被僵尸進程耗盡,之后你就得自己想辦法。對基于進程的master/worker模式來說,這種場景相當常見。如果worker進程逃出外殼,并花了很長時間,master進程就會使用SIGKILL終結等待外殼命令的worker進程(除非你使用進程組,一下子終結整個進程組)。逃出外殼的派生進程然后被init繼承。等該進程終于完成后,init需要對它執行wait(2)操作。Docker引擎可以解決這個問題,通過Docker引擎使用設置PR_SET_CHILD_SUBREAPER確認容器里面的僵尸進程,https://github.com/docker/docker/issues/11529有所描述。
安全
運行時安全對容器來說仍要打個問號;想針對生產環境進行加固是經典的先有雞還是先有蛋的安全問題。以我們Shopify為例,我們并不依賴容器提供任何額外的安全保障。然而,許多使用場合卻依賴容器提供額外保障。由于這個原因,大多數廠商仍在虛擬機中運行容器,而虛擬機的安全久經考驗。由于操作系統虛擬機勝出,我希望看到虛擬機在今后十年內消失,因為有人曾在Linux郵件列表上說過:“我曾經聽到虛擬機管理程序是活生生的證據,證明了操作系統的無能。”容器在虛擬機(硬件層虛擬化)和PaaS之間(應用程序層)提供了完美的中間體。我知道,運行時安全方面在做更多的工作,比如說能夠將系統調用列入黑名單。映像方面的安全一向是問題的根源,但是Docker正在借助libtrust和notary――它們將是新的發行層(https://github.com/docker/distribution)的一部分的,積極改進這方面。
映像層和傳輸
第一個版本的Docker為映像的構建、傳輸和運行時環境采取了一條巧妙的捷徑。它選擇了適用于所有情況的工具:文件系統層,而不是為每個問題選擇一種合適的工具。這種抽象機制一直作用于在生產環境中運行容器。這是完全可以接受的最小可行產品實用主義,但是每個問題都能極其高效地得到解決。
映像構建可以表示為有向工作圖。這樣可以弄清楚緩存和并行處理,以便迅速地構建可預測的映像。
映像傳輸而不是使用映像層,它就可以執行二進制差異化(binary diffing)。這個話題已研究了幾十年。分發層和運行層越來越分離開來,為這種優化創造了條件。
運行時環境應該就實施單單一個CoW層,而不是再次使用隨意的映像層抽象。如果你在第一次讀取時使用AUFS之類的統一文件系統,就遍歷鏈接文件列表來匯編最終文件。這很緩慢,而且完全沒有必要。
層模式對映像傳輸以及對構建來說是個問題。這意味著,你必須極其小心對待映像每個層中的東西,因為不然你很可能到頭來為大型應用程序傳輸100MB的數據。如果你在自己的數據中心里面有大型鏈接,這個問題不大,但是如果你想使用Docker Hub之類的注冊中心服務,這就會通過公開的互聯網來傳輸。映像分發目前正在積極開發之中。Docker公司有足夠的動機將這方面做得可靠、安全而快速。至于構建,我希望這為插件創造條件,以便一種優秀的解決方案浮出水面。
結束語
另外許多話題有意沒有探討,比如存儲、網絡、多租戶、編排和服務發現。如今Docker需要的是更多的人將容器部署到生產環境,而且是大批人在部署。遺憾的是,許多公司一開始就對PaaS寄以厚望,利用當前的架構過度補償。如果你規模很小,或者規劃將Docker部署到全新的環境,這種方法才可行。想在生產環境下得到更廣泛的使用,我們就需要解決上述一些問題,以便讓Docker的優點明顯壓倒缺點。
Docker將自己置于令人興奮的位置,充當PaaS的接口,無論是發現、網絡還是服務發現,應用程序沒必要關心底層基礎設施。這是好消息,因為正如Solomon所說,Docker的最大優點就是,它讓人們達成了共識。我們終于開始就映像和運行時環境之外的其他方面達成共識。
我與Docker公司的人員深入探討過所有上述問題。在一定程度上存在所有這些問題的GitHub Issues。我在這里的目的是,僅僅是提供個人之見,闡明哪幾個方面對降低Docker的準入門檻最為重要。我對未來滿懷激情,但是我們仍有大量工作要做,才能讓Docker應用到更多的生產環境。