1. 程式人生 > 實用技巧 >基於Rust-vmm實現Kubernetes執行時

基於Rust-vmm實現Kubernetes執行時

隨著容器及K8s的廣泛使用,越來越多的容器安全與隔離問題被暴露出來,如:容器逃逸、水平攻擊、DDos攻擊等嚴重威脅了辦公和生產環境的安全與穩定,影響了業務的正常執行。安全容器技術孕育而生,產生了kata、gVisor、unikernel等多種安全容器方案。本文旨在介紹各種安全容器方案,分析各方案特點,結合騰訊在容器安全領域的實踐,幫助讀者選擇適合自身特性的容器執行時。同時引入Rust-VMM專案,介紹 Rust-VMM 技術和生態,演示如何使用K8s排程和啟用Rust-VMM安全容器執行時,展望以Rust語言實現的容器執行時的廣闊前景。

容器安全與隔離

一個基於K8s叢集構建的基礎設施中,內部存在不同層次的隔離,從容器到Pod再到節點最後到cluster,每一層隔離都有它的特點和特性,我們尤其關注Pod級別的隔離特性。

相比其他層次的安全隔離,Pod及容器級別的隔離對我們的挑戰非常大。容器在執行時使用root執行程序,儘管使用namespace技術為容器空間內的pid、uts、fib等進行了隔離,但由於各個容器共享系統核心,容器與核心間缺乏隔離保護,容易引發容器逃逸等安全問題,典型容器逃逸攻擊如:CVE-2018-14634、CVE-2016-5195、CVE-2019-5736 及 CVE-2019-14271等。

docker.vh.neargle.com:8888/?command_exec=python3 -c "import docker;client = docker.DockerClient(base_url='unix:///var/run/docker.sock');data = client.containers.run('alpine:latest', r'''sh -c \"echo 'ssh-rsa xxxxx root@620e839e9b02' >> /tmp/root/root/.ssh/authorized_keys\" ''', remove=True, volumes={'/': {'bind': '/tmp/root', 'mode': 'rw'}})"

上述指令碼是一個簡單的例子,這段程式碼會向docker.sock的埠發起請求,拉起一個alpine的容器,容器內程序會向所在主機注入一段SSH的公鑰。在容器裡的惡意使用者或者攻擊者,就可以獲輕鬆得這個容器所在host主機的SSH的登入許可權,從而能夠非法檢視同主機其他容器空間的資訊,篡改關鍵檔案或記錄,甚至以主機為跳板攻擊整個叢集。

還有一個就是Noisy Neighbor,就是吵鬧鄰居問題。關於Noisy Neighbor,容器方面已經有了很多進展,比如對於CPU、memory、bandwidth甚至是buffer IO,基於Cgroup對這些資源已經有了一些隔離和限制,但是這些限制並不能完全解決Noisy Neighbor的問題,還是有一些吵鬧的程序會影響到正常的業務程序的執行。

# kubectl run --rm -it bb --image=busybox sh
/ #  f(){ f|f& };f                   # WARNING: Don't try this!

上面是一個簡單的例子,啟動一個busybox的容器,在裡面執行一個巢狀迴圈的指令,會把這臺主機上所有的file descriptor全部耗盡,造成這臺主機上正常執行的業務程序工作不正常,這個是Noisy Neighbor的風險和問題。

對於上述問題,建議使用者關注官方的漏洞報告,升級作業系統或docker的版本,根據安全指引配置容器環境,同時可以參考以下措施增強容器叢集的安全防護級別。

  • 在物理層面上隔離,為不同的租戶之間劃分不同的Hardware Isolation域,讓不同的租戶使用不同的硬體空間,從物理上、網路上以及儲存上徹底的隔離,這也是最直接最有效的方法。
  • 利用一些Security Tools,包括經常用的SElinux或者Cgroup隔離,劃分不同的資源訪問和安全規則,但是這些安全規則需要編寫大量的profile檔案,實現起來難度頗大。
  • 入侵檢測機制,主機防禦的一種手段。入侵檢測的軟體或者程序會監控這臺主機上有風險的程序或者有風險的檔案,對於這些檔案的讀寫操作都會有安全方面的記錄,會即時預警,即時響應。比如對於containerd-shim/busybox/docker-runc的關鍵程序,比如docker-runc對於bash、init或者對於fd的訪問都可以做到即時的預警和標記。對於上面提到的容器逃離漏洞,通過入侵檢測的機制,就可以有一個比較有效的防禦。
  • 定製Linux Kernel Patch,一些特殊的資源隔離或者安全漏洞,也可以為kernel打一些自身特有的patch來加以防範,但是這裡的patch面臨的問題是多種多樣的,所以這裡就不再展開細講了,如果有關注的同學可以參考一下騰訊對於Linux Kernel方面的一些文章和報道。

