容器技術(shù)近年來越來越火,作為云原生技術(shù)的最底層基石,要開發(fā)云原生應(yīng)用,就有必要對于容器技術(shù)有一個更加深入的了解。容器的概念特別多,docker、oci、cri、runc、containerd名字容易看暈,這邊做一下總結(jié)
Container
首先先來了解一下,什么是容器?這邊我們一般說的“容器”,都是“Linux容器”(當然現(xiàn)在微軟也在搞容器,但還沒linux上面那么成熟)。不同于一般認識,其實容器本身并不是一個特別新的技術(shù),早在2000年就已經(jīng)有了,當時是用來在chroot環(huán)境(隔離mount namespac的工具)中做進程隔離(使用namespac和cgroups),容器的歷史可以看這篇文章。而現(xiàn)在docker所用的容器技術(shù),和當時并沒有本質(zhì)上的區(qū)別。容器的本質(zhì),一句話解釋,就是一組受到資源限制,彼此間相互隔離的進程。實現(xiàn)起來也并不復(fù)雜,隔離所用到的技術(shù)都是由linux內(nèi)核本身提供的(所以說目前絕大部分的容器都是必須要跑在linux里面的)。其中namespace用來做訪問隔離(每個容器進程都有自己獨立的進程空間,看不到其他進程),cgroups用來做資源限制(cpu、內(nèi)存、存儲、網(wǎng)絡(luò)的使用限制)??偟膩碚f容器就是一種基于操作系統(tǒng)能力的隔離技術(shù),這和基于hypervisor的虛擬化技術(shù)(能完整模擬出虛擬硬件和客戶機操作系統(tǒng))復(fù)雜度不可同日而語。這邊是一個對比圖
可以看出,容器是沒有自己的OS的,直接共享宿主機的內(nèi)核,也沒有hypervisor這一層進行資源隔離和限制,所有對于容器進程的限制都是基于操作系統(tǒng)本身的能力來進行的,由此容器獲得了一個很大的優(yōu)勢:輕量化,由于沒有hypervisor這一層,也沒有自己的操作系統(tǒng),自然占用資源很小,而打出來的鏡像文件也要比虛擬機小的多。對于虛擬機和容器的區(qū)別,劉超老師有一個很形象的比喻:虛擬機好比房子,容器好比衣服。衣服是跟著人的,人站起來了,衣服也跟著起來,人躺下了,衣服也躺下了,而房子永遠在那里,不管人是躺著還是站著。所以說基于容器的技術(shù)原理,它天生對應(yīng)用友好,輕量化,又具備了一定的隔離性,大火也就不奇怪了。
了解了容器的技術(shù)原理,接下來就可以說說容器的那些名詞概念了
OCI
Open Container Initiative,是由多家公司共同成立的項目,并由linux基金會進行管理,致力于container runtime的標準的制定和runc的開發(fā)等工作。所謂container runtime,主要負責的是容器的生命周期的管理。oci的runtime spec標準中對于容器的狀態(tài)描述,以及對于容器的創(chuàng)建、刪除、查看等操作進行了定義。
runC
是對于OCI標準的一個參考實現(xiàn),是一個可以用于創(chuàng)建和運行容器的CLI(command-line interface)工具。runc直接與容器所依賴的cgroup/linux kernel等進行交互,負責為容器配置cgroup/namespace等啟動容器所需的環(huán)境,創(chuàng)建啟動容器的相關(guān)進程。runC基本上就是一個命令行小工具,它可以不用通過Docker引擎,直接就可以創(chuàng)建容器。這是一個獨立的二進制文件,使用OCI容器就可以運行它。
containerd
containerd 是一個守護進程,它可以使用runC管理容器,并使用gRPC暴露容器的其他功能。相比較Docker引擎,使用 gRPC ,containerd暴露出針對容器的增刪改查的接口,Docker engine調(diào)用這些接口完成對于容器的操作。
Docker架構(gòu)
docker在早期所有功能都是做在docker engine里面的,但是之后功能越來越多,越來越重,很多功能已經(jīng)和基礎(chǔ)的容器運行時沒有關(guān)系了(比如swarm項目)。所以為了響應(yīng)社區(qū)的呼聲,也為了兼容oci標準,docker也做了架構(gòu)調(diào)整。將容器運行時相關(guān)的程序從docker daemon剝離出來,形成了containerd。Containerd向docker提供運行容器的API,二者通過grpc進行交互。containerd最后會通過runc來實際運行容器。調(diào)整完后的docker架構(gòu)圖如下(docker 1.12版本以后):
其中containerd-shim稱之為墊片,它使用runC命令行工具完成容器的啟動、停止以及容器運行狀態(tài)的監(jiān)控。containerd-shim進程由containerd進程拉起,并持續(xù)存在到容器實例進程退出為止(和容器進程同生命周期)。這種設(shè)計的優(yōu)點是,只要是符合OCI規(guī)范的容器,都可以通過containerd-shim來進行調(diào)用(比如kata-runtime)
執(zhí)行
ps -ef|grep docker
,可以看到當前正在運行的docker進程
[root@localhost ~]# ps -ef|grep docker
root 1431 1 1 2018 ? 10:35:01 /usr/bin/dockerd --bip=10.1.100.1/24 --ip-masq=true --mtu=1472
root 1498 1431 0 2018 ? 01:23:31 docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc
root 1802 1431 0 2018 ? 00:00:03 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 4567 -container-ip 10.1.100.2 -container-port 4567
root 1820 1431 0 2018 ? 00:00:03 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 10.1.100.2 -container-port 443
root 1848 1498 0 2018 ? 00:00:07 docker-containerd-shim 2abb8ab1515e9dd491fc6e7d747e6ec8cdff3e98bcb6ae67c9b90ddfa120f1ed /var/run/docker/libcontainerd/2abb8ab1515e9dd491fc6e7d747e6ec8cdff3e98bcb6ae67c9b90ddfa120f1ed docker-runc
root 1852 1431 0 2018 ? 00:13:47 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 10.1.100.2 -container-port 80
root 1869 1431 0 2018 ? 00:00:03 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10022 -container-ip 10.1.100.2 -container-port 22
root 1886 1498 0 2018 ? 01:24:27 docker-containerd-shim dd06d2cc632f45af1c53cd8df219c6275862bd94fed811bd3331290f8cf4a9a7 /var/run/docker/libcontainerd/dd06d2cc632f45af1c53cd8df219c6275862bd94fed811bd3331290f8cf4a9a7 docker-runc
root 15443 30672 0 13:08 pts/0 00:00:00 grep --color=auto docker
從上圖可以看出,dockerd
、docker-containerd
、docker-containerd-shim
分別對應(yīng)上述架構(gòu)圖中的docker daemon、containerd、containerd-shim,在這邊我啟動了兩個docker容器,所以會有兩個docker-containerd-shim進程。docker-runc
就是docker-containerd-shim
調(diào)用的真正來創(chuàng)建容器的模塊。
總結(jié)一下,其實docker總的架構(gòu)來說,真正用來創(chuàng)建容器的就是runC
,其他的都是一些容器的管理和增強功能,所以說你如果想創(chuàng)建一個容器玩一下,直接下載一個runC的二進制跑一下就行了,當然生產(chǎn)上面不能這么搞。
CRI
kubernetes在初期版本里,就對多個容器引擎做了兼容,因此可以使用docker、rkt對容器進行管理。以docker為例,kubelet中會啟動一個docker manager,通過直接調(diào)用docker的api進行容器的創(chuàng)建等操作。
kubelet做適配的問題,就是容器項目每次升級更新,kubelet都需要相應(yīng)的進行配合變更測試,很多api可能這一版能用,下一版就不能用了,由于容器項目一直在高速迭代,k8項目組苦不堪言。痛定思痛后,sig-node項目組在k8s 1.5版本之后,推出了自己的運行時接口api--CRI(container runtime interface)。cri接口的推出,隔離了各個容器引擎之間的差異,而通過統(tǒng)一的接口與各個容器引擎之間進行互動。
與oci不同,cri與kubernetes的概念更加貼合,并緊密綁定。cri不僅定義了容器的生命周期的管理,還引入了k8s中pod的概念,并定義了管理pod的生命周期。在kubernetes中,pod是由一組進行了資源限制的,在隔離環(huán)境中的容器組成。而這個隔離環(huán)境,稱之為PodSandbox。在cri開始之初,主要是支持docker和rkt兩種。其中kubelet是通過cri接口,調(diào)用docker-shim,并進一步調(diào)用docker api實現(xiàn)的。
如上文所述,docker獨立出來了containerd。kubernetes也順應(yīng)潮流,孵化了cri-containerd項目,用以將containerd接入到cri的標準中。
CRI-O
為了進一步與oci進行兼容,kubernetes還孵化了cri-o,成為了架設(shè)在cri和oci之間的一座橋梁。通過這種方式,可以方便更多符合oci標準的容器運行時,接入kubernetes進行集成使用。可以預(yù)見到,通過cri-o,kubernetes在使用的兼容性和廣泛性上將會得到進一步加強。
Kubernetes架構(gòu)
看到這張圖可能大家都暈了,其實這也反應(yīng)了容器及容器編排技術(shù)高速發(fā)展的現(xiàn)狀,K8的容器接入方案分別經(jīng)歷了四個階段(從右往左):
1.最早是在kubelet這一層進行適配,通過啟動docker-manager來訪問docker(不同的容器產(chǎn)品有不同的manager,這部分代碼是kubelet項目標準代碼的一部分)
2.K8 1.5之后引入了CRI接口,通過docker-shim(墊片)的方式接入docker。shim程序一般由容器廠商根據(jù)CRI規(guī)范自己開發(fā),實現(xiàn)方式可以自己定義(即CRI規(guī)范定義了要做什么,怎么做可以基于自己的理解)。而docker-shim由于歷史原因,還是k8項目組來做的,這部分代碼也包含在kubelet代碼里面,但架構(gòu)上是分開的。
3.docker在分出containerd后,k8也順應(yīng)潮流,孵化了cri-containerd項目,用于和containerd對接,這樣就不走docker daemon了。
4.目前孵化的cri-o項目,則連containerd都繞過了,直接使用runC去創(chuàng)建容器(你可以把cri-o看作是k8生態(tài)里面的containerd)
值得注意的是,在containerd的1.1版本中,甚至已經(jīng)去除了cri-containerd這樣一個守護進程,cri接口以插件的形式直接整合在了containerd項目里面,目前docker和kubernetes分別調(diào)用containerd的架構(gòu)圖如下:
容器項目發(fā)展趨勢
可以看到,隨著k8生態(tài)的不斷成熟和業(yè)界影響力的不斷增大,k8項目對于docker的依賴已經(jīng)越來越少,從cri-containerd開始就已經(jīng)可以脫離docker體系,到cri-o更是連docker的影子都看不到了(containerd項目主要還是docker在維護)。而現(xiàn)在還有crictl這樣的工具,連容器的日常維護都不需要使用docker命令了。Docker inc.基于自身的商業(yè)訴求,也是不斷再往平臺化發(fā)展(swarm項目),也在不斷添加復(fù)雜的功能,可惜憑一己之力還是很難和谷歌一手搭建出來的CNCF的宏偉藍圖去抗爭。
目前在CNCF里面有兩個容器運行時項目,分別是rkt
和containerd
。rkt從目前的發(fā)展情況看很有可能被移出CNCF,cri-o和containerd都在同時發(fā)展,這兩個項目其實重合度是比較高的,做的事情也差不多,只不過cri-o是專門為k8s設(shè)計的,而containerd設(shè)計之初的定義就是一個通用的容器運行時平臺,有說containerd即將通過委員會評審,從CNCF中畢業(yè)。
安全容器
最后討論一個比較前沿的話題,叫做“安全容器”。根據(jù)前面的介紹,相信你也了解了,容器有諸多好處,但也并不是完美的。它有個很大的軟肋--隔離性?;趦?nèi)核提供的namespace功能,其實很多東西還是無法隔離的,一個簡單的例子就是時間,一旦在某個容器內(nèi)修改了系統(tǒng)時間,所有運行在這個宿主機上的容器內(nèi)的時間都會改變,這顯然不符合“沙盒”原則。再比如容器里面的jvm,如果不修改內(nèi)核代碼的話,看到的都是宿主機的cpu和內(nèi)存情況。而更加嚴重的問題是,由于共享宿主機內(nèi)核,一旦某個容器被植入惡意代碼,攻擊宿主機內(nèi)核,會影響到該宿主機上的所有容器。而這個問題在虛擬機時代是不存在的,因為虛擬機都是獨立操作系統(tǒng)和內(nèi)核,里面可以隨便你折騰。目前所有的公有云,還都是使用虛擬機進行多租戶管理,安全性是一個很重要的原因。
基于上述情況,很容易就能想到,是否有一種技術(shù),既可以擁有容器的速度,還可以擁有虛擬機的隔離性呢?你別說,還真有人在研究這個,而且已經(jīng)有了幾個有一定成熟度的項目,比如kata container和gvisor。
kata containers是由OpenStack基金會管理,但獨立于OpenStack項目之外的容器項目。kata containers整合了Intel的 Clear Containers 和 國內(nèi)HyperHQ團隊 的 runV項目,它的本質(zhì)是在容器中建立一個經(jīng)過裁剪的,輕量化的內(nèi)核,所以在你啟動一個kata container后,你就能看到一個正常的虛擬機在運行,而Qemu就是這個虛擬機的VMM(虛擬機管理程序)。作為標準的虛擬機,kata container自然原生就帶了pod的概念,一個虛擬機就是一個pod,而用戶定義的容器,就是這個pod里面的進程。這邊是kata container的一個架構(gòu)圖:
從這個架構(gòu)圖可以看到,kata-runtime是和runc平級的,而kata container是完全按照OCI規(guī)范來進行設(shè)計的。大家不難想到,根據(jù)前面的介紹,containerd可以直接通過containerd-shim來對kata-runtime進行調(diào)用,所以對于docker來說,完全可以通過原來的方式創(chuàng)建kata容器,管理的方式也都和原來一樣。而你也不難想到,k8自然也是可以非常方便的創(chuàng)建和管理kata container容器(通過docker-shim、cri-containerd或者cri-o)。
可以看到,kata container項目基本還是走的傳統(tǒng)虛擬機的路子,通過裁剪出一個精簡的guest kernel(這個內(nèi)核是經(jīng)過高度優(yōu)化的,只用于一個容器的運行),以及通過hypervisor(QEMU)虛擬出來的硬件設(shè)備,提供一個完整的虛擬機環(huán)境。相對而言,gVisor設(shè)計更加激進,gVisor的設(shè)計中有個一個sentry進程,負責轉(zhuǎn)發(fā)應(yīng)用對于內(nèi)核各類功能的調(diào)用,簡單說,就是對應(yīng)用“冒充”內(nèi)核的系統(tǒng)組件,在這種思想下,不難理解,sentry需要自己實現(xiàn)一個完成的linux內(nèi)核的網(wǎng)絡(luò)棧,以便于處理應(yīng)用進程間的通訊請求。目前gVisor還處于早期階段,能調(diào)用的內(nèi)核功能還相當有限,并且在重I/O或者重網(wǎng)絡(luò)的情況下性能會急劇下降。但這種通過用戶態(tài)去運行一個操作系統(tǒng)內(nèi)核,來為應(yīng)用進程提供強隔離的思路,確實是未來安全容器進一步演化的一個方向。
就目前來說,我更加看好kata container的發(fā)展。一方面gVisor這種“冒充內(nèi)核組件”的方式雖思路很好,但還不夠成熟,功能和性能各方面還有限制,另外更重要的是,gVisor本質(zhì)上不是內(nèi)核,它所有的調(diào)用還是要依賴于宿主機內(nèi)核的能力,而kata container是一個獨立內(nèi)核,這可以讓kata起來的內(nèi)核版本不同于宿主機的內(nèi)核,提供真正虛擬機級別的隔離性,對于cilium這類對于內(nèi)核版本要求非常高的項目具有非常重大的意義。其實目前業(yè)界其實已有這方面的試水,華為云的CCI(云容器實例)明確提到了是基于kata container的安全容器技術(shù),阿里也推出了ECI(彈性容器實例,是不是基于kata不確定),應(yīng)該說安全容器的發(fā)展前景還是非常良好的。
kata container與service mesh
這兩個一個是基于虛擬化技術(shù)的容器運行時,一個是微服務(wù)治理框架,有什么關(guān)系呢?先來看一下下面這個圖
這是一個典型的service mesh的架構(gòu)圖,service 和sidecar分別部署為獨立的進程,所有的服務(wù)治理邏輯諸如服務(wù)發(fā)現(xiàn)、服務(wù)注冊、負載、限流/熔斷/降級、分布式跟蹤、灰度等功能都放在sidecar里面實現(xiàn),由服務(wù)網(wǎng)格統(tǒng)一管理所有的sidecar,應(yīng)用無感知。這就是所謂的服務(wù)治理下沉到基礎(chǔ)設(shè)施,這也是service mesh的核心理念。
service mesh理念很好,真正實現(xiàn)起來難度卻很大。究其原因,sidecar仍然是一個用戶態(tài)的進程,proxy攔截了所有進出service的流量,iptables本身性能就不行,對于服務(wù)調(diào)用來說又多了幾跳,用戶態(tài)的上下文切換,性能必然會有損失,事實上istio的性能問題至今一直為人詬病,也不能大規(guī)模使用于生產(chǎn)環(huán)境。為了解決這個問題,也誕生了諸如cilium這樣的項目,直接繞過內(nèi)核的tcp堆棧來提高性能。而安全容器項目,為這個問題的解決提供了一個新的思路,因為kata container本身就是一個精簡的guest kernel,那為何不把這部分服務(wù)治理邏輯直接下沉到guest kernel中,作為內(nèi)核網(wǎng)絡(luò)協(xié)議棧的一部分,真正的成為基礎(chǔ)設(shè)施。這樣只要基于虛擬化的容器啟動,自動就具有了服務(wù)治理的全部功能,而且是內(nèi)核級別的,應(yīng)用完全無感知。之前小劍老師分享的service mesh介紹中,有這么一張PPT
當時小劍的結(jié)論是把服務(wù)治理這一層加入內(nèi)核的網(wǎng)絡(luò)協(xié)議棧是最理想的,但是實現(xiàn)起來不現(xiàn)實。而kata container里面運行的是一個真正的內(nèi)核(哪怕是精簡的),要定制一些功能相對沒那么難,此外基于內(nèi)核態(tài)還可以做很多用戶態(tài)做不了的事情。當然這里面還有很多工作要做,但無疑虛擬化的容器項目無疑給我們打開了新世界的大門。