gvisor


 5 月 2 日,谷歌發布了一款新型的沙箱容器運行時 gVisor,號稱能夠為容器提供更安全的隔離,同時比 VM 更輕量。容器基於共享內核,安全性是大家關注的一大要點,gVisor 的發布勢必將引來更多對容器隔離性的關注。那么 gVisor 在技術上如何實現隔離,其性能如何?隔壁阿里巴巴已經有人為你探索了 gVisor 的架構和組件,並作了性能分析。

 

   背景   

gVisor 的開源為安全容器的實現提供了一種新思路。所謂安全容器需要包含兩個要點:安全隔離與 OCI 兼容。如今,共用內核的容器在安全隔離上還是比較弱。雖然內核在不斷地增強自身的安全特性,但由於內核自身代碼極端復雜,CVE 漏洞層出不窮。2018 年至今,已經公開了 63 個與內核相關的安全漏洞,而 2017 年全年則是 453 個 [1]。

大家已經有了一個共識:要有良好的安全隔離,就要阻斷容器內程序對物理機內核的依賴。Hyper,Clear Container 以及二者合二為一的 KATA[2],就遵循了這個思路。KATA 容器本質上是個虛擬機,但兼容了 OCI 標准。每個容器都有一個自己的 Guest Kernel。KATA 通過對 Qemu 與 Guest Kernel 的深度優化,減弱了 VM 在啟動速度與運行效率上的劣勢。

gVisor 也隔離了容器內的惡意代碼對 Host Kernel 的訪問,但使用了另一種方法。我們將這種方法稱之為進程虛擬化。它在內核之外實現了一個“內核進程”,Sentry,它提供了大部分 Linux Kernel 的系統調用。並且通過巧妙的方式將容器內進程的系統調用轉化為對這個“內核進程”的訪問。Google 將 gVisor 定位為“Sandbox”,而不是安全容器,一個原因是 Sentry 還不能完全代替 Linux Kernel,部分系統調用還是要轉接到 Host Kernel 上。

Google 當前開源的 gVisor 版本更像是一個 Demo 版,在一些典型的應用場景下,性能比不上 KATA。但是我們把它的名字拆解來看:gVisor = Google’s new HyperVisor? 猜測 Google 還會有后招。

本文簡單分析了 gVisor 的技術實現。時間倉促,文中敘述難免有不妥甚至錯誤之處,歡迎大家拍磚。

架構分析-整體架構

gVisor 由 3 個組件構成:Runsc,Sentry 與 Gofer。類似 RunC 與 RunV,Runsc 是一種 Runtime 引擎,負責容器的創建與銷毀。Sentry 就是上文提到的那個“內核進程”,容器內程序的系統調用都由它進行處理。Gofer 是文件系統的操作代理,IO 請求都會由它轉接到 Host 上。這 3 個組件均由 Go 語言實現。這有利有弊:Go 語言開發較簡單,並且類型安全,但是卻限制了 gVisor 的性能。各組件間的關系如圖 1 所示。

 

 

 

    圖 1: gVisor 的架構

Runsc 的概念較容易理解,我們不再多說。基於 Runsc,可以通過這種方式啟動一個 gVisor 容器:

$ docker run --runtime=Runsc hello-gVisor

Sentry

Sentry 是 gVisor 的核心組件,它就像一個簡單的操作系統內核,提供了系統調用,進程管理,內存管理等功能。

 系統調用

gVisor 設計上的一個巧妙之處是它對系統調用的劫持方法。目前提供了兩種劫持模式:KVM 模式,與 ptrace 模式。ptrace 模式的性能不及 KVM 模式。因為應用的每個 SYSCALL 都需要通過 ptrace 訪問 Sentry。嚴格來說,它的安全性也不及 KVM 模式。嚴重懷疑 ptrace 模式是 Google 最初的 demo,本文不做過多分析。

在 KVM 模式下,gVisor 能夠截獲應用程序的每個系統調用,並將其轉交給 Sentry 進行處理。相比較 VM,我們看不到 Qemu 的身影,也看不到 Guest Kernel,Sentry 包攬了所有必要的操作。這種對虛擬化的實現方法,我們稱之為“進程級虛擬化”。