容器執行時

上述安全實踐方案和措施能夠很大程度的減少對外提供服務時受攻擊的範圍,提高容器服務的安全能力。但我們仍然想要尋找一種簡單的、有效的、統一的runtime解決方法,我們把眼光投入到CNCF runtime landscape,可以看到有多種解決方案。

簡單梳理一下這些解決方案,都是按照兩大標準劃分的。一個就是CRI的標準,偏向於kubelet或者K8s這一側的標準。還有一側的標準就是OCI,就是偏向於容器底層基礎實現。

可以看到OCI這邊有很多種實現方案,簡單梳理一下,劃分了一個表格:

OCI Solution OCI Compatible Dedicated Docker Image Implementation Language Open source Hot plug Direct access to HW Required Hypervisors Backed by
Runc yes yes golang yes no yes None Docker
gVisor + runsc yes yes golang yes no no none or kvm Google
Kata + qemu yes yes golang,C yes yes yes kvm Hyper
Firecracker + Firecracker containerd no yes Rust,golang yes no no kvm Amazon
Nabla + runnc yes no C,golang yes no no None IBM

簡單介紹一下,比如有runc的方案,就是基於原生namespace的容器方案,docker使用的,containerd直接對接的runc的方案。GVisor是谷歌開源出來的一種基於使用者態的核心程序的安全沙箱技術的方案。Kata和qemu的基於虛擬化實現安全容器,這個現在也是比較熱門,使用比較廣泛的一種方案。Firecracker,由AWS開源出來的,Firecracker Containerd是對接OCI標準的元件,但現在還沒有完全對接OCI標準,所以這裡是有所欠缺的。最後是Nabla, runnc是Nabla對接OCI實現的一個元件,Nabla是IBM開源的一套安全容器的方案,但是它跟上述的一些方案有所區別,它是一個基於unikernel的方案,把業務應用和核心完全編譯在了一起,直接執行在host上面,所以可以看到它跟其他的方案不一樣的就是它不能直接使用容器的映象,需要自己編譯一個帶著核心的特殊映象,所以用起來不太符合容器的標準。

比較下來,典型的三種安全容器方案就是runc,kata-runtime,以及 gvisor 方案。

三種方案各有優缺點,從架構來說,runc的方案最簡單,直接用原生的namespace的方案。kata是基於hypervisor的方案,就是基於虛擬化的方案,所以它的每一個容器或者它的容器是執行在一個一個guest kernel上面,就是guest的虛擬機器裡,然後會有一個kata agent負責runtime跟底層真正跑的container的進行通訊。GVisor是介於runc和kata runtime之間,它不像Kata有一個虛擬化的Guest Kernel的存在,它是把Guest Kernel變成了一個運行於使用者態的一個核心的程序叫做Sentry,Gofer是它IO的元件,核心的sentry負責的就是攔截和劫持所有執行在它的沙箱裡面容器的所有的Syscall,對系統呼叫進行過濾和保護。

Solution Typical Software Bullet Point
Rule-based Sandbox RunC Needs to work with SELinux, AppArmor, Seccomp, cgroup
VM-based Sandbox Kata-container BareMetal Only, Heavy control logic
Application kernel based Sandbox gVisor Compatibility problem, Bottleneck in Sentry

最後彙總比較一下,runc的方案實現最簡單,而且它的效能也是最好的。但是一大缺點就是安全隔離的特性不夠安全,所以需要大量的外部Security Tools的保護。
基於kata的虛擬化的沙箱方案,它只能執行在支援KVM虛擬化的環境上面,因為他的虛擬層是基於KVM實現的。Kata的邏輯的鏈路很長,呼叫的路徑也非常長,會造成一些效能上的損耗,也會有一些問題。
GVisor介於兩者之間,但是GVisor現在的問題就是它的相容性不夠,300多個系統呼叫只能相容裡面的一部分,並且Sentry的作為核心會有一些效能瓶頸。

騰訊雲在安全容器上融合和上述方案的優點,結合騰訊雲在虛擬化,儲存和網路方面的優勢,
選擇使用mVMd + QEMU + EKLET的方案,實現一個彈性的Kubernetes的服務,即EKS,大家可以訪問一下騰訊雲的官網,看一下EKS的介紹。

EKS是基於Hypervisor的虛擬化的解決方案,不同於Kata,EKS使用的containerd + mVMd元件更加輕量,呼叫路徑更短。通過containrtd + mVMd來實現對於上層K8s呼叫的CRI的解析,並且把它轉化成真正對於底層一個一個Guest VM或者QEMU的控制指令,在guest VM裡會啟動相應的containers容器。

