Kubernetes架構(gòu)簡介
Kubernetes架構(gòu)如下圖所示:

在這張系統(tǒng)架構(gòu)圖中,我們把服務(wù)分為運行在工作節(jié)點上的服務(wù)和組成集群級別控制板的服務(wù)。Kubernetes節(jié)點有運行應(yīng)用容器必備的服務(wù),而這些都是受Master的控制。
每次個節(jié)點上當(dāng)然都要運行Docker。Docker來負(fù)責(zé)所有具體的映像下載和容器運行。
Kubernetes主要由以下幾個核心組件組成:
1)etcd保存了整個集群的狀態(tài);
2)apiserver提供了資源操作的唯一入口,并提供認(rèn)證、授權(quán)、訪問控制、API注冊和發(fā)現(xiàn)等機制;
3)controller manager負(fù)責(zé)維護(hù)集群的狀態(tài),比如故障檢測、自動擴展、滾動更新等;
4)scheduler負(fù)責(zé)資源的調(diào)度,按照預(yù)定的調(diào)度策略將Pod調(diào)度到相應(yīng)的機器上;
5)kubelet負(fù)責(zé)維護(hù)容器的生命周期,同時也負(fù)責(zé)Volume(CVI)和網(wǎng)絡(luò)(CNI)的管理;
6)Container runtime負(fù)責(zé)鏡像管理以及Pod和容器的真正運行(CRI);
7)kube-proxy負(fù)責(zé)為Service提供cluster內(nèi)部的服務(wù)發(fā)現(xiàn)和負(fù)載均衡;
而和運行時緊密相關(guān)的就是kubelet。
kubelet架構(gòu)
kubelet架構(gòu)如下圖所示:

kubelet是運行在每個節(jié)點上的主要的“節(jié)點代理”,每個節(jié)點都會啟動kubelet進(jìn)程,用來處理Master節(jié)點下發(fā)到本節(jié)點的任務(wù),按照PodSpec描述來管理Pod和其中的容器(PodSpec是用來描述一個pod的YAML或者JSON對象)。kubelet通過各種機制(主要通過apiserver)獲取一組PodSpec并保證在這些PodSpec中描述的容器健康運行。

容器運行時接口(CRI)
Kubernetes節(jié)點的底層由一個叫做“容器運行時”的軟件進(jìn)行支撐,它負(fù)責(zé)比如啟停容器這樣的事情。最廣為人知的容器運行時當(dāng)屬Docker,但它不是唯一的。例如最近比較火熱的安全容器KataContainer。所以也就很自然會與有一個需求,就是我們怎么去把KataContainer run在Kubernetes里?
那么這個時候我們還是先來看Kubelet在做什么事情,所以Kubelet要想辦法像call docker一樣去call KataContainer,然后由KataContainer負(fù)責(zé)幫忙把hypervisor這些東西set up起來,幫我把這個小VM運行起來。所以這個時候就要需要想怎么讓Kubernetes能合理的操作KataContainers。
對于這個訴求,就關(guān)系到Container Runtime Interface,我們叫它CRI。CRI的作用其實只有一個:就是它描述了對于Kubernetes來說,一個Container應(yīng)該有哪些操作,每個操作有哪些參數(shù),這就是CRI的一個設(shè)計原理(本質(zhì)上是一堆ops)。
Kubelet與容器運行時通信(或者是CRI插件填充了容器運行時)時,Kubelet就像是客戶端,而CRI插件就像對應(yīng)的服務(wù)器。它們之間可以通過Unix套接字或者gRPC框架進(jìn)行通信。

protocol buffers API包含了兩個gRPC服務(wù):ImageService和RuntimeService。ImageService提供了從鏡像倉庫拉取、查看、和移除鏡像的RPC。RuntimeSerivce包含了Pods和容器生命周期管理的RPC,以及跟容器交互的調(diào)用(exec/attach/port-forward)。一個單塊的容器運行時能夠管理鏡像和容器(例如:Docker和Rkt),并且通過同一個套接字同時提供這兩種服務(wù)。這個套接字可以在Kubelet里通過標(biāo)識–container-runtime-endpoint和–image-service-endpoint進(jìn)行設(shè)置。