既然基於 KVM,Sentry 就有多種身份。有時它運行在 Guest 態的 Ring0, 此時它就像容器內應用的 Kernel。有時運行在 Host 態的 Ring 3,此時它就像 Host 上的一個普通進程。Sentry 處在什么狀態上,完全取決於它當前正在處理的工作。當它在處理容器內的系統調用時,就處於 Guest 態。而當它需要跟 Host Kernel 進行交互時,就會通過 HLT 指令陷回 Host 模式。

Sentry 目前約實現了 200 個左右的系統調用,而 Linux Kernel 則為 X86_64 提供了 318 個系統調用 (4.16 內核)。當應用調用了那些未被實現的系統調用時,Sentry 會直接報錯返回。由於系統調用尚未完備,導致部分軟件還不能無縫地運行在 gVisor 中。而且 gVisor 已支持的系統調用中,有若干還必須依賴 Host 內核。當處理這些系統調用時,Sentry 會陷回 Host 模式。

 Sentry 的進程管理

Sentry 承擔了一定的進程管理職責。啟動 gVisor 容器后,可以在 Host 上看到兩個 Runsc 進程。一個進程負責容器創建與 IO(Gofer),另一個向容器內的應用提供系統調用支持,也就是我們前面提到的“內核進程”。容器內所有的進程,都以 Runsc 進程的線程存在。這有點像 Qemu。Runsc 進程在啟動時會創建一定量的 vCPU 線程。

Sentry 復用了 go 語言的 GMP 模型 [3]。每個應用的線程均對應到 go 語言內置的 goroutine(參見 kernle.Task.Start 函數),即 G。go runtime 會根據情況,選擇是通過 Host 內核的原生 sys_clone 生成新的 M(工作線程)還是復用之前的。在這里 vCPU 即 P。最后 go runtime 調度器,將 goroutine、vCPU 和工作線程三者結合起來。M、P 線程的調度由 Host 內核來調用。

 內存管理

容器內應用的所有代碼均由 Runsc 進程進行映射,並代理執行。gVisor 為每個應用進程都維護了一個 Guest 頁表, Runsc 進程自身也有一個 Guest 頁表。這么做是出於安全性的考慮,隔離了各個進程以及 Runsc 之間的地址空間。避免它們在內存上相互踩踏,也避免了惡意代碼對 Runsc 的攻擊。但在這種架構下,應用程序每次觸發 syscall,都會伴隨着一次 Guest 頁表的切換。熟悉虛擬化的同學知道,這是一個非常可觀的開銷。

可以與之鮮明對比的是 Unikernel[4],在典型的 Unikernel 的架構下,所有的進程 / 內核均運行在同一個地址空間中,系統調用等同於一次函數調用。但是應用程序中的任何一個不小心都可能導致整個系統 core 掉。安全與性能永遠是個 Tradeoff。

網絡

gVisor 里面看到的網絡設備是由 docker 創建容器的時候創建的 veth pair,並且在容器內部將虛擬網絡設備改名為 eth0。這與普通的容器並沒有太大區別。

 

 

 

gVisor 提供兩種網絡通信的方式:

  1. 通過宿主機 TCP/IP 協議棧

  2. 通過 gVisor 實現的用戶態 TCP/IP 協議棧 (netstack)

默認配置是使用 netstack,如果需要更高的網絡性能可以通過修改配置文件切換到使用宿主機的 TCP/IP 協議棧。

通過 gVisor 的 netstack 網絡通信,gVisor 在捕獲到應用的程序的系統調用的時候,並不使用宿主機的系統調用接口而是調用 netstack 提供的 socket 接口, 在經過 netstack 協議棧之后,通過宿主機的 raw socket 的方式進行收發包。

通過宿主機 TCP/IP 協議棧進行網絡通信,其整個數據流跟原生容器一樣,唯一區別在於 gVisor 需要捕獲到安全容器內應用程序關於網絡的系統調用。例如, listen/accept/sendmsg 等等。之后再將其轉換成 host 的系統調用來進行網絡通信。