在整個的架構中,在Runtime方面最大的瓶頸是QEMU,因為QEMU有幾十年的歷史了,所以存在著它比較臃腫,反應慢,佔用的資源多等等問題。所以讓QEMU作為底層Runtime還不夠快,不夠安全。為了增強QEMU的效能和安全特性,我們很自然把眼光投向了Rust-Vmm。

Rust-Vmm 介紹

Rust-Vmm是一個開源工程,是一個可以自由定製的VMM(virtual machine monitor)虛擬機器管理器,使用者可以按照自己的方式訂製它。它是基於Rust語言實現的VMM,有著Rust語言帶來的優點和特性。

首先,Rust語言一個記憶體安全的語言,相比於用C或者C++會頻繁遇到的各種記憶體的問題,比如記憶體的溢位、空指標、野指標、越界訪問等等,更進一步會造成安全的問題、效能的問題,以及各種崩潰的問題。Rust語言很好地解決了這一點,從它的語法、編譯規則等杜絕了記憶體級別訪問的漏洞以及風險,所以用Rust寫的Rust-Vmm天然的就是記憶體安全的。

第二,Rust-Vmm是不易被攻擊的,Rust-VMM是從零開始的,它是從最小的硬體虛擬化出發的,最小的硬體虛擬化意味著它有著最小的攻擊面,被攻擊的面就非常少,所以它會很安全。

第三,Rust-Vmm能夠很靈活的定製。Rust-VMM可以靈活定製它的每一個元件,所有的對於裝置的模擬或者關鍵特性的處理都是封裝成了一個一個的Rust-Vmm crates包,比如有VCPU,有linuxloader,vm-virtIO等等。其中crates是Rust語言中的包管理工具,可以理解JAVA或golang裡面的package,它是以發行不同的包或者庫的形式對外發布它的feature。

第四,Rust-Vmm有非常高的效能,基於Rust語言的without garbage collection特性,它是沒有GC回收檢查機制的,不像JAVA或者其他更高階的語言會有一個runtime,Rust-Vmm的效能上會更好,同時基於KVM實現的虛擬化方案也是效能的保證。

簡單介紹一下Rust-Vmm的一個歷史,它是由谷歌首先實現的,谷歌首先實現一個Rust based的輕量級的VMM,它叫做crosVM,大家也可以從連結裡面看到,它是一個為chrome瀏覽器做的一個微核心。然後AWS,亞馬遜基於谷歌開源出來的crosVM,實現了自己的基於rust的VMM叫Firecracker。兩個專案的開發人員會發現做這兩個專案的時候,會有很多重複的重疊的通用的程式碼,很自然的把可以開源的、通用的部分結合到一塊,就有了Rust-Vmm的專案。

從這個全景圖裡面可以看到,Rust-Vmm應用在多個專案和產品中。從最開始的谷歌開源的crosVM裡面會有Rust-Vmm,比如AWS的Firecracker、以及其它CSP雲伺服器廠商,比如QEMU裡面會把一些耗時的或者記憶體訪問的部分也用Rust-Vmm實現,還有開源的Cloud Hypervisor專案。

Cloud Hypervisor是Intel開源出來的一套框架,把Rust-Vmm的元件組合在一起,能夠對外提供一個VMM的完整服務。使用者可以配置使用已公開發布的crates包,或整合自己的開發的crates包,通過將各種crates包組合在一起,就可以生成一個訂製的安全的高效能的VMM。

基於 Rust-Vmm 實現 K8s Runtime

講到這裡,基於Rust-VMM的一個實現,元件、原料和所有的知識都已經具備了,接下來就是如何對接K8s,基於Rust-VMM實現Kubernetes runtime,執行一個Kubernetes的Pod。

測試環境如圖所示,用到了包括K8s, containerd, Kata, Rust-Vmm, Cloud Hypervisor等開源專案,首先我們配置Kubernetes節點上的kubelet使用的runtime為containerd, 在containerd上配置了Kata-runtime作為容器的執行時,再配置了Kata-runtime使用的vmm為基於Rust-Vmm crates構建的Cloud Hypervisor,同時為了提升效能,這裡也是用了virtos-fs技術來承載物理機上的容器映象到子機內容器的imagefs的共享,所以在物理機上需要額外開啟virtiofsd的程序,在guest os的核心上也是使用最新的包含virtio-fs支援的核心版本。

在K8s上建立一個Pod後,排程器排程這個Pod到我們這個環境上的一個worker節點,節點上的kubelet程序根據我們的配置呼叫containerd,containerd的runtime外掛kata來建立Cloud Hypervisor的vmm(virtual machine monitor),在子機內預置的Guest OS作業系統啟動後,kata-agent也隨之啟動,成功建立母機與子機的通道,後續cri協議中的createcontainer,startcontainer以及停止容器等介面也由這個通道傳遞到子機內的kata-agent,由其來呼叫子機內真正的容器執行時,從而完成整個pod的生命週期。

