Unikernel不適合生產環境
最近我犯了個錯,在推ter上語氣激昂的問是否該講講為什么unikernel不適合用在生產環境。結果響應十分強烈:有的人感覺unikernel走錯方向了,在尋找支持這種觀點的細節;有的人是unikernel的支持者,也很想知道反對unikernel的觀點是什么。顯然人們都想聽到反對在生產環境上使用unikernel的觀點究竟是什么。
那么,unikernel的問題究竟是什么?讓我們首先來看一下定義:unikernel是一個完全運行在微處理器的特權模式的應用程序(具體的術語可能不一樣:在x86上這被稱作運行在Ring 0)。即在unikernel中,完全沒有傳統意義上的應用程序;相反,應用程序的功能被拖拽到了操作系統內核中。(沒有操作系統(“no os”)的想法會誤導你;并不是沒有操作系統,而是應用程序已經擔負起了操作系統作為硬件接口的責任 - 這是一個完全的OS(“all os”),只不過是一個粗制的,一個貧血的操作系統。)在我們討論這樣做會面臨哪些挑戰的時候,有必要首先探索一下unikernel的背后的動機是什么,即使僅僅是因為那么一點點的好處...
選擇在操作系統內核中實現功能的主要原因是性能:通過消除在用戶-內核邊界之間的上下文切換,那些需要依靠在這個邊界進行切換來完成的操作就可以做到更快。對于unikernel,這些觀點看起來貌似是正確的:一方面現代的平臺運行時(runtime)的復雜度很高,一方面現代微處理器的性能很好。很少有人會留意到應用的性能受到了用戶-內核態的上下文切換產生的開銷的限制。并且這種觀點還可能站不住腳的地方在于,unikernel很大程度上依賴硬件虛擬化來實現多租戶等功能,這個事實也削弱了這些觀點的說服力。正如我 在過去的一篇文章中展開的,在硬件層的虛擬化有無法逃避的性能稅費:通過把可以看到硬件的系統(即hypervisor)與可以實際上看到應用程序的系統(借宿操作系統/guest OS)隔離開來,在硬件的利用率(比如DRAM,網卡,CPU、I/O等)方面的損失了效率,而且再多的意志力(willpower)和暴力(brute force)都無法彌補這種損失。但是,繼續在性能這一點上繼續糾纏下去沒有什么好處,那就讓我們讓我們認為unikernel在性能論這一點上占了上風,但是存在一些有堅實基礎的反面觀點,讓我們繼續討論。
另外一個unikernel的支持者給出的觀點是unikernel更加安全,但實在不清楚這背后的思想基礎是什么。是的,unikernel通常運行更少的軟件(因而攻擊面更小)- 但是unikernel并沒有什么什么本質上的東西一定會導出軟件更少的結果。是的,unikernel通常運行新開發的,與總不同的軟件(因而不會受到 OpenSSL 每周漏洞列表中出現的漏洞的威脅 ),但是這種因為模糊晦澀就會安全的觀點,可以套用到任何新出現的或者難以理解的系統上。這個安全觀點同時也似乎忽視了unikernel很大程度上依賴的保護邊界的事實:由底層hypervisor提供的在寄宿操作系統(guest OS)之間提供的保護邊界。 Hypervisor的漏洞是的確存在的;不能一方面渲染Linux內核的漏洞是如何的一種潛在威脅,一方面覺得hypervisor的漏洞只會存在于想像中。相反,不讓應用程序開發者使用用戶保護邊界的工具,破壞了 最小特權原則(Principle of least privilege):任何應用程序中的漏洞都會根植在unikernel中。在這個以容器為基礎進行部署的世界中,這會造成一個很棘手的問題 - 秘密管理(secret management) - 并且會讓其變得相當難以處理(并且有更高的風險)。在最好的情況下,uniknernel算得上是一個“安全戲劇”(注:security theater意思是那些旨在提升安全,或只想給人們制造一種更安全、一切盡在掌控之中的感覺,卻實際上對于提升安全性并沒有或者很少幫助的安全措施);在最糟的情況,這就是一個安全噩夢。
unikernel的支持者最后一個的觀點是unikernel體積小 - 但是,unikernel并沒有什么小的地方!拿我自己的經驗來說,我在 小的系統和 大的系統上面做過實現內核的事情;你當然可以有一個精簡的系統;而無需走上借助于unikernel來實現一種類似胃繞道手術一樣的效果。(我自己也喜歡在Alpine Linux非常精簡的用戶態基礎上來運行Linux程序和/或Docker容器。)并且根據unikernel沒有包含多少代碼的這種程度來說,這種感覺似乎更多是因為幼稚,而不是因為設計。但是僅僅通過代碼來衡量unikernel的體積是錯誤的,并且這里unikernel的支持者也忽略了更大型的系統的細節:因為unikernel以一個借宿操作系統來運行,由hypervisor為它分配的DRAM會被完全占用,即使應用自身沒有利用這些內存。因為內存耗盡仍然是導致應用崩潰的罪惡首要之一(特別是在動態環境中),受這個原因(需求)的驅使,內存大小調整很容易被過度工程化,通常采取的是盲目翻倍或者在相反的情況被吞并(slop up)。在unikernel的模型中,任何這種吞并的情況沒有了 - 沒有誰再能使用它,因為hypervisor不知道它有沒有真正地被使用。(這跟在容器中的情況完全不同,在容器中沒有被應用使用的內存可以被其他的容器使用,或者被系統自身所使用)。因此,當考慮了整個系統,unikernel的支持觀點變得更加單薄(如果沒有被完全否決掉的話)。
所以這些就是支持unikernel的原因:很大一部分是因為性能,然后一點安全戲劇,和一個軟件崩潰食譜( software crash diet)。正如這些理由不冷不熱所預示的,它們構成了unikernel的好消息的終點。事情從這里開始,都是壞消息:要獲得這些好處必須付出成本,不管這些好處是多么多么的脆弱。
unikernel的缺點從應用程序自身的運行機制開始。當操作系統的邊界被消除了,你可能也消除了應用程序和外部世界的接口,如網絡和持久存儲 - 但難道你就沒有使用這些接口的需求,你就完全沒這需要了?有些unikernel(如OSv和Rumprun)采用的是實現一個“POSIX類似”的接口來盡量減少對應用程序的影響。好消息是:app好像也可以工作。壞消息是:我們還未嘗提到這些應用程序 需要被移植!這里希望你的應用的“POSIX相似性”不會設計到一些老的的觀念,如創建一個進程:在unikernel中 沒有進程,如果你的應用程序依靠這個基礎的話,基本上你就是被囚禁了( 或者比囚禁了更糟)。
如果這種方法看起來比較邊緣,對于那些特定語言的unikernel,事情會變得更加糟糕,如 MirageOS這種特定語言的unikernel深嵌了一個特定的語言運行時。一方面,只允許用某一個類型安全的語言的實現可以讓一些嚴重的unikernel安全問題被解規避掉;但另一方面,保佑你所有你需要的都已經在OCam中有了!
因此要讓你的應用運行起來會有一些問題,假定你都已經都解決了這些問題:或者是因為你的unikernel暴露的POSIX接口(注:原文是surface)對于你的應用或者平臺來說足夠了,或者它已經是用OCam或者Erlang或者Haskell或者其他什么語言實現的。假如你有了可以用unikernel來運行的應用程序,現在到了一個最深刻的為什么 unikernel 不適合生產環境的理由 - 并且這個理由當真要部署任何真正在運行生產中的時候,(至少對于我來說)簡直直擊unikernel的心臟: Unikernel是完全不可調試的。沒有進程,因此也沒有netstat,沒有tcpdump,沒有ping。并且這些還只是已經有幾個世紀歷史的工具,那任何現代的工具,如DTrace或者MDB就更不用說了。站在調試的角度上,要說這是最基本的工具都言輕了:這豈止是石器時代,這簡直就是前寒武紀。我,作為一個全部職業生涯都在開發生產環境系統,和各種工具來調試這些系統的人,我發現這種隱含出的拒絕調試生產環境是讓人感到憤怒的,似乎在那些unikernel支持者中有更深讓人不爽的癥狀:完全沒有對運維的同理心(empathy)。生產環境問題就被簡單地揮揮衣袖撇開了 - 服務在不正常的時候只能被等著重啟。這種態度,盡管即使只是被推論出來并不廣為存在,對于任何曾經負責過運維一個系統的人來說很難是不讓人不冒火的。(假如你認為我是一個局外人,聽聽再 我在DockerCon 2015的演講中,在我強調系統出了問題后系統需要調試而不是僅僅被重啟后臺下的掌聲吧)。如果需要把話明說出來,這種態度讓人憤怒因為這是錯誤的:如果一個生產環境的應用開始工作不正常,原因在于一個非致命的情況,如如 監聽降低(listen drops),重啟應用是在可能的最遭時間(換句話說,在負載最高的情況下)中斷了應用,卻并沒有朝著找出問題的根源(一個不夠的未完成的工作)邁出任何一步。
那么,能在unikernel中實現一個生產環境的調試工具么?一個字,不可能。調試工具通常會跨越用戶-內核邊界,在利用命令行提供的特定工具進行查詢是最有成效的,然而提供這種工具的器官已被特意地從uninkernel中摘除了,名曰減肥(注:上文中提到的胃繞道手術就是一種減肥的手術);任何提供足夠復雜調試工具以供生產環境使用的unikernel都違反了自己的信條。unikerne不適合生產環境不僅僅因為其實現方式,同時也因為其被理解的方式:他們在生產環境工作不正常的時候會無法被理解 - 而且源于它們自己的堅持,它們永遠也做不到這一點。
盡管以上所有這些,我確實發現了一些和unikernel支持者一樣的觀點:我同意容器的革命需要一個更加精簡,更加安全,更加高效的runtime,而不僅僅是一個運行在虛擬硬件之上的一個共享的Linux 借宿操作系統(Linux guest OS) - 并且在 Joyent過去的幾年 ,我們的焦點一樣的在用SmartOS and Triton來達成這個目標。盡管我們和unikernel 的支持者看到了相似的問題,我們采取的方式有本質不同:與放棄在一個多租戶的基礎上運行容器的觀念不同,我們拿已經安全的 zone做為基石并且為其 添加了原生執行Linux 二進制文件的能力。即,我們選擇利用操作系統的進步成果,而不是忽視它們的存在,不但給Linux和Docker帶了安全裸機之上(on-the-metal)的容器,同時也帶來了操作系統的先進成果,如 ZFS和 Crossbow,也當然少不了DTrace。這些努力都值得最后一次重新強調:我們在生產環境上的努力體現在我們做的每件事情上,但特別應指出的是, 全面的工具來調試生產環境的操作系統 - 并且通過將這些工具鏈帶入Linux容器這個更廣闊的世界, Triton已經允許在生產環境中的調試,而這在之前是不敢想的。。
我認為,終有一日,unikernel最有成效的結果還是得益于它產生的負面效果:它會作為一個很好的示例,來證明它們的方法對于生產系統是不切實際的。照此,它們會跟 事務型內存( transactional memory ), m-to-n調度模型一起,成為曾風靡一時,但因現實無情的細節,而死掉的斷氣軟件。但是沒必要把我的話當真,正如我在tweet中提到的,不可調試的系統自身就如同種下一個惡果 - 只是自己受用就好,就不要加害其他蕓蕓眾生了。
( 原文鏈接:Unikernels are unfit for production,翻譯:鐘最龍)
來自: http://dockone.io/article/987