從2015年5月初開始創業開發 HyperContainer (runV) 到現在,也快五年了,在這個時候還來寫一篇什麼是安全容器,顯得略有尴尬。不過,也正是經過這五年,越來越多到人開始感到,我需要它卻說不清它,這個時候來給大家重新解釋 “安全容器” 也正是時候。
緣起:安全容器的命名
Phil Karlton 有一句名言——
計算機科學界隻有兩個真正的難題——緩存失效和命名。
就容器圈而言,我相信命名絕對配得上這句話,這毫無疑問是一件讓老開發者沉默,讓新人落淚的事情。
僅就系統軟體而言,我們當今比較通行地稱為 Linux 容器(LinuxContainer)的這個概念,曾經用過的名字大概還有——jail, zone, virtualserver, sandbox... 而同樣,在早期的虛拟化技術棧裡,也曾經把一個虛拟機環境叫做容器。畢竟這個詞本身就指代着那些用來包容、封裝和隔離的器物,實在是太過常見。以至于,以嚴謹著稱的 Wikipedia 裡,這類技術的詞條叫做“系統級虛拟化”,進而回避了“什麼是容器”這個問題。
當2013年 Docker 問世之後,容器這個概念伴随着“不可變基礎設施”、“雲原生”這一系列概念,在随後的幾年間,以摧枯拉朽之勢颠覆了基于“軟體包+配置”的細粒度組合的應用部署困境,用簡單地聲明式政策+不可變容器就清爽地描述了軟體棧。應用怎麼部署似乎離題了,我們這裡想要強調的是——
雲原生語境下的容器,實質是“應用容器”——以标準格式封裝的,運作于标準作業系統環境(常常是Linux ABI)上的應用打包——或運作這一應用打包的程式/技術。
這個定義是我在這裡寫下的,但是它并不是我的個人意志,這是基于 OCI (Open ContainerInitiative) 規範這一共識的,這個規範規定了容器之中的應用将被放置在什麼環境中,如何運作,比如啟動容器根檔案系統上的哪個可執行檔案,用什麼使用者,需要什麼樣的 CPU、記憶體資源,有什麼外置存儲,有什麼樣的共享需求等等。
有了這一共識做基礎,我們來說安全容器,這又是一段命名血淚史。當年,我的聯合創始人趙鵬是用“虛拟化容器”命名的我們的技術的,為了搏人眼球,用了“Secure as VM, Fast asContainer”的大字智語,自此,被容器安全性問題戳中心坎的人們立刻用“Secure Container”來稱呼這類東西,一發而不可收。而我們的内心中,這項技術提供了一層額外的隔離,隔離可能意味着安全性中的一環,也意味着某些運維效率、某些優化可能或者其他的功能。實際上,給安全容器這樣的定義更合理——
一種運作時技術,為容器應用提供一個完整的作業系統執行環境(常常是LinuxABI),但将應用的執行與主控端作業系統隔離開,避免應用直接通路主機資源,進而可以在容器主機之間或容器之間提供額外的保護。
間接層:安全容器的精髓
安全問題的唯一正解在于允許那些(導緻安全問題的)Bug發生,但通過額外的隔離層來阻擋住它們。
—— LinuxCon NA 2015, Linus Torvalds
為了安全,為什麼要引入間接層?因為以 Linux 之類的目前主流主控端作業系統的規模,是無法從理論上驗證程式是沒有 Bug 的,而一旦合适的 Bug 被合适地利用,安全性風險就變成了安全性問題了,架構和修補并不能確定安全,是以,進行額外的隔離、縮減攻擊面,就成了緩解安全問題的法寶——我們不能確定沒有漏洞,但我們通過組合來減少漏洞被徹底攻破的風險。
Kata: 雲原生化的虛拟化
2017年12月,我們在 KubeCon上對外釋出了 Kata Containers 安全容器項目,這個項目的兩個前身——我們發起的的 runV 和Intel 發起的 Clear Container 都釋出于2015年5月(是的,早于上面 Linus 的引語)。這組項目的思路很簡單 ——
1. 作業系統本身的容器機制沒辦法解決安全性問題,需要一個隔離層;
2. 虛拟機是一個現成的隔離層,AWS這樣的雲服務已經讓全世界相信,對戶來說,"secure of VM" 是可以滿足需求的;
3. 虛機裡面隻要有個核心,就可以支援 OCI 規範的語義,在核心上跑個 Linux 應用這并不太難實作;
4. 虛機可能不夠快,阻礙了它在容器環境的應用,那麼可不可以擁有 "speed of container" 呢?
現在,如果最後一個問題可以解決,那麼它就是我們要的“安全的容器”了——這就是 Kata Containers。
目前 Kata Containers 通常是在 Kubernetes 環境中使用的,Kubelet 通過CRI 接口讓 containerd 或 CRI-O 執行運作時操作,通常鏡像操作由這些 CRI Daemon 來進行,而根據請求,把 Runtime 操作寫成一個 OCI Spec 交給 OCI Runtime 執行。這裡,對于 1.2 以上的 containerd 和 1.5 版本上後的 Kata Containers(對應 ),通常是利用 containerd 來完成的:
- 每個 Pod 會有一個 shim-v2 程序來為 containerd/CRI-O 執行各種運作時操作,這個程序和整個 Pod 的生命周期一緻,通過一個 ttRPC 接口為 containerd/CRI-O 提供服務;
- Shim-v2 會為 Pod 啟動一個虛拟機作為 PodSandbox提供隔離性,其中運作着一個 Linux 核心,通常這個 Linux 核心是一個裁剪過的核心,不會支援沒有必要的裝置;
- 這裡用的虛拟機可以是 Qemu 或是 Firecracker,如果是 Firecracker,那麼根本就沒有模拟的裝置,而如果是 Qemu,通過配置和更新檔,也會讓它盡量小一些,支援的其他虛拟機還包括 ACRN 和 cloud-hypervisor,未來後者可能會被越來越多的用到;
- 這個虛拟機在啟動的時候可能根本就是一個 initrd 而沒有完整作業系統,或者有個極小的作業系統,總之,它完全不是按照一台模拟機的作業系統來配置的,它隻是支撐容器應用運作的基礎設施,以及相關的應用環境 Metrics 采集或應用跟蹤調試需要的資源;
- 容器本身的 rootfs 會在 Sandbox 啟動之後,在收到 contaienrd/CRI-O 的建立容器的 OCI 請求之後,以熱插拔的方式動态插入到虛機中,容器的 rootfs 準備和虛機本身的啟動是可以并行的;
- 依照 CRI 語義和 OCI 規範,Pod 裡可以啟動多個相關聯的容器,它們被放在同一個虛機裡,并且可以互相共享 namespace;
- 外來的存儲卷可以以塊裝置或檔案系統共享的方式插入到 PodSandbox 中,但對于 Pod 裡的容器來說,它們都是加載好的檔案系統,目前開始逐漸普及的檔案系統方式是專為 Kata 這樣的場景設計的 virtio-fs,它不僅比傳統的 9pfs 更快、有更完整的 POSIX 檔案系統的支援,而且借由所謂 vhost-user 和 DAX 技術,它還可以在不同的 Pod 之間共享相同存儲内容的緩存頁 (page-cache),讓它們可以和普通的 runC 容器一樣,節約寶貴的記憶體;
- 對于網絡,使用 tcfilter,可以直接支援各種 CNI 的插件來提供容器網絡,這樣的好處是無需做什麼調整就自然的工作,但是效率上因為做一次橋接會有損失,在生産環境中,也可以考慮使用 enlightened 網絡模式,用一個特制的 CNI 插件來高效接入容器網絡。
可以看到,首先 Kata Containers 是個全功能的容器運作時引擎,它用起來完全不像是傳統虛機,分明就是個容器引擎,并且,通過“少用不必要的記憶體”和“共享能共享的記憶體”來降低記憶體的開銷,更小的記憶體不僅開銷更小,啟動也更輕快,對于大多數場景來說,這實作了"secure of VM, speed of container"。在安全性之外,它比起傳統的虛機,更具有容器的彈性,更少了機器的那種實體操作手感,我們把這種技術稱為“雲原生化虛拟化”或者“面向雲原生的虛拟化”技術。
gVisor: 程序級虛拟化
在 Kata Containers 之後半年的哥本哈根KubeCon 上,Google 開源了他們内部開發了五年的 gVisor 安全容器作為回應。
如果說 Kata Containers 是對通過對有隔離技術進行組合和改造來建構容器間的隔離層的話,gVisor 的設計顯然是更加簡潔的——gVisor 是一個用 Go 語言重寫的運作在使用者态的作業系統核心,稱為 Sentry,它并不依賴于虛拟機,相反,它借助“平台(Platform)”的能力,讓主控端把應用的所有通路都重新轉交給 Sentry,在 Sentry 中處理後再将一些必要的操作請主控端幫忙來完成。
可以說 gVisor 在做的是一個純粹的面向應用的隔離層,從 Linux ABI 到 Linux ABI 的“過濾器”。全新寫作的優勢在于不需要遷就太多已有技術棧的桎梏,可以寫得更輕,啟動肯定也會更快,事實上,資源的伸縮也更友善,或者說更容器一些。好多作業系統圈的朋友都毫不掩飾地說,他們更喜歡 gVisor 的架構,如果能解決一些目前不容易解決掉的問題的話。
gVisor 作為隔離層,它的安全性依據在于:
- 首先是攻擊面變小,宿主作業系統将隻為沙箱裡的應用執行大約20%的 Linux 系統調用。通過研究,gVisor 的作者們發現,大多數攻擊都是通過一些不常用系統調用中的缺陷進行的,不常用的系統調用的實作路徑一般也比較少被審閱,相對于那些熱路徑來說,安全性要更差一些,gVisor 的設計讓應用對那些不常用系統調用的通路根本不會落到主控端作業系統核心上,進而避免了大部分的攻擊;
- 其次是他們發現了最最常被攻擊到的系統調用是 open(),于是他們直接将真有必要的 open()調用交給了一個專門的稱為 Gopher 的程序來執行,進而可以更容易被限制、審計和管控;
- 最後,他們是用進階語言 Go 寫的核心,優勢當然是更加記憶體安全,當然,他們也坦誠,這個語言其實不太“系統級”,他們為此不得不做了很多手腳,也為 Go Runtime 貢獻了很多修改。
當然,gVisor 的架構是很漂亮,但重新實作一個核心這件事情,除了 Google 這樣的巨頭恐怕也沒有幾家可以做(類似的基本做到的也就是微軟的初代 WSL了),而且這個超前的設計還是有些現實問題:
- 首先,就是它不是 Linux,是以,在相容性方面和 Kata 這樣的解決方案尚有差距;
- 其次,對于目前的 Linux 系統調用方式和 CPU 指令系統,每個系統調用的攔截都會有相當的性能開銷,盡管全棧優化可以有一定緩解,但對系統調用比較多的場景來說,性能損失無法忽略。
是以,短時間内 gVisor 方案并不能成為一個終極解決方案,當然,它不僅可以适應一些特定的場景,而且帶來的啟示性可能對未來的作業系統乃至 CPU 指令集的演進發生作用,進而推動我們可以擁有一個更完美的安全容器解決方案。
安全容器:不止于安全
安全容器的隔離層讓應用的問題——不論是惡意攻擊,還是意外錯誤——都不至于影響主控端,也不會在不同的 Pod 之間互相影響。而且實際上,額外隔離層帶來的影響并不僅是安全,對于排程、服務品質和應用資訊的保護都有好處。
傳統的作業系統容器技術是核心程序管理的一個延伸,容器程序本身是一組關聯的程序,對于主控端的排程器來說是完全可見的,一個 Pod 裡的所有容器或程序,同時也都被主控端排程和管理。這就意味着,在有大量容器的環境下,主控端核心的負擔很重,在很多實際環境中已經可以觀察到這個負擔帶來的開銷了。而采納安全容器之後,從主控端上是看不到這些完整的資訊的,隔離層同時也降低了主控端的排程開銷,減少了維護負擔,避免了容器之間、容器和主控端之間的服務品質幹擾。從另一個方向看,安全容器作為一道屏障,可以讓主控端的運維管理操作不能直接通路到應用的資料,這樣,把使用者的應用資料直接保護在沙箱裡就可以降低對使用者的授權要求,保障使用者的資料私密性。
當我們的目光向投向未來,可以看到,安全容器不僅僅是在做安全隔離,因為安全容器隔離層的核心,相對于主控端的核心是獨立的,專門對應用服務,從這個角度說,主機/應用的功能之間做合理的功能配置設定和優化,展現出讓人期待的潛力,将來的安全容器,可能不僅是隔離性開銷的降低,甚至是提升應用的效能——
隔離,讓雲原生基礎設施更完美。
Kata Containers開源位址:
https://katacontainers.io/