我們來看一下最終我們達到的一個效果,如圖中第一條指令,我們執行kubectl get pod命令可以看到pod的狀態為執行中,同樣的,我們也可以通過crictl pods命令查詢到這個Pod的狀態。通過ps查詢程序列表,可以看到包含這個pod id的kata shim程序,virtiofsd程序以及cloud-hypervisor這個vmm的程序。最後我們也可以通過kubectl exec -it互動式的進入pod的container內,通過uname返回此容器使用的核心版本為5.6,物理機的核心版本為5.4。

總結與展望

最後是對未來的展望,基於Rust-Vmm我們想要做得更多。比如希望擴充套件一下Rust-Vmm在網路以及儲存方面的一些特性,尤其是在騰訊雲的環境裡面,通過結合CBS和VPC,提升Runtime的效能。

第二,會結合入侵檢測、主機安全、安全工具等,把它們融合在一起,構建起一個多維的、多重防護的一套全維度安全的容器服務平臺,Rust-VMM會實現裡面的一部分。

第三,擴充套件Rust Runtime,尤其在邊緣計算領域,在邊緣端能夠實現K8s以下完全由Rust接管,由Rust語言全部重寫,比如底層的Containerd、 shim等元件,變成一個Full Stack Rust的實現。

最後,因為Rust-VMM也是基於hypervisor的一個實現,所以基於hypervisor的一些特性,比如熱遷移、備份、回滾這些機制,未來都可以在Rust-Vmm上有所實現,這個也是我們的未來的一個設想。

最後大家如果想要了解更多關於我剛才介紹的K8s Runtime,Rust-Vmm以及EKS方面的知識,大家可以掃描下方的二維碼加入騰訊雲容器團隊的公眾號。

或者點選下方的網址瞭解TKE的公有云產品。騰訊雲對外也有一個開源出來的專案叫做TKEstack,大家可以從下方的github網址訪問得到,也希望大家多多支援。

最後如果大家有興趣,立志於開源事業,立志於CNCF的雲原生事業,也歡迎大家加入騰訊的團隊。連結就在上方,歡迎大家加入我們。

Q&A

  1. 現在的Rust-VMM專案開源了,哪裡可以看到專案相關的資訊?

    Rust-VMM是有谷歌,也有亞馬遜、英特爾,也有業內的廠商共同開源出來的一個專案,相關的資訊大家可以從github(https://github.com/rust-vmm)上面找到它們開源的專案,另外google一下就可以找到這個專案更多的資訊。

  2. 基於Rust-Vmm成熟度如何,基於Rust-Vmm構建的安全容器主要解決了哪些難題。

    大家可以對比一下AWS開源的Firecracker,它就是基於Rust-Vmm實現的一套亞馬遜自己的方案,而且這套方案已經應用在了亞馬遜fargate以及lambda的專案,serverless的解決方案裡面。所以基於Rust-Vmm這種專案的成熟度,亞馬遜已經把它做到商用了,這一點是有所保證的。

    但是Rust-Vmm本身又是開源的專案,它是跟AWS的firecracker有不太一樣的地方,成熟度方面肯定是有所欠缺的,需要廠商自己維護和保證質量問題或者安全問題。

    因此我想說基於Rust-Vmm實現的方案,亞馬遜已經幫我們做了前驅的工作,已經幫我們做了一個商業上的試水,所以這個是沒有問題的。

  3. 是否現在的公有云容器執行時都不傾向於使用runc。

    不是不使用runc,使用runc有一些隔離的問題。比如兩個租戶共用一個物理資源池,或者兩個租戶的容器執行在一個物理節點上,這個時候就帶來了容器逃離以及吵鬧鄰居的問題,多租戶的場景下或者serverless的場景下就會有這種問題。

    所以大部分的公有云廠商的解決方案其實就是物理隔離。一個租戶所在的物理主機都是屬於自己的,屬於同一個租戶的,所以你會發現某一個使用者假如從一個容器裡面逃離出去之後,逃離出去的這臺主機還是自己的,所以這樣逃出去,從物理上面還有安全防護的。

    但是這種面對於serverless的場景,serverless天然的就是要脫離主機的管理、脫離主機的隔離,所以serverless場景下,跟公有云的容器服務就有所區別,它就不能簡單靠物理隔離的方式實現的,它就要考慮沙箱(安全容器)的技術,比如像剛才我也介紹了多種多樣的沙箱隔離的技術,我們可能選擇的就是這種基於hypervisor的實現方案。

    如果感興趣,您可以訪問一下騰訊雲的官網,看一下里面EKS相關的介紹,這個就是騰訊對於這一領域的解決方案和貢獻。
    【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!