下圖顯示了ImageService和RuntimeService具體需要實現(xiàn)哪些接口。

CRI Shim
CRI Shim可以做什么?它可以把CRI請求 翻譯成Runtime API。我舉個例子,比如說現(xiàn)在有個Pod里有一個A容器和有個B容器,這時候我們把這件事提交給Kubernetes之后,在Kubelet那一端發(fā)起的CRI code大概是這樣的序列:首先它會run Sandbox foo,如果是Docker它會起一個infra容器,就是一個很小的容器叫foo,如果是Kata它會給你起一個虛擬機叫foo,這是不一樣的。
所以接下來你creat start container A和B的時候,在Docker里面是起兩個容器,但在Kata里面是在我這個小虛擬機里面,在這Sandbox里面起兩個小NameSpace,這是不一樣的。所以你把這一切東西總結(jié)一下,你會發(fā)現(xiàn)OK,我現(xiàn)在要把Kata run在Kubernetes里頭,所以我要做工作,在這一步要需要去做這個CRI shim,我就想辦法給Kata作一個CRI shim。
而我們能夠想到一個方式,我能不能重用現(xiàn)在的這些CRI shim。重用現(xiàn)在哪些?比如說CRI containerd這個項目它就是一個containerd的CRI shim,它可以去響應(yīng)CRI的請求過來,所以接下來我能不能把這些情況翻譯成對Kata這些操作,所以這個是可以的,這也是我們將用一種方式,就是把KataContainers接到我的Containerd后面。這時候它的工作原理大概這樣這個樣子,Containerd它有一個獨特設(shè)計,就是他會為每一個Contaner起個叫做Contained shim。你run一下之后你會看他那個宿主機里面,會run一片這個Containerd shim一個一個對上去。

而這時候由于Kata是一個有Sandbox概念的這樣一個container runtime,所以Kata需要去match這些Shim與Kata之間的關(guān)系,所以Kata做一個Katashim。把這些東西對起來,就把你的Contained的處理的方式翻譯成對kata的request,這是我們之前的一個方式。
但是你能看到這其實有些問題的,最明顯的一個問題在于對Kata或gVisor來說,他們都是有實體的Sandbox概念的,而有了Sandbox概念后,它就不應(yīng)該去再去給他的每一個Container啟動有一個shim match起來,因為這給我們帶來很大的額外性能損耗。我們不希望每一個容器都去match一個shim,我們希望一個Sandbox match一個shim。
另外,就是你會發(fā)現(xiàn)CRI是服務(wù)于Kubernetes的,而且它呈現(xiàn)向上匯報的狀態(tài),它是幫助Kubernetes的,但是它不幫助Container runtime。所以說當(dāng)你去做這個集成時候,你會發(fā)現(xiàn)尤其對于VM gVisorKataContainer來說,它與CRI的很多假設(shè)或者是API的寫法上是不對應(yīng)的。所以你的集成工作會比較費勁,這是一個不match的狀態(tài)。
最后一個就是我們維護(hù)起來非常困難,因為由于有了CRI之后,比如RedHat擁有自己的CRI實現(xiàn)叫cri-o(基于Open Container Initiative的Kubernetes Container Runtime Interface的實現(xiàn)),他們和containerd在本質(zhì)上沒有任何區(qū)別,跑到最后都是靠runC起容器,為什么要這種東西?
我們不知道,但是我作為Kata maintainer,我需要給他們兩個分別寫兩部分的integration把Kata集成進(jìn)去。這就很麻煩,者就意味著我有100種這種CRI我就要寫100個集成,而且他們的功能全部都是重復(fù)的。
Containerd ShimV2
為了解決以上的shim問題,引入了shimv2。前面我們說過CRI,CRI決定的是Runtime和Kubernetes之間的關(guān)系,那么我們現(xiàn)在能不能再有一層更細(xì)致的API來決定我的CRI Shim跟下面的Runtime之間真正的接口是什么樣的?
這就是ShimV2出現(xiàn)的原因,它是一層CRI shim到Containerd runtime之間的標(biāo)準(zhǔn)接口,所以前面我直接從CRI到Containerd到runC,現(xiàn)在不是。我們是從CRI到Containerd到ShimV2,然后ShimV2再到RunC再到KataContainer。這么做有什么好處?
最大的區(qū)別在于:在這種方式下,你可以為每一個Pod指定一個Shim。因為在最開始的時候,Containerd是直接啟動了一個Containerd Shim來去做響應(yīng),但我們新的API是這樣寫的,是Containerd Shim start或者stop。所以這個start和stop操作怎么去實現(xiàn)是你要做的事情。
例如KataContainers項目可以這么實現(xiàn):在created Sandbox的時候call這個start的時候,我啟動一個Containerd Shim。但是當(dāng)我下一步是call API的時候,就前面那個CRI里面,Container API時候,我就不再起了,我是reuse,我重用為你創(chuàng)建好的這個Sandbox,這就位你的實現(xiàn)提供了很大的自由度。
所以這時候你會發(fā)現(xiàn)整個實現(xiàn)的方式變了,這時候Containerd用過來之后,它不再去care每個容器起Containerd Shim,而是由你自己去實現(xiàn)。我的實現(xiàn)方式是我只在Sandbox時候,去創(chuàng)建containerd-shim-v2,而接下來整個后面的container level操作,我會全部走到這個containerd-shim-v2里面,我去重用這個Sandbox,所以這個跟前面的時間就出現(xiàn)很大的不同。如下圖所示是Kata1.5中采用shim v2的實現(xiàn)。