文件系統

 

 

 

gVisor 也跟 linux 一樣對文件系統做了一層抽象,提供了 VFS 層,在其之下分別實現具體的文件系統。有 9p,tmpfs,procfs,sysfs 等。

gVisor 兼容 OCI,因此它的 rootfs 的文件來源就來自容器 OCI 鏡像各層聚合以后的 rootfs。為了減少 Guest App 直接對 Host 系統調用的依賴,Sentry 使用了 9pfs。應用程序通過 9p 協議與 Runsc 進程通信(內部運行着 Gofer Server 的功能),通過 Runsc 間接地來對 Host 的 rootfs 進行操作。

gVisor 本身並未提供 Library,容器中的應用可以直接鏈接鏡像 rootfs 中的 Library。所有的 binary 無需重新編譯鏈接,確保了 gVisor 對已有程序的兼容性。

除此之外,Sentry 還開發了內部的 tmpfs,這主要是為了保證運行性能。如果應用程序的臨時文件也要經過 9pfs,性能上將無法忍受。Sentry 還模擬 Linux,開發了 /proc 與 /sys 文件系統中的部分文件,做到與 Linux 的兼容性。

性能測試-數據對比

我們利用 memcached 與 mysql 對比了 gVisor 與普通容器, RunV (類似 KATA),與 AliUK(阿里內部自研的下一代執行單元)。

 

 1. memcached: memcached 的主要開銷在網絡上。gVisor 在 memcached 的    性能的差距是由於它自身的協議棧未被優化過。

 

 

gVisor(ptrace)

gVisor(kvm)

runc

runv

aliuk

memcached

11.8M/s

13.5M/s

66.5M/s

57.8M/s

82.7M/s

 

如上為幾種模型對 memcached 的 net_rate 指標,數值越大性能越好,發現 gVisor(ptrace) 確實小於 gVisor(kvm), 並且明顯低於 runc/runv/aliuk;aliuk 的性能最佳。

  

 2. mysql:在我們的測試中,mysql 主要開銷在 IO 處理。gVisor 的根文件系 .   統通過 9p 協議訪問 Host 上的文件。9p 協議性能相比 runc 容器本地 fs 性   能,甚至是 runv 和 aliuk 的 qcow2 的虛擬磁盤性能,都要差很多。

 

 

gVisor(kvm)

runc

aliuk

mysql

22773.19ms

1371.44ms

1673.86ms

 

如上為 gVisor(kvm)、runc、aliuk 使用 sysbench 對 mysql oltp.lua 進行混合測試的平均時延,gVisor 的性能明顯也最低。

另外,Sentry 對 Host 內核的依賴,與 Syscall 劫持的低效也是 gVisor 比 AliUK 要差的原因之一。

功能對比

 

runc容器

RunV

gVisor

AliUK

備注

自包含

NA

完整

較完整

完整

gVisor有部分SYSCALL的實現依賴於Host內核

性能

較高

 

資源占用

較少

較少

較少

 

兼容性

NA

較強

gVisor某些內核功能支持不完整,同時/proc  /sys接口與Linux差別較大

安全性

較強

gVisor某些非外部資源訪問的內核功能對Host內核產生依賴,增大了安全容器與Host內核的攻擊面

前景展望

gVisor 是一種對安全容器的解決方案。它在 OCI 兼容、二進制兼容、多進程方面實現得很巧妙,隨着 SYSCALL 的完善和軟件代碼的成熟,會有越來越多的容器能夠無縫地遷移到 gVisor 上。

但是 gVisor 的當前版本在性能上還不盡如人意。應用程序每次調用 SYSCALL 都伴有頁表切換,而且部分功能還依賴於 Host 內核而陷出到 Host。同時 IO 方面采用了性能不佳的 9pfs,加上網絡協議棧方面未進行過優化。這些都導致了它的性能相比較普通容器來說,有較大程度地降低。對性能有較高要求的應用無法應用在 gVisor 上面。

參考文獻:

[1]https://www.cvedetails.com/product/47/Linux-Linux-Kernel.html?vendor_id=33

[2] https://github.com/kata-containers

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM