為何Docker還未在產品中取得廣泛成功
[編者的話] 作為工作與第一線的Docker使用者,本文作者向大家描述了當前Docker無法滿足產品用例的幾個方面,以及未來的發展動向,供廣大Docker使用者參考。
注:由于原文作者文筆十分“神奇”,譯文無法保證準確表達作者的本意,請參考原文理解。
截至上周,Docker一直處于上升勢頭,這其中也涉及到一些實質性問題。對于當今許多產品的用戶而言,使用Docker卻是弊大于利的。Docker漂亮地使得容器技術對于開發者充滿吸引,尤其是在開發、測試以及CI環境下,但是它也確實破壞了生產。在DockerCon 2015的 “Docker in Production”一主題 中,我想開誠布公地談談Docker面臨的挑戰,以使它能適合產品的用例。這里提到的都不是新問題;它們均在GitHub中以某種形式存在著。其中絕大多數問題,我都已經在 會議 中 提及 ,或已和Docker團隊討論過。本文不會明確指出哪些問題已經不復存在:例如,新的注冊機制改進了舊版本的很多缺板。雖然許多看似存在問題的地方不會在文中提及,但是我個人堅信接下來提到的問題是至關重要的,需要在短時間內修復,以使得更多的組織能夠在產品中使用容器。在Shopify,我們大規模地使核心平臺運行于容器之上,現已超過一年時間,因此這里的列表無疑更傾向于 本人使用Docker的經驗 。對于像Docker這樣發展如此迅速的技術,保證一切通用基本是不可能的。因此,當你發現錯誤時,還望能夠 伸出援手 。
鏡像搭建
為大型應用搭建容器鏡像依舊是一項挑戰。如果我們依靠容器鏡像來進行測試、CI和應急部署,那么我們便須在1分鐘內準備好這個鏡像。 Dockerfile的存在使其這對于大型應用而言基本是不可能的完成的事。縱使它易于使用,但是它的抽象層次太高,以至于不能應對復雜的用例:
- 為重量級的面向應用的依賴提供帶外緩存
- 在構建期間的訪問密鑰,避免提交到鏡像
- 對于最終鏡像層級的完全控制
- 層級構建的并行化
絕大多數人不需要這些特性,但是對于大型應用而言,它們卻是快速構建的前提條件。諸如Chef和Puppet之類的廣泛應用的配置管理軟件,對于鏡像構建而言依舊過于笨拙。我敢打賭,未來5年內當前這種形式的系統將不復存在,并為容器取代。然而,許多應用依靠它們實現供應、部署和業務流。 Dockerfile并不能實實在在地應對當前由配置管理所管控的復雜度,但在某些地方這種復雜度確實需要被控制。在Shopify,我們最終通過 docker的commit API 重新實現了我們的系統 。這是痛苦的,我希望大家不會經歷這個“痛苦”并渴望摒棄它,但目前我們真的只能自己來排除困難。只是很少有人會如此大篇幅地來討論面向產品的容器。
很難說這個領域將出現什么,并且當前在這個領域也并未進行多少探索(其中一個例子是 dockramp ,另一個是 packer )。未來Docker引擎將繼續 改進 ,以拆分客戶端(Dockerfile)的構建原語(如添加文件、設置入口等)。 合并到1.8版本的工作 將會使得上述工作變得更加簡單,它將向配置管理供應商、愛好者及公司開放一塊試驗田。考慮到供應系統的歷史,認為一個標準會解決這些問題是不切實際的, 正如運行時那樣 。同樣,關于可伸縮鏡像構建這一特性當前也是相當不明確的。據我所知,并沒有人在積極地倡導,更糟糕的是,它已經有一年以上沒變化了。
垃圾回收
每個Docker的主要部署人員最終都會寫一個垃圾回收器(以下簡稱GC),來 移除主機中的舊鏡像 。這用到了各種方法,例如移除x天之前的鏡像,并確保至多只有y個鏡像存在主機中。Spotify最近 將它們開源 了。很早之前,我們也一樣實現了我們自己的GC。我能夠理解為GC實現一個可預見的UI有多難,但這確實是最需要的東西。大多數人發現他們只是在其產品容器的空間不足時,才偶爾需要GC。最后,你也將會遇到這個問題:傳到Docker registry的相同鏡像溢出為大鏡像[譯者:復數],好在,這個問題已經在 分布式規劃 中提出。
迭代速度以及內核狀態
在1.x的發行版中Docker引擎關注的是穩定性。Pre-1.5則是投入了少量工作來降低產品接入使用的門檻。開放容器生態模型是Docker成功所必須的,并且他們害怕破壞這個模型。當前,Docker迭代速度總是得忍受如下問題:每次UX變更都要經歷過多流程。Docker 1.7則是發布了網絡和存儲插件牽頭的 實驗性版本 。這些特性被明確標注為“不適用于產品”并且隨時可能會被移除或修改。對于那些已經押注Docker的公司而言,這是一則好消息,因為這將使得核心團隊對于新特性完成更快速的迭代,且無需關心“最佳實踐”中的向后兼容性。對于公司而言,修改Docker內核依舊很困難,因為這需要fork或是取得上游的采用,前者風險高,維護負擔重,后者對于那些有趣的補丁而言則顯得耗時費力。隨著1.7版插件模式的公布,處理這類問題的策略變得明晰:使每個可選組件都是可插拔的。這種“水果拼盤”式的哲學于 DockerCon Europe 2014 首次引入,盡管還很模糊。在6月的DockerCon,很高興地得知這個策略由作為第一縱列的 Plumbing團隊全權文檔化 (這對我是最重要的,因為海象是我最喜愛的海洋哺乳動物,而Plumbing正是以它作為 吉祥物 )[譯者:呵呵]。正如過去2年一樣,它現在依舊是一個痛處,即使未來終于看起來有點盼頭了。
日志記錄
日志是本可以早點改進而獲利的領域之一。它很難成為一個有吸引力的問題,但是它絕對是一個普遍性的問題。當前并沒有優秀且通用的解決方案。這里充斥著各種方案:tail日志文件,記錄到容器內,記錄到掛載的主機,記錄到主機的syslog,通過某些東西暴露日志(如 fluentd ),通過應用記錄到網絡,或者是記錄到一個文件并由另一個進程將日志發送到Kafka。 Docker1.6支持合并到內核中的日志驅動 ;然而,驅動被內核采納是 很不容易 的。1.7版本中, 進程外插件被實驗性支持 ,但是,令人失望的是,它并未附帶日志驅動。我認為這將是1.8版本的計劃,但并未找到任何官方記錄。就這點來看,供應商未來將能夠編寫它們自己的日志驅動。或許,社區內分享將會變得微不足道,而且大型應用將不再需要定制解決方案。
密鑰
在“頻繁而遍存”這一類目中,我們同樣找到了密鑰。大多數人在遷移容器時,依賴配置管理來 安全地提供機器上的密鑰 ;然而,因為密鑰而一直采用同樣的在配置管理路徑并不太理想。一個可選方案是通過鏡像來散布它們,但這具有安全隱患,并且會使得在開發、持續集成以及生產之間循環鏡像變得困難。最純粹的方案是通過網絡獲取密鑰,保持鏡像的文件系統無狀態。直到最近,這個領域還不存在什么面向容器的解決方案,但是即將有2款引人注目的密鑰代理開源,分別是 Vault 和 Keywhiz 。在Shopify,我們于一年半前 開發了ejson 來解決這個問題,它被用來管理JSON格式的非對稱加密密鑰文件;然而,它會對其運行環境做一定的假設,因此和密鑰代理相比,顯得不是那么通用(如果你感興趣,可以閱讀 這篇文章 )。
文件系統
Docker依賴于文件系統的寫時復制 CoW (LWN上關于聯合文件系統Union FS的 系列文章 ,它們使CoW成為可能)。這項特性確保當你擁有100個容器時,不需要對應的100倍磁盤空間。每個容器都只是在鏡像的頂部創建CoW層,它們只在改變原始鏡像的文件時才使用必要的磁盤空間。好的容器使用者會盡可能避免對容器內部的文件系統產生影響,因為這意味著使容器保持某種狀態,是為大忌。這種狀態應該被存儲在映射到主機的卷或者網絡上。此外,層級存儲能夠在部署時節省空間,因為鏡像通常是相似的,它們擁有一些共同的層。在Linux上支持CoW的問題在于它們太“新”了。在Shopify有幾百臺主機在重負荷下運行,這里有一些經驗:
- AUFS 曾發現在需要重新掛載時,整個分區被鎖住的情況。遲鈍且占用大量內存。代碼基巨大,難以閱讀,似乎這正是它不被上游接受的原因,因此需要一個定制的內核。
- BTRFS 因為du和ls不能工作,新的工具集有一定的學習曲線。和AUFS一樣,我們同樣發現了分區凍結和內核鎖死,不管我們如何更新內核版本。當接近磁盤空間上限時,BTRFS的行為變得不可預測,并且當我們擁有1000份CoW層(BTRFS中的子卷)時,BTRFS同樣會占用大量內存。
- OverlayFS 這在Linux3.18內核中并入,對于我們而言,它現在已經 相當穩定和快速 了。由于采用在inode間共享頁緩存,它 占用的內存少了很多 。不幸的是,它需要你運行最新的內核,而這些內核往往未被大多數發行版采用,因此通常需要我們自己來編譯一個。
所幸的是,對于Docker而言,Overlay很快就會普及了,而默認的AUFS對于產品而言依舊不太安全,根據我們的經驗,這種情況在運行大量節點時尤甚。很難說這里需要做些什么,因為大多數發行版并未配備兼容Overlay的內核( Overlay被提議為默認FS ,但是由于這里的原因最終被拒了)。雖然Overlay正是此領域的發展方向,不過我們還需等一陣子。
依賴于內核邊緣特性
正如Docker依賴前沿的文件系統那樣,它同樣依賴大量內核的新特性,也就是namespace和cgroup(不是新的但卻是最常用的)。這些特性(尤其是命名空間)還不夠成熟以在生產中廣泛應用。我們 偶爾 會遇到由于它們所造成的模糊的bug。在產品中,我們并未開啟網絡命名空間,因為我們曾經歷過一些軟鎖死的情況,并追蹤到它的實現,但沒能修復其上游。內存cgroup會使用 相當數量的內存 ,并且我們也收到過外界謂之不可靠的報告。隨著容器被越來越頻繁地使用,相信大公司應該會率先解決穩定性問題的。
我們曾遇到過的一個 頑固的例子 就是僵尸進程。一個容器運行在PID命名空間,意味著這個容器內的第一個進程的pid就是1。容器內的init需要有 獲知子進程結束 的特殊能力。當一個進程結束時,它并不會馬上從內核的進程數據結構中消失,而是成為一個僵尸進程。這保證了其父進程可以通過wait(2)來檢測其是否終止。然而,如果一個進程沒有父進程,那么它的父進程將被設置為init。當這個進程結束時,通過wait(2)獲知子進程的終止就成了init的工作,否則這個僵尸進程就永遠不會結束。通過這種方式,你便可以通過僵尸進程耗盡內核的進程數據結構。在基于進程的master/worker模型中,這種場景是十分普遍的。如果一個worker需要調用命令[原文:shell out],這會占用很長世間,master可能會通過SIGKILL終止worker對shell命令的等待(除非你使用 進程組并一次性kill整個組 ,但基本不這么做)。由于命令調用而Fork出來的進程之后會被init繼承。當它最終結束時,init需要對其使用wait(2)。Docker引擎可以通過PR_SET_CHILD_SUBREAPER來承認部分容器內的僵尸進程,這在 #11529 中有描述。
安全
對于容器而言,運行時的安全在某種情況下依舊是一個問題,并且要讓其對于產品足夠“堅固”是一個經典“雞與蛋”的安全問題。在我們的例子中,我們并不依靠容器提供任何額外的安全保證。不過,有些用例卻這么做了。出于這個原因, 大多數供應商依舊在虛擬機上運行容器 ,這種安全是經過考驗過的。我希望在未來10年內看到虛擬機絕跡,因為操作系統虛擬化贏得了這場“戰爭”,正如 某人曾經在Linux郵件列表中所說 :“我曾聽到過一種言論:管理程序的存在是操作系統無能的證據”。容器提供了介于虛擬機(硬件級虛擬化)和PaaS(應用層次)之間的 完美的中庸之道 。我知道更多對于運行時的改進已經完成,比如系統調用黑名單。圍繞鏡像的安全也已經 引起關注 ,但是Docker正在通過 libtrust 和 notary 改進這個問題,后者是 新的分布層 的一部分。
鏡像層和傳輸
在Docker的第一次迭代中,它對于鏡像構建、傳輸和運行時就走了一條捷徑。它并不是針對每個問題選取正確的工具,而是選擇了一個能夠解決所有情況的工具:文件系統層。這種抽象一直暴露到在產品中運行容器。[譯者:This abstraction leaks all the way down to running the container in production.大家自己理解下吧。。。]這是一種完全可以接受的最小成本的產品使用主義,但是其中的每個問題本都可以被更加有效地解決:
- 鏡像創建 這項工作可以用有向圖表示。它允許識別出可以緩存和并行化的部分,以完成快速的可預測的構建。
- 鏡像傳輸 它完全可以使用 二進制diff ,而不是使用鏡像層。這是一個已經研究了幾十年的課題。分布層和運行時層變得越來越分離,開放了這方面的優化空間。
- 運行時 應該只是做一個單獨的CoW層,而不是再次使用任意的鏡像層抽象。如果你正在使用一個聯合文件系統,比如AUFS,在第一次讀取時,你需要便利一個文件鏈表來組裝成最終的文件,緩慢且完全不必要的。
這個層次模型對于傳輸而言是一個問題(正如之前所述,對構建也一樣)。這意味著,您需要格外關心鏡像中的每一層有些什么東西,否則你可能輕易地需要為一個巨大的應用傳輸100多兆數據。如果在你的數據中心中有大量鏈接,這并不是什么大問題,但是我如果你希望使用諸如Docker Hub之類的注冊服務,這可是在開放網絡上傳輸的。 鏡像分布在繼續工作著 。這里有很多理由促使Docker公司讓其變得堅固、安全和迅速。正如構建一樣,我希望它能夠對插件開放,以形成一個好的解決方案。與構建器相反,如果有諸如bittorrent分布的特殊機制,人們應該能夠普遍認可。
總結
這里有意避免提及許多話題,諸如存儲、網絡、多租戶、業務流以及服務發現。Docker現在更需要的是讓更多人在生產環境中大規模使用容器。不幸的是,許多公司從一開始就 朝著在PaaS的光輝 下過分補償它們的技術棧。這種方式只適用于小規模產品,且計劃使用Docker進行綠場部署[譯者:從無到有],這種情況下基本不會遭遇產品的模糊性[譯者: 聯系灰場部署理解]。為了見到Docker在產品中更廣泛的應用,我們需要解決上面強調的問題,來支持Docker。
Docker置身于一個有趣的位置,它作為PaaS的接口進行探索,使得應用的網絡發現和服務發現不需要關心下層設施。這是一則好消息,因為正如Solomon所說,關于Docker最好的部分便是它讓人們就某些事達成共識。我們終于開始贊同除了鏡像和運行時之外的東西。
上述的話題,我都已經和Docker公司的好些人討論過了,并且GitHub Issue也有很多留給他們。這里我所嘗試做的只是,為最重要的領域提供一個可選的視角,來降低準入門檻。對于未來,我很興奮,但是我們依舊要做很多工作來使在產品中運行Docker更加可行。