文章目錄
- 1. 容器發展
- 2. KataContainers原理
- 3. gVisor原理
- 4. 對比
1. 容器發展
使用虛拟化技術來做一個像 Docker 一樣的容器項目,并不是一個新鮮的主意。早在 Docker 項目釋出之後,Google 公司就開源了一個實驗性的項目,叫作
novm
。這,可以算是試圖使用正常的虛拟化技術來運作 Docker 鏡像的第一次嘗試。不過,
novm
在開源後不久,就被放棄了,這對于 Google 公司來說或許不算是什麼新鮮事,但是 novm 的昙花一現,還是激發出了很多核心開發者的靈感。
是以在 2015 年,幾乎在同一個星期,
Intel OTC (Open Source Technology Center)
和國内的 HyperHQ 團隊同時開源了兩個基于虛拟化技術的容器實作,分别叫做
Intel Clear Container
和
runV
項目。
而在 2017 年,借着 Kubernetes 的東風,這兩個相似的容器運作時項目在中立基金會的撮合下最終合并,就成了現在大家耳熟能詳的
Kata Containers
項目。 由于 Kata Containers 的本質就是一個精簡後的輕量級虛拟機,是以它的特點,就是“像虛拟機一樣安全,像容器一樣靈活”。
而在 2018 年,Google 公司則釋出了一個名叫
gVisor
的項目。gVisor 項目給容器程序配置一個用 Go 語言實作的、運作在使用者态的、極小的“獨立核心”。這個核心對容器程序暴露 Linux 核心 ABI,扮演着“Guest Kernel”的角色,進而達到了将容器和主控端隔離開的目的。
難看到,無論是 Kata Containers,還是 gVisor,它們實作安全容器的方法其實是殊途同歸的。這兩種容器實作的本質,都是給程序配置設定了一個獨立的作業系統核心,進而避免了讓容器共享主控端的核心。這樣,容器程序能夠看到的攻擊面,就從整個主控端核心變成了一個極小的、獨立的、以容器為機關的核心,進而有效解決了容器程序發生“逃逸”或者奪取整個主控端的控制權的問題。這個原理,可以用如下所示的示意圖來表示清楚。