首先,你還是用原來的CRI Containerd,只不過現(xiàn)在裝的是runC,你現(xiàn)在再裝一個katacontainer放在那機器上面。接下來我們Kata那邊會給你寫一個實現(xiàn)叫kata-Containerd-Shimv2。所以前面要寫一大坨CRI的東西,現(xiàn)在不用了。現(xiàn)在,我們只focus在怎么去把Containerd對接在kata container上面,就是所謂的實現(xiàn)Shimv2 API,這是我們要做的工作。而具體到我們這要做的事情上,其實它就是這樣一系列與run一個容器相關(guān)的API。
比如說我可以去create、start,這些操作全部映射在我Shimv2上面去實現(xiàn),而不是說我現(xiàn)在考慮怎么去映射,去實現(xiàn)CRI,這個自由度由于之前太大,造成了我們現(xiàn)在的一個局面,就有一堆CRI Shim可以用。這其實是一個不好的事情。有很多政治原因,有很多非技術(shù)原因,這都不是我們作為技術(shù)人員應(yīng)該關(guān)心的事情,你現(xiàn)在只需要想我怎么去跟Shimv2對接就好了。
容器運行時總結(jié)
下圖顯示了當(dāng)前主要的容器運行時和主要維護(hù)者。

-
容器
+關(guān)注
關(guān)注
0文章
536瀏覽量
23030 -
kubernetes
+關(guān)注
關(guān)注
0文章
275瀏覽量
9538
發(fā)布評論請先 登錄
Kubernetes的Device Plugin設(shè)計解讀
Kubernetes之路 2 - 利用LXCFS提升容器資源可見性
容器開啟數(shù)據(jù)服務(wù)之旅系列(二):Kubernetes如何助力Spark大數(shù)據(jù)分析
阿里云容器Kubernetes監(jiān)控(一) - 資源監(jiān)控
阿里云容器Kubernetes監(jiān)控(一) - 資源監(jiān)控
阿里云容器Kubernetes監(jiān)控(一) - 資源監(jiān)控
不吹不黑,今天我們來聊一聊 Kubernetes 落地的三種方式
在Kubernetes上運行Kubernetes
Kubernetes API詳解
深入研究Kubernetes調(diào)度
帶你快速了解 kubernetes
什么是Kubernetes容器運行時CRI
Kubernetes架構(gòu)和核心組件組成 Kubernetes節(jié)點“容器運行時”技術(shù)分析
評論