原文:Making Containers More Isolated: An Overview of Sandboxed Container Technologies
摘要
既然主流 IT 工業都在采用基於容器的基礎設施(雲原生方案),那么了解這一技術的短板就很重要了。Docker、LXC 以及 RKT 等傳統容器都是共享主機操作系統核心的,因此不能稱之為真正的沙箱。這些技術的資源利用率很高,但是受攻擊面積和潛在的攻擊影響都很大,在多租戶的雲環境中,不同客戶的容器會被同樣的進行編排,這種威脅就尤其明顯。主機操作系統在為每個容器創建虛擬的用戶空間時,不同容器之間的隔離是很薄弱的,這是造成上述問題的根本原因。基於這樣的現狀,真正的沙箱式容器,成為很多研發工作的焦點。多數方案都對容器之間的邊界進行了重新架構,以增強隔離。本文覆蓋了四個項目,分別來自於 IBM、Google、Amazon 以及 OpenStack,幾個方案的目標是一致的:為容器提供更強的隔離。IBM Nabla 在 Unikernel 的基礎上構建容器;Google 的 gVisor 為運行的容器創建一個特定的內核;Amazon 的 Firecracker 是一個超輕量級的沙箱應用管理程序;OpenStack 將容器置入特定的為容器編排平台優化的虛擬機之中。下面對幾個方案的概述,有助於讀者應對即將到來的轉型機會。
目前的容器技術
容器是一種對應用進行打包、分享和部署的現代化方式。與把所有功能打包為單一軟件的單體應用相反,容器化應用或微服務的設計目標是專注於單一任務。容器中包含要完成這一任務所需的所有依賴項目(包、庫和一些二進制文件)。正因如此,容器化應用是平台無關的,能夠在任何操作系統上運行,並不在意其版本或者已部署軟件。這給開發人員帶來了極大的方便,再也不用為不同的客戶和平台准備不同版本的軟件了。因此也有了一個不太准確的想法:把容器當成了“輕量級虛擬機”。當容器在主機上完成部署之后,每個容器的資源,例如文件系統、進程和網絡棧都會被安置在一個虛擬的隔離環境之中,其它容器無法訪問這一隔離環境。這個技術能夠在一個集群內同時運行幾百或幾千個容器,容器化應用能夠輕松的通過復制容器實例的方式進行伸縮。
目前容器技術的發展,得益於兩項技術的進步:Linux 命名空間以及 Linux Control Group(cgroup)。命名空間虛擬隔離的用戶空間,並且給應用分配獨立的系統資源,例如文件系統、網絡棧、進程號以及用戶編號等。在這個隔離的用戶空間中,PID = 1 的應用程序控制了文件系統的根,並可以用 root 的身份運行。這個抽象的用戶空間允許每個應用都不受同一主機上運行的其它應用的影響。目前有六個可用的命名空間:mount
、inter-process communication
(ipc)、UNIX time-sharing system
(uts)、process id
(pid)、network
以及 user
。另外還提議了兩個額外的命名空間,分別是 time
和 syslog
,但是 Linux 社區還在對其規范進行定義。Cgroups 為應用進行硬件的限制、優先級、審計和控制。CPU、內存、設備和網絡都是硬件資源。當把命名空間和 cgroup 組合起來,我們就可以在單一主機上,安全的運行多個應用,並且其中的每個應用都有各自的隔離環境,這是容器的的根本。
虛擬機和容器之間的區別是,虛擬機是硬件層的虛擬化,而容器是操作系統級的。虛擬機管理器(VMM)為每個虛擬機模擬一個硬件環境,容器運行時則為每個容器模擬一個操作系統。虛擬機共享主機的物理硬件,容器會共享主機操作系統的內核以及物理硬件。因為容器從主機上共享的資源更多,它們對存儲、內存以及 CPU 的利用比虛擬機更加有效。然而共享越多,其代價就是容器之間、容器和主機之間的信任邊界就越模糊。圖 1 中描述了虛擬機和容器的架構差異。
相對於命名空間隔離技術而言,虛擬化硬件隔離通常會有更好的安全邊界。容器(進程)中逃出的攻擊者,往往比虛擬機中逃出的攻擊者具有更大的威脅。命名空間和 cgroup 的弱隔離是造成這種風險的原因。Linux 為每個進程中加入新的屬性字段,通過這種方式實現了命名空間和 cgroup。這些在 /proc
文件系統中的字段會告訴主機操作系統,一個進程是否能看到其它進程,或者這個進程能夠使用的 CPU 或內存的預算。如果從主機操作系統上查看運行中的進程和線程(例如 top
和 ps
命令),容器進程看起來和主機上的其它進程都是很相似的。一般來說 LXC 或者 Docker 這樣的傳統容器,在同一主機上運行時,會共享統一主機的同一內核,因此不能稱其為沙箱。例如 CVE-2014-3519、CVE-2016-5195、CVE-2016-9962、CVE-2017-5123 以及 CVE-2019-5736,都會從容器中越獄而出。多數內核漏洞都適用於容器逃逸,這是因為內核漏洞通常會導致權限升級,最終允許受攻擊的進程在原命名空間之外獲得控制權。除了經由軟件漏洞進行的攻擊之外,錯誤的配置,例如部署一個具備過高權限(例如 CAP_SYS_ADMIN
、privileged
)的容器,或者關鍵掛載點(例如 /var/run/docker.sock
)都可能引發容器逃逸。想在多租戶集群中部署容器、或者把包含有敏感數據的容器和其它不受信容器部署在同一主機上,就要考慮一下發生災難性后果的可能性了。
這些安全性方面的擔憂,促使研究人員為容器構建了更強的信任邊界。具體的解決方式就是創建一個真正的沙箱容器,盡可能的從主機操作系統中隔離開來。多數解決方案都是一種混合架構,在虛擬機的強信任邊界和容器的高效率之間嘗試取得平衡。在成文之時,還沒有任何一個項目成熟到能夠成為標准,但未來的容器發展毫無疑問地會采納其中一些有用的概念。本文的剩余部分會討論幾個有前途的項目,並對它們的特點進行比較。
我們會從 Unikernel 開始,它是最早的一個單一目標虛擬機的嘗試,它把應用和最小化的操作系統庫打包成為單一鏡像。很多致力於創建安全、低耗的最優化虛擬機鏡像的未來項目,都以 Unikernel 的概念為基礎。然后我們會看看 IBM 的 Nabla,這個項目的目標是像容器一樣的運行 Unikernel 應用;接下來是 Google gVisor,它在用戶空間的內核中運行容器。在這兩個類 Unikernel 項目之后,我們把目光轉向以虛擬機為基礎的容器方案,Amazon 的 Firecracker 以及 OpenStack Kata。最后一節的結論中,會對所有的方案進行比較。
Unikernel
虛擬化技術讓雲計算成為可能。Xen 以及 KVM 這樣的 VMM 是 AWS 和 GCP 的基石之一。雖然現代 VMM 能在單一集群內處理幾百個虛擬機,然而傳統的通用操作系統構建出來的虛擬機通常沒有為在虛擬化環境中的運行進行優化。通用操作系統的設計目標是盡可能支持更多類型的應用程序,所以它的核心會包含所有類型的驅動、協議以及調度器。然而當前雲中運行的虛擬機通常是被單一應用獨占的,例如 DNS、代理服務器或者數據庫。每個應用都只依賴於一小部分內核功能,閑置的內核功能不但浪費了系統資源,還擴大了攻擊面積。更多的代碼,就要面對更多的安全和隱患和 Bug。這種現狀促使計算機科學家們用最小化的內核功能來支持單一應用,從而設計出了單一用途的操作系統。
操作系統研究者們在 90 年代提出了 Unikernel 的概念。Unikernel 是一個特別的單尋址空間的虛擬機鏡像,能夠直接運行在 VMM 上。它把應用程序及其依賴的內核功能打包到一個鏡像之中。 Nemesis和 Exokernel 是 Unikernel 最早的兩個學術項目。圖 2 描述了 Unikernel 虛擬機鏡像創建和部署的過程。
Unikernel 把內核拆分為多個庫,只將應用依賴的庫打包到鏡像里。跟虛擬機類似,Unikernel 在虛擬機 VMM 上工作。低耗的 Unikernel 能夠快速的啟動和擴容。Unikernel 的突出特點就是安全、低耗、高度優化和快速啟動。Unikernel 鏡像只包含應用依賴的庫,如果不做特別要求,其中甚至連 Shell 都沒有,這就減小了受攻擊面積。不但是攻擊者缺乏落腳點,就算是有定制失誤的鏡像,其影響范圍也只在它自己的實例之中。Unikernel 鏡像只有幾兆,因此能在幾十毫秒內完成啟動,還可以在單一主機上運行幾百個實例。用單一尋址空間代替多數現代操作系統中使用的多級頁表,Unikernel 應用的內存訪問延遲比虛擬機中更低。由於應用是在構建鏡像時進行編譯的,編譯器能夠進行更多的靜態類型檢查,從而優化二進制文件的效率。
Unikernel.org 維護了一個 Unikernel 項目的列表。即便是具有這樣鮮明的特點,Unikernel 還是沒有獲得太多關注。Docker 2016 年收購了一家 Unikernel 的初創公司,大家認為 Docker 可能要把容器打包到 Unikernel 里面。三年后,還是沒有出現任何集成的跡象。進展緩慢的重要原因之一就是,還沒有成熟的構建 Unikernel 應用的成熟工具,大多數 Unikernel 應用只能在特定 VMM 中運行。另外要把應用移植到 Unikernel 上,可能需要針對不同語言進行定制,並且手動選擇依賴的內核庫。Unikernel 中的監控和排錯即使能做得到,也會對性能造成很大影響。所有這些限制,都降低了開發者向 Unikernel 遷移的意願。容器和 Unikernel 有很多相似之處。它們都是單一用途的只讀鏡像,意味着鏡像中的組件無法更新或補丁,要更新應用就必須更新鏡像。今天的 Unikernel 有點像前 Docker 時代:沒有容器運行時可用,開發者必須使用 chroot
、unshare
和 cgroup
等基礎工具來把應用放入沙箱。
IBM Nabla
IBM 的研究者們提出了 Unikernel as process 的點子:Unikernel 應用以進程的形式在特定的虛擬機系統中運行。IBM 的 Nabla Containers 項目,用面向 Unikernel 的 Nabla Tender 替換了 QEMU 這樣的通用 VMM,進一步強化了 Unikernel 的信任邊界。Unikernel 和通用 VMM 之間的 Hypercall 還是一個很大的受攻擊面積,所以針對 Unikernel 的 VMM 可以使用更少的系統調用,從而顯著的提高安全性。Nabla Tender 攔截 Unikernel 發送給 VMM 的 Hypercall,並翻譯為系統調用。Linux Seccomp 策略會阻斷所有 Tender 不需要的系統調用。Unikernel 和 Nabla Tender 以用戶空間進程的形式在主機上運行。圖 3 展示了 Nabla 在 Unikernel 應用和主機之間創建瘦接口的過程。
研究者聲稱,Nabla Tender 和主機的通信使用了不到 7 個系統調用。由於系統調用是用戶空間進程和操作系統內核之間的橋梁,越少的系統調用,就越難攻擊到核心。把 Unikernel 運行為進程還有個好處就是使用 gdb 這類基於進程的調試器進行調試。
為了和容器編排平台對接,Nabla 還提供了符合 OCI 運行時標准的 Nabla 運行時 runnc
。OCI 運行時標准規范了運行時客戶端(例如 Docker 和 Kubelet)以及運行時(例如 Runc)之間的 API。Nabla 還提供了一個鏡像構建器,用於創建能夠使用 runnc 運行的 Unikernel 鏡像。Unikernel 和傳統容器的文件系統之間存在差異,因此 Nabla 沒有遵循 OCI 的鏡像標准,換句話說, Docker 鏡像和 runnc 是不兼容的。在本文寫作期間,這個項目還在早期試驗階段,還有一些功能缺失,例如加載/訪問主機文件系統的能力、加入多網卡的能力(Kubernetes 需要)或者從其它 Unikernel 鏡像進行引用的能力。
Google gVisor
Google gVisor 是 GCP App Engine、Cloud Functions 和 CloudML 中使用的沙箱技術。Google 意識到在公有雲基礎設施中運行不受信容器的風險,以及虛擬機沙箱的低效,因此開發了用戶空間的內核作為沙箱來運行不受信應用。gVisor 通過攔截所有從應用到主機內核的系統調用,並使用用戶空間中 gVisor 的內核實現來處理這些調用。本質上來說,gVisor 是 VMM 和客戶內核的組合,圖 4 展示了 gVisor 的架構。
gVisor 在應用和主機之間建立了穩固的安全邊界。這個邊界限制了應用在用戶空間的系統調用。無需依賴虛擬硬件,gVisor 以主機進程的方式運行,充當主機和應用之間的沙箱。哨兵實現了多數的 Linux 系統調用,尤其是內核功能,例如信號分發、內存管理、網絡棧以及線程模型。哨兵已經實現了 319 個 Linux 系統調用中的 70% 多,用於為沙箱應用提供支持。哨兵和主機內核的通信只使用了不到 20 個 Linux 系統調用。gVisor 和 Nabla 有很相似的策略:保護主機。它們都使用了不到 10%的系統調用來和主機內核通信。gVisor 創建通用核心,而 Nabla 依賴的是 Unikernel,它們都是在用戶空間運行特定的客戶內核來支持沙箱應用的運行。
有人可能會奇怪,開源的 Linux 內核已經如此穩定,為什么 gVisor 還要重新實現一個。gVisor 的內核使用的是 Golang,其中的強類型安全以及內存管理都比 C 編寫的 Linux 內核更安全。gVisor 的另外一個重要賣點就是它和 Docker、Kubernetes 以及 OCI 標准的緊密集成。把運行時修改為 gVisor runsc,就能拉取和運行大多數的 Docker 鏡像了。在 Kubernetes 里,可以把整個 Pod(而非每個容器分別)運行在 gVisor 沙箱中。
gVisor 還在嬰兒期,也一樣有一些限制。gVisor 要攔截和處理沙箱應用中的系統調用,總要有一定開銷,因此不適合系統調用繁重的應用。注意 Nabla 沒有這個開銷,這是因為 Unikernel 應用不進行系統調用。Nabla 只使用 7 個系統調用來處理 Hypercall。gVisor 沒有直接的硬件訪問(透傳),所以如果應用需要硬件(例如 GPC)訪問,就無法在 gVisor 上運行。最后,gVisor 沒有實現所有的系統調用,因此使用了未實現系統調用的應用是無法在 gVisor 上運行的。
Amazon Firecracker
Amazon Firecracker 用於 AWS Lambda 和 AWS Fargate。它是一個 VMM,會創建輕量級虛擬機(MicroVM),特別適合多租戶容器和無服務器場景。在 Firecracker 出現之前,Lambda 和 Fargate 都在每個客戶獨立的 EC2 虛擬機上運行,從而保證強隔離。雖然在公有雲中,虛擬機的強隔離性要優於容器,但是使用通用的 VMM 和虛擬機來做應用沙箱是很不經濟的。Firecracker 為雲原生應用定制了 VMM,兼顧了安全和性能兩方面問題。Firecracker VMM 為每個客戶虛擬機提供了最小操作系統功能,並且模擬設備來增強安全和性能。可以用 Linux 內核以及 ext4 文件系統輕松的構建運行在 Firecracker 之上的虛擬機鏡像,Amazon 在 2017 年開始開發 Firecracker,並在 2018 年開源。
和 Unikernel 概念類似,只有一個功能和設備的小子集可以用於容器操作。和傳統虛擬機對比,microVM 在受攻擊面積、內存消耗和啟動時間方面都很有優勢。評估表明,Firecracker 的 microVM,運行在 2CPU 和 256G 內存的主機上,消耗不到 5MB 內存,啟動大約用了 125ms。圖 5 展示了 Firecracker 架構以及它的安全邊界。
Firecracker VMM 依賴於 KVM,每個 Firecrfacker 實例都以用戶空間進程的方式運行。每個 Firecracker 進程都被 seccomp、cgroup 和命名空間策略鎖定,因此它的系統調用、硬件資源、文件系統和網絡活動都被嚴格限制。每個 Firecracker 進程中都有多個線程。API 線程作為客戶端和主機以及 microVM 之間的控制平面。VMM 線程呈現了一個 virtIO 設備的最小集(網絡和塊設備)。Firecracker 只提供了 4 個模擬設備給 microVM:virtio-block、virtio-net、串口控制台以及一個用於停止 microVM 的只有一個按鈕的鍵盤。為了安全性考慮,虛擬機不提供和主機分享文件的機制。主機上的數據(例如容器鏡像),通過塊設備暴露給 microVM。虛擬機的網絡接口由網橋上的 TAP 設備提供支持。所有的出棧數據包都會拷貝到 TAP 設備,並受 cgroup 策略的速率限制。安全邊界的層次最大程度的降低了用戶應用之間互相干擾的可能性。
目前為止,Firecracker 還沒有完全和 Docker 以及 Kubernetes 完成集成。Firecracker 不支持硬件透傳,所以需要 GPU 以及任何設備加速訪問的應用都無法兼容。它限制了虛擬機和主機的文件共享以及網絡模型。然而這個項目有強力的社區支持,應該很快會和 OCI 標准打通並支持更多應用。
OpenStack Kata
出於對傳統容器安全性的擔憂,Intel 在 2015 年啟動了它們以虛擬機為基礎的容器技術:Clear Container。Clear Container 依賴 Intel VT 的硬件虛擬化技術以及高度定制的 QEMU-KVM(qemu-lite)來提供高性能的基於虛擬機的容器。在 2017 年,Clear container 項目加入了 Hyper RunV,這是一個基於 hypervisor 的 OCI 運行時,從而啟動了 Kata 容器項目。繼承了 Clear Container 的所有財產,Kata 現在支持更多的基礎設施和容器規范。
Kata 完整的集成了 OCI、CRI 以及 CNI,它支持多種網絡模型以及可配置的客戶內核,這樣一些有特別網絡需求或者內存版本限制的應用就可以得到支持了,圖 6 展示了 Kata 和現有編排平台的交互。
Kata 在主機上有一個 kata-runtime
,用於配置新容器。在 Kata VM 中的每個容器,在主機上都有對應的 Kata Shim。Kata Shim 從客戶端接收 API 請求(Docker 或 Kubelet),通過 VSock 轉發請求到 Kata 虛擬機中的代理。Kata 作出很多優化啟動時間的優化。NEMU 是一個輕量級的 QEMU,約有 80% 的設備和包被刪除。VM-Templating 克隆一個運行中的 Kata VM 實例,並分享給其它新啟動的 Kata VM。這一操作能夠顯著降低啟動時間以及內存消耗,但是可能受到跨虛擬機的邊緣通道攻擊,例如 CVE-2015-2877。熱插拔功能讓虛擬機以最小資源啟動(例如 CPU、內存、virtio block),並在有申請時加入額外的資源。
Kata 容器和 Firecracker 都是基於虛擬機的沙箱技術,也都是服務於雲原生應用的。但是它們用不同的方法來實現目標。Firecarcker 用一個特定的 VMM 來給客戶操作系統創建安全的虛擬化環境,而 Kata 是一個為運行容器而高度優化的輕量級虛擬機。有人已經嘗試在 Firecracker VMM 上運行 Kata。這個項目還在試驗階段,也許會把兩個項目的長處融為一體。
結論
我們已經看了多個用於解決容器隔離問題的方案。IBM Nabla 是一個基於 Unikernel 的方案,把應用打包為特別的虛擬機。Google gVisor 是特制 VMM 和客戶操作系統核心的結合,提供了應用和主機之間的安全界面。Amazon Firecracker 是一個特定的 VMM,為每個客戶操作系統提供最小化的硬件和內核資源。Kata 是高度優化的虛擬機,內置了容器引擎,可以運行在 VMM 上。這些方案各有優劣,很難說那個更好。表格 1 中展示了一個針對重要功能的對比表。如果你有應用運行在 Unikernel 系統中,例如 MirageOS 或者 IncludeOS,Nabla 是最佳選擇。gVisor 是目前和 Docker 和 Kubernetes 集成最好的,但是因為系統調用實現不完整,有些應用可能無法運行。Firecracker 支持自定義的客戶操作系統鏡像,所以如果你的應用需要在特定虛擬機上運行,它是你的最佳方案。Kata 兼容 OCI,在 KVM 以及 Xen 上都能運行。它可以簡單的在混合環境中部署微服務。
雖然可能需要很長時間,才能看到有一個或多個解決方案最終被主流接受,但已經可以看到大多數雲廠商已采取行動來解決這些問題。對於構建本地雲原生平台的組織而言,它不是世界末日。快速修補、最小權限配置和網絡分段等常見做法都可以有效地減少被攻擊面。