而它們的差別在于,
Kata Containers
使用的是傳統的虛拟化技術,通過虛拟硬體模拟出了一台“小虛拟機”,然後在這個小虛拟機裡安裝了一個裁剪後的 Linux 核心來實作強隔離。
而 gVisor 的做法則更加激進,Google 的工程師直接用 Go 語言“模拟”出了一個運作在使用者态的作業系統核心,然後通過這個模拟的核心來代替容器程序向主控端發起有限的、可控的系統調用。接下來,我就來為你詳細解讀一下
KataContainers
和
gVisor
具體的設計原理。
2. KataContainers原理
我們前面說過,Kata Containers 的本質,就是一個輕量化虛拟機。是以當你啟動一個 Kata Containers 之後,你其實就會看到一個正常的虛拟機在運作。這也就意味着,一個标準的虛拟機管理程式(
Virtual Machine Manager, VMM
)是運作
Kata Containers
必備的一個元件。在我們上面圖中,使用的 VMM 就是
Qemu
。
而使用了虛拟機作為程序的隔離環境之後,
Kata Containers
原生就帶有了 Pod 的概念。即:這個 Kata Containers 啟動的虛拟機,就是一個 Pod;而使用者定義的容器,就是運作在這個輕量級虛拟機裡的程序。在具體實作上,Kata Containers 的虛拟機裡會有一個特殊的 Init 程序負責管理虛拟機裡面的使用者容器,并且隻為這些容器開啟 Mount Namespace。是以,這些使用者容器之間,原生就是共享 Network 以及其他 Namespace 的。
此外,為了跟上層編排架構比如 Kubernetes 進行對接,Kata Containers 項目會啟動一系列跟使用者容器對應的 shim 程序,來負責操作這些使用者容器的生命周期。當然,這些操作,實際上還是要靠虛拟機裡的 Init 程序來幫你做到。而在具體的架構上,Kata Containers 的實作方式同一個正常的虛拟機其實也非常類似。這裡的原理,可以用如下所示的一幅示意圖來表示。
可以看到,當 Kata Containers 運作起來之後,虛拟機裡的使用者程序(容器),實際上隻能看到虛拟機裡的、被裁減過的
Guest Kernel
,以及通過
Hypervisor
虛拟出來的硬體裝置。
而為了能夠對這個虛拟機的 I/O 性能進行優化,
Kata Containers
也會通過 vhost 技術(比如:
vhost-user
)來實作
Guest
與
Host
之間的高效的網絡通信,并且使用
PCI Passthrough
(PCI 穿透)技術來讓 Guest 裡的程序直接通路到主控端上的實體裝置。這些架構設計與實作,其實跟正常虛拟機的優化手段是基本一緻的。
3. gVisor原理
相比之下,gVisor 的設計其實要更加“激進”一些。它的原理,可以用如下所示的示意圖來表示清楚。
gVisor
工作的核心,在于它為應用程序、也就是使用者容器,啟動了一個名叫
Sentry
的程序。 而 Sentry 程序的主要職責,就是提供一個傳統的作業系統核心的能力,即:**運作使用者程式,執行系統調用。**是以說,Sentry 并不是使用 Go 語言重新實作了一個完整的 Linux 核心,而隻是一個對應用程序“冒充”核心的系統元件。
在這種設計思想下,我們就不難了解,Sentry 其實需要自己實作一個完整的 Linux 核心網絡棧,以便處理應用程序的通信請求。然後,把封裝好的二層幀直接發送給 Kubernetes 設定的 Pod 的
Network Namespace
即可。
此外,
Sentry
對于
Volume
的操作,則需要通過
9p
協定交給一個叫做
Gofer
的代理程序來完成。Gofer 會代替應用程序直接操作主控端上的檔案,并依靠
seccomp
機制将自己的能力限制在最小集,進而防止惡意應用程序通過 Gofer 來從容器中“逃逸”出去。
而在具體的實作上,
gVisor
的
Sentry
程序,其實還分為兩種不同的實作方式。這裡的工作原理,可以用下面的示意圖來描述清楚。
第一種實作方式,是使用 Ptrace 機制來攔截使用者應用的系統調用(System Call),然後把這些系統調用交給 Sentry 來進行處理。這個過程,對于應用程序來說,是完全透明的。而 Sentry 接下來,則會扮演作業系統的角色,在使用者态執行使用者程式,然後僅在需要的時候,才向主控端發起 Sentry 自己所需要執行的系統調用。這,就是 gVisor 對使用者應用程序進行強隔離的主要手段。不過, Ptrace 進行系統調用攔截的性能實在是太差,僅能供 Demo 時使用。
而第二種實作方式,則更加具有普适性。它的工作原理如下圖所示。
在這種實作裡,
Sentry
會使用 KVM 來進行系統調用的攔截,這個性能比 Ptrace 就要好很多了。當然,為了能夠做到這一點,Sentry 程序就必須扮演一個
Guest Kernel
的角色,負責執行使用者程式,發起系統調用。而這些系統調用被 KVM 攔截下來,還是繼續交給 Sentry 進行處理。隻不過在這時候,
Sentry
就切換成了一個普通的主控端程序的角色,來向主控端發起它所需要的系統調用。
可以看到,在這種實作裡,Sentry 并不會真的像虛拟機那樣去虛拟出硬體裝置、安裝 Guest 作業系統。它隻是借助 KVM 進行系統調用的攔截,以及處理位址空間切換等細節。值得一提的是,在 Google 内部,他們也是使用的第二種基于
Hypervisor
的
gVisor
實作。隻不過 Google 内部有自己研發的
Hypervisor
,是以要比 KVM 實作的性能還要好。
4. 對比
通過以上的講述,相信你對 Kata Containers 和 gVisor 的實作原理,已經有一個感性的認識了。需要指出的是,到目前為止,gVisor 的實作依然不是非常完善,有很多 Linux 系統調用它還不支援;有很多應用,在 gVisor 裡還沒辦法運作起來。 此外,
gVisor
也暫時沒有實作一個 Pod 多個容器的支援。當然,在後面的發展中,這些工程問題一定會逐漸解決掉的。
另外,你可能還聽說過 AWS 在 2018 年末釋出的一個叫做
Firecracker
的安全容器項目。這個項目的核心,其實是一個用 Rust 語言重新編寫的 VMM(即:虛拟機管理器)。這就意味着,
Firecracker
和
Kata Containers
的本質原理,其實是一樣的。隻不過, Kata Containers 預設使用的 VMM 是 Qemu,而 Firecracker,則使用自己編寫的 VMM。是以,理論上,Kata Containers 也可以使用 Firecracker 運作起來。