
作者 | 王旭 螞蟻金服資深技術專家
本文整理自《CNCF x Alibaba 雲原生技術公開課》第 28 講,
點選直達課程頁面。
關注“阿裡巴巴雲原生”公衆号,回複關鍵詞“入門”,即可下載下傳從零入門 K8s 系列文章 PPT。
一、緣起:安全容器的命名
Phil Karlton 有一句名言:“計算機科學界隻有兩個真正的難題——緩存失效和命名。”
對我們容器圈而言,我相信「命名」絕對配得上這句話。這毫無疑問是一件讓老開發者沉默、讓新人落淚的事情。僅就系統軟體而言,我們當今比較通行地稱為「Linux 容器技術」這個概念,它曾經用過的名字還有 Jail, Zone, Virtual Server, Sandbox 等。同樣,在早期虛拟化的技術棧裡也把一類虛拟機叫做容器,畢竟這個詞本身就指代那些用來包容、封裝和隔離的器物。它實在太過常見了,以至于以嚴謹著稱的 Wikipedia,它的詞條叫做「OS-Level Virtualization」(系統級虛拟化) ,進而回避了「什麼是容器」這個問題。
在 2013 年,Docker 問世之後,容器這個概念伴随着不可變基礎設施、雲原生這一系列概念在随後的幾年間以摧枯拉朽之勢颠覆了基于“軟體包+配置”這種細粒度組合的應用部署,用簡單的聲明式政策和不可變的容器就清爽地定義了軟體棧。應用怎麼部署,在這兒似乎有點離題了,我在這裡想要強調的是:
“雲原生語境下的容器,實質是「應用容器」——是以标準格式封裝的,運作于标準作業系統環境(常常是 Linux ABI)上的應用打包——或運作這一應用打包的程式/技術。”
這個定義是我下的,但它并不是我的個人意志,是基于 OCI 規範這一共識寫出來的。這個規範規定了容器之中應用被放到什麼樣的環境下、如何運作,比如說容器的根檔案系統上哪個可執行檔案會被執行,是用什麼使用者執行,需要什麼樣的 CPU,有什麼樣的記憶體資源、外置存儲,還有什麼樣的共享需求等等。
是以說,标準格式的封裝、标準的作業系統環境在一起以應用為中心就構成了應用容器的打包。
以這個共識為基礎,就可以來說說安全容器了。當年,我和我的聯合創始人趙鵬使用「虛拟化容器」這個名字來命名我們的技術的,不過為了博人眼球,我們用了「Secure as VM, Fast as Container」這樣的 Slogan,于是,被容器安全性問題戳中心坎的人們立刻用「Secure Container」或者說「安全容器」來稱呼這種東西了,一發而不可收。雖然在我們的内心裡,這個技術是一層額外的隔離,它隻是安全中的一環,但是呢,使用者還是願意用安全容器這個名字來稱呼它。我們給安全容器下的定義就是:
安全容器是一種運作時技術,為容器應用提供一個完整的作業系統執行環境(常常是 Linux ABI),但将應用的執行與主控端作業系統隔離開,避免應用直接通路主機資源,進而可以在容器主機之間或容器之間提供額外的保護。
這就是我們的安全容器。
二、間接層:安全容器的精髓
說安全容器的時候,就要提到「間接層」這個詞。它出自于 Linus Torvalds 在 2015 年的 LinuxCon 上提出的:
“安全問題的唯一正解在于允許那些(導緻安全問題的)Bug 發生,但通過額外的隔離層來阻擋住它們。”
為了安全,為什麼要引入隔離層呢?其實 Linux 本身這樣的規模是非常大的,無法從理論上來驗證程式是沒有 Bug 的,于是,一旦合适的 Bug 被利用,安全性風險就變成安全性問題了。安全性的架構和修補并不能確定安全,是以我們需要進行一些額外的隔離來減少漏洞以及因為這些漏洞造成的被徹底攻破的風險。
這就是安全容器的由來。
三、Kata Containers:雲原生化的虛拟化
2017 年 12 月,我們在 KubeCon 上對外釋出了 Kata Containers 的安全容器項目,這個項目有兩個前身:由我們之前開始的 runV 以及 Intel 的 Clear Container 項目。這兩個項目都是 2015 年 5 月開始開展的,實際上是早于 Linus 在 KubeCon 2015 說的那番話的。
它們的思路都很簡單:
- 作業系統本身的容器機制沒法解決安全性問題,需要一個隔離層;
- 虛拟機本身,VM,它是一個現成的隔離層,比如說像阿裡雲、AWS,它們都使用了虛拟化技術,是以對于全世界來說,大家已經普遍地相信,對于使用者來說,隻要能做到「secure of VM」,那這個安全性就可以滿足公有雲的需求了;
- 虛拟機中如果有個核心,就可以支援我們剛才所提到的 OCI 的定義,也就是說提供了 Linux ABI 的運作環境,在這個運作環境中跑一個 Linux 應用不太難實作。
現在的問題是虛機不太夠快,阻礙了它在容器環境中的應用,如果能擁有「speed of container」的話,那我們就可能可以有一個用虛拟機來做隔離的安全容器技術了。這個也就是 Kata Containers 本身的一個思路,就是用虛拟機來做 Kubernetes 的 PodSandbox。在 Kata 裡面被拿來做 VM 的先後有 qemu, firecracker, ACRN, cloud-hypervisor 等。
下圖就是 Kata Containers 怎麼去和 Kubernetes 內建的,這裡的例子用的是 containerd,當然 CRI-O 也是一樣的。
目前,Kata Containers 通常是在 Kubernetes 中使用。首先 Kubelet 通過 CRI 接口找到 containerd 或者 CRI-O,這個時候比如鏡像這樣的操作一般也是由 containerd 或者 CRI-O 來執行的。根據請求,它會把 runtime 部分的需求變成一個 OCI spec,并交給 OCI runtime 執行。比如說上圖上半部分中的 kata-runtime,或者說下半部分精簡過後的 containerd-shim-kata-v2。具體的過程是這樣的:
- 當 containerd 拿到一個請求的時候,它會首先建立一個 shim-v2。這個 shim-v2 就是一個 PodSandbox 的代表,也就是那個VMM 的代表;
- 每一個 Pod 都會有一個 shim-v2 來為 containerd/CRI-O 來執行各種各樣的操作。shim-v2 會為這個 Pod 啟動一個虛拟機,在裡面運作着一個 linux kernel,也就是圖裡面的 Guest kernel。如果這個裡面用的是 qemu 的話,我們會通過一些配置和一些更新檔,讓它變得小一些。同時這個裡面也沒有額外的 Guest 作業系統,不會跑一個完整的像 CentOS, Ubuntu 這樣的作業系統;
- 後我們會把這個容器的 spec 以及這個容器本身打包的存儲,包括 rootfs 和檔案系統,交給這個 PodSandbox。這個 PodSandbox 會在虛機中由 kata-agent 把容器啟動起來;
- 依照 CRI 語義和 OCI 規範,在一個 Pod 裡面是可以啟動多個相關聯的容器的。它們會被放到同一個虛拟機裡面,并且可以根據需求共享某些 namespace;
- 除了這些之外,其它的一些外置的存儲和卷也可以通過熱插拔的方式來插到這個 PodSandbox 裡面來;
- 對于網絡來說,目前使用 tcfilter 就可以無縫地接入幾乎所有的 Kubernetes 的 CNI 插件。而且我們還提供了一個 enlightened 的模式,這樣的話會有一個特制的 CNI 插件來提高容器的網絡能力。
可以看到,在我們的 PodSandbox 裡面,實際上隻有一個 Guest Kernel 跑着一些容器本身的打包和容器應用,并不包含一個完整的作業系統。就是說,這個過程,它用起來并不像是傳統的虛拟機,對于容器來說,它隻有容器的引擎,并且通過少用不必要的記憶體、共享能共享的記憶體來進一步地降低記憶體的開銷。
與傳統的虛拟機比起來,開銷更小、啟動更輕快,對于大部分的場景來說,它可以做到「secure as VM」、「fast as container」。同時,在安全性技術以外,相比傳統的虛機,它有更多的彈性,更少了機器的那種實體操作的手感,比如說這裡面說過的包括動态資源的插拔以及使用 virtio-fs 這樣的技術等。它是一個專門為我們這種場景、為像 kata 這樣的場景來做的一個把 host 的基本檔案系統的内容(比如說容器的 rootfs )共享給虛拟機的這樣一個技術。
通過其中一些之前為非易失存儲、非易失記憶體來做的 DAX 的技術,能夠在不同的 PodSandbox 之間,也就是不同的 Pod 之間、不同的容器之間,共享一些可以共享的隻讀的記憶體部分。這樣可以在不同的 PodSandbox 之間去節省很多的記憶體。同時所有的 Pod 的管理都是通過 Kubernetes 從外部進行的容器管理,并且從外部來擷取 metrics 和 debug 資訊,并沒有登陸虛拟機這樣一種手感。是以它看起來是一種非常容器化的操作,雖然從底層來看,它還是一個虛拟機,但是實際上它是一個面向雲原生的虛拟化。
四、gVisor:程序級虛拟化
gVisor,我們又把它叫做程序級的虛拟化,它是和 kata 不一樣的另外一種方式。
在 2018 年的 5 月份,哥本哈根的 KubeCon 上,Google 開源了他們内部開發了 5 年的 gVisor 安全容器作為對 kata containers 的回應,表明了他們有一種不同的安全容器的解決方案。
如果說 Kata Containers 是通過對現有的隔離技術進行組合和改造來建構容器間的隔離層的話,那麼 gVisor 的設計顯然是更加簡潔的。
如上圖右側所示,它是一個用 Go 語言重寫的運作在使用者态的作業系統核心,這個核心的名字叫做 sentry,它并不依賴于虛拟化和虛拟機技術,相反,它是借助一個它們内部叫做一個 Platform(平台)的能力,讓主控端的作業系統做一個操作,把應用所有的期望對作業系統的操作都轉交給 sentry 來進行,sentry 做處理之後會把其中的一部分交給作業系統來幫它完成,大部分則由自己來完成。
gVisor 是一個純粹的面向應用的隔離層,從一開始就不是一個完全等同于虛拟機的東西,它就是用來在 Linux 上面跑一個 Linux 程式的。作為一個隔離層,它的安全性依據在于:
- gVisor 的開發者們首先要把攻擊面變小,主控端的作業系統将隻為沙箱裡的應用提供大約 20% 的系統調用;
Linux 大概有 300 多個 Syscall,實際上 sentry 最後向作業系統發起的調用隻會集中在 60 多個 Syscall 上。這個是源于 gVisor 的開發者們對作業系統的安全做了一些研究,他們發現,大多數對作業系統的成功的攻擊都是來自于不常用的系統調用的。
這個很容易了解,因為不常用的系統調用,它的實作路徑一般都是比較老的路徑,也就是說這些部分的開發一般不是太積極,隻有很少的開發者來維護,那些熱門路徑上的代碼要更安全一些,因為那些代碼被 review 的次數比較多。是以 gVisor 的設計就是讓應用對那些并不常用的 Syscall 的通路根本就到不了作業系統層面,而隻在 sentry 裡就把它處理掉。
從 sentry 通路主控端的,隻使用那些被驗證過的、比較成熟、比較熱的路徑上的系統調用,這樣的話,安全性就會比原來看起來好很多。我們現在 Syscall 是原來的 1/5,但是被攻擊的可能性是并不到 1/5 的。
- 其次,他們發現,一些經常被攻擊的系統調用需要把它隔離出來,比如 open(),就是打開檔案的那個操作;
在 Unix 系統裡面,大部分東西都是一些檔案,是以 open 可以做太多的事情了,大部分的攻擊都是通過 open 來進行的。gVisor 的開發者就單獨地把 open 放到了一個獨立的程序裡面去實作,這個程序叫做 Gofer。一個獨立的程序實際上是更容器被 seccomp、被一些系統的限制、一些 "capbility drop" 來保護。Gofer 可以做更少的事情,可以用非 root 去執行,如此一來整個系統的安全性就被進一步地被提高了。
- 最後,sentry 和 Gofer 都是用 Go 語言來實作的,不是用傳統的 C 語言實作的。
Go 語言本身是一個記憶體更安全的一個實作,是以整個 gVisor 就更不容易被攻擊,更不容易發生一些記憶體上的問題。當然,Go 語言在有些地方還是不夠太系統級的,gVisor 的開發者也坦言,他們為了做這件事情,也對 Go Runtime 做了很多調整,并把這些東西也回報回給了 Go 語言的社群。
可以說 gVisor 的架構很漂亮,有很多開發者跟我坦誠,他們其實很喜歡 gVisor 的架構,覺得這個更簡單、更純粹、更幹淨。當然了,雖然它的架構很漂亮,但重新實作一個核心這件事情也隻有 Google 這樣的巨頭能做得出來,類似的可能還有微軟的 WSL 1。而且這個設計是比較超前的,它其實存在一些問題:
- 首先,sentry 并不是 Linux,是以在相容性方面與 kata 這樣的方案比起來還是有一定的差距的。這個沒有辦法,但是對于特定應用來說,這個可能并不是問題;
- 其次,對于目前的系統調用的實作方式,還有 CPU 的指令系統來說,我們從應用去攔截 Syscall,再把這個 Syscall 送給 sentry 去執行,這個過程本身是有相當大的開銷的。在一定場景之下,gVisor 是可以有更好的性能的。但是,在大部分的場景之下,gVisor 的性能仍然是比不上 kata 這樣的解決方案的。
是以短時間之内,gVisor 這樣的解決方案并不能成為一個終極的解決方案,不過它可以适應一些特定的場景,并且它也帶來一些啟示性。我覺得這個啟示性對未來的作業系統、CPU 指令集的發展都可能會有一些作用。而且我相信,在未來,不管是 kata 還是 gVisor,都會有一個演進,我們期待着最後會有一個公共的解決方案來統一地解決應用的執行問題。
五、安全容器:不止于安全
安全容器的名字雖叫安全,但是它提供的是一個隔離性。它的作用是不止于安全的。
安全容器通過隔離層讓應用的問題——不管是來自于外部的惡意攻擊還是說意外的錯誤,都不至于影響主機,也不會在不同的 Pod 之間互相影響,是以實際上,這個額外的隔離層,它所帶來的影響不隻是安全,還有其它的方面。它對于系統的排程、服務品質,還有應用資訊的保護都是有好處的。
我們說傳統的作業系統容器技術是核心程序管理的一個延伸,容器程序本身是一組相關聯的程序,對于主控端的排程系統來說,它是完全可見的,一個 Pod 裡的所有容器或程序,同時也都被主控端排程和管理。這就意味着,如果你有一個大量容器的環境,主控端本身核心的負擔就會很重,在很多實際環境中已經可以觀察到這個負擔帶來的開銷了。
尤其是現在計算機技術的不斷發展,一個作業系統會有大量的記憶體,大量的 CPU,幾百 G 的記憶體都是可以見到的。在這個情況下,如果配置設定的容器數量很多,排程系統就會有非常沉重的開銷。在采納安全容器之後,在主控端上就看不到這些完整的資訊了,這個隔離層同時承擔了一些對隔離層上面應用的排程,于是在主機上面就隻需要排程這些沙箱本身,降低了主控端的排程開銷,這也就是它為什麼會提高排程效率的原因。
提高排程效率的同時,它會把所有的應用彼此隔離起來,這樣就避免了容器之間、容器和主機之間的幹擾,提高了服務品質。從另外一個方向來看,我們做安全容器的初衷是為了保護主控端不受到容器内惡意或者有問題的應用的影響,反過來,作為一個雲來說,我們有可能會面對有惡意的攻擊,是以也是保護我們自己。
同時使用者也不願意讓我們過多地去通路使用者的資源,使用者需要使用資源,但它并不需要我們看到它的資料。安全容器可以把使用者運作的東西完全封裝在容器裡,這樣的話可以讓主機的運維管理操作并不能通路到應用的資料,進而把應用的資料保護在沙箱裡,不需要去碰到使用者資料。如果我們要通路使用者資料,作為一個雲的話,那就必須得讓使用者給你授權,這個時候,使用者不确定你是不是有什麼惡意的操作,如果我們的沙箱封裝得很好的話,那也就不需要額外的對使用者授權的要求,這對于保護使用者的私密性是更好的。
當我們把目光看向未來的時候,可以看到,安全容器不僅僅是在做安全隔離,安全容器隔離層的核心相對于主控端的核心是獨立的,專門對應用服務,從這個角度來說,主機和應用的功能之間實際上是一個合理的功能配置設定與優化。它可以展現出很多的潛力,未來的安全容器,可能不僅僅是隔離性能開銷的降低,同時也是在提高應用的性能。隔離技術會讓雲原生基礎設施更加完美。
六、本文總結
本文的主要内容就到此為止了,這裡為大家簡單總結一下:
- 現在,所謂“安全容器”是指一種容器運作時技術,為容器應用提供一個完整的作業系統執行環境(常常是 Linux ABI),但将應用的執行與主控端作業系統隔離開,避免應用直接通路主機資源,進而可以在容器主機之間或容器之間提供額外的保護;
- Kata Containers 是一個使用虛拟化來提供隔離層的開源安全容器項目,完全相容 Kubernetes 等雲原生生态系統,項目托管在OpenStack Foundation,由螞蟻金服和Intel共同上司;
- gVisor 是一種利用程序級虛拟化技術實作的安全容器技術,由 Google 開發并開源,用 Go 語言實作了一個使用者态的相容核心;
- 最後,安全容器提供的隔離性不止是安全中的一環,也可以提供性能、排程、管理方面的隔離。
“ 阿裡巴巴雲原生 關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公衆号。”