近日K8s官方稱最早將在1.23版本棄用docker作為容器運(yùn)行時(shí),并在博客中強(qiáng)調(diào)可以使用如containerd等CRI運(yùn)行時(shí)來(lái)代替docker。
本文會(huì)做詳細(xì)解讀,并介紹docker與containerd的關(guān)系,以及為什么containerd是更好的選擇。
這里先回答下TKE用戶關(guān)心的問(wèn)題:我們的集群該怎么辦?
TKE集群該怎么辦
·TKE早在2019年5月就已經(jīng)支持選擇containerd作為容器運(yùn)行時(shí)。如果新建集群,推薦選擇containerd作為容器運(yùn)行時(shí)
·已有集群在升級(jí)到K8s 1.23(假定TKE第一個(gè)不支持dockershim的K8s版本,也可能是1.24)之前,仍然可以繼續(xù)使用docker作為容器運(yùn)行時(shí)
·已有集群通過(guò)TKE集群升級(jí)功能升級(jí)到1.23時(shí),TKE會(huì)提供切換運(yùn)行時(shí)為containerd的選項(xiàng)。當(dāng)然,這種情況下沒(méi)辦法做到Pod不受影響,只能采用重裝節(jié)點(diǎn)的方式來(lái)升級(jí)
·已有集群也可以將運(yùn)行時(shí)切換為containerd,新增節(jié)點(diǎn)會(huì)使用containerd,存量節(jié)點(diǎn)不受影響仍然使用docker(注意:這會(huì)造成同一集群中docker節(jié)點(diǎn)與containerd節(jié)點(diǎn)共存,如果·有使用Docker in Docker,或者其他依賴節(jié)點(diǎn)上docker daemon與docker.sock的業(yè)務(wù),需要提前采取措施來(lái)避免產(chǎn)生問(wèn)題,例如通過(guò)按節(jié)點(diǎn)標(biāo)簽調(diào)度,保證這類(lèi)業(yè)務(wù)調(diào)度到docker節(jié)點(diǎn);或者采用如前文所述在containerd集群運(yùn)行Docker in Docker的方案)
·當(dāng)然,在未來(lái)docker也有可能在內(nèi)部實(shí)現(xiàn)CRI或者添加一個(gè)dockershim進(jìn)程,如果docker做了相應(yīng)適配,TKE這邊在未來(lái)也會(huì)進(jìn)行支持。
解讀K8s棄用dockershim
Docker support in the kubelet is now deprecated and will be removed in a future release.The kubelet uses a module called"dockershim"which implements CRI support for Docker and it has seen maintenance issues in the Kubernetes community.We encourage you to evaluate moving to a container runtime that is a full-fledged implementation of CRI(v1alpha1 or v1 compliant)as they become available.(#94624[1],dims[2])[SIG Node]
K8s在1.20的change log中提到K8s將于1.20版本開(kāi)始逐步放棄對(duì)Docker的支持。在K8s的官方博客中也提到具體的聲明和一些FAQ。
·Don't Panic:Kubernetes and Docker[3]
·Dockershim FAQ[4]
在博客中提到K8s將在1.20版本中添加不推薦使用docker的信息,且最早將于1.23版本中把dockershim從kubelet中移除,屆時(shí)用戶將無(wú)法使用docker作為K8s集群的運(yùn)行時(shí),不過(guò)通過(guò)docker構(gòu)建的鏡像在沒(méi)有docker的K8s集群中依然可以使用。
“寄生”在kubelet中的dockershim
本次改動(dòng)主要內(nèi)容是準(zhǔn)備刪除kubelet中的dockershim,當(dāng)然這種做法也是符合預(yù)期的。
在早期rkt和docker爭(zhēng)霸時(shí),kubelet中需要維護(hù)兩坨代碼分別來(lái)適配docker和rkt,這使得kubelet每次發(fā)布新功能都需要考慮對(duì)運(yùn)行時(shí)組件的適配問(wèn)題,嚴(yán)重拖慢了新版本發(fā)布速度。
另外虛擬化已經(jīng)是一個(gè)普遍的需求,如果出現(xiàn)了類(lèi)型的運(yùn)行時(shí),SIG-Node小組可能還需要把和新運(yùn)行時(shí)適配的代碼添加到kubelet中。這種做法并不是長(zhǎng)久之計(jì),于是在2016年,SIG-Node提出了容器操作接口CRI(Container Runtime Interface)。
CRI是對(duì)容器操作的一組抽象,只要每種容器運(yùn)行時(shí)都實(shí)現(xiàn)這組接口,kubelet就能通過(guò)這組接口來(lái)適配所有的運(yùn)行時(shí)。但Docker當(dāng)時(shí)并沒(méi)有(也不打算)實(shí)現(xiàn)這組接口,kubelet只能在內(nèi)部維護(hù)一個(gè)稱之為“dockershim”組件,這個(gè)組件充當(dāng)了docker的CRI轉(zhuǎn)接器,kubelet在創(chuàng)建容器時(shí)通過(guò)CRI接口調(diào)用dockershim,而dockershim在通過(guò)http請(qǐng)求把請(qǐng)求交給docker。
于是kubelet的架構(gòu)變成下圖這樣:
在使用實(shí)現(xiàn)了CRI接口的組件作為容器運(yùn)行時(shí)的情況下,kubelet創(chuàng)建容器的調(diào)用鏈如圖中紅色箭頭所示,kubelet中的ContainerManager可以直接通過(guò)CRI調(diào)用到容器運(yùn)行時(shí),這過(guò)程中只需要一次grpc請(qǐng)求;
而在使用docker時(shí),ContainerManager會(huì)走圖中藍(lán)色的調(diào)用鏈,CRI的請(qǐng)求通過(guò)unix:///var/run/dockershim.sock流向dockershim,dockershim做轉(zhuǎn)換后把請(qǐng)求轉(zhuǎn)發(fā)給docker,至于為什么docker后面還有個(gè)containerd稍后會(huì)講到。
在kubelet中實(shí)現(xiàn)docker的轉(zhuǎn)接器本來(lái)就是一種不優(yōu)雅的實(shí)現(xiàn),這種做法讓調(diào)用鏈變長(zhǎng)且不穩(wěn)定性,還給kubelet的維護(hù)添加了額外工作,把這部分內(nèi)容從kubelet刪掉就是時(shí)間問(wèn)題了。
棄用Docker后會(huì)有什么不同?
If you’re an end-user of Kubernetes,not a whole lot will be changing for you.This doesn’t mean the death of Docker,and it doesn’t mean you can’t,or shouldn’t,use Docker as a development tool anymore.Docker is still a useful tool for building containers,and the images that result from running docker build can still run in your Kubernetes cluster.
消息一出,大家最關(guān)心的事情應(yīng)該就是棄用docker后到底會(huì)產(chǎn)生什么影響?
官方的答復(fù)是:Don't Panic!隨后又重點(diǎn)解釋了幾個(gè)大家最關(guān)心的問(wèn)題,我們來(lái)分析下官方提到的這些方面:
·正常的K8s用戶不會(huì)有任何影響
是的,生產(chǎn)環(huán)境中高版本的集群只需要把運(yùn)行時(shí)從docker切換到其他的runtime(如containerd)即可。containerd是docker中的一個(gè)底層組件,主要負(fù)責(zé)維護(hù)容器的生命周期,跟隨docker經(jīng)歷了長(zhǎng)期考驗(yàn)。同時(shí)2019年初就從CNCF畢業(yè),可以單獨(dú)作為容器運(yùn)行時(shí)用在集群中。TKE也早在2019年就已經(jīng)提供了containerd作為運(yùn)行時(shí)選項(xiàng),因此把runtime從docker轉(zhuǎn)換到containerd是一個(gè)基本無(wú)痛的過(guò)程。CRI-O是另一個(gè)常被提及的運(yùn)行時(shí)組件,由redhat提供,比containerd更加輕量級(jí),不過(guò)和docker的區(qū)別較大,可能轉(zhuǎn)換時(shí)會(huì)有一些不同之處。
·開(kāi)發(fā)環(huán)境中通過(guò)docker build構(gòu)建出來(lái)的鏡像依然可以在集群中使用
鏡像一直是容器生態(tài)的一大優(yōu)勢(shì),雖然人們總是把鏡像稱之為“docker鏡像”,但鏡像早就成為了一種規(guī)范了。具體規(guī)范可以參考image-spec[5]。在任何地方只要構(gòu)建出符合Image Spec的鏡像,就可以拿到其他符合Image Spec的容器運(yùn)行時(shí)上運(yùn)行。
·在Pod中使用DinD(Docker in Docker)的用戶會(huì)受到影響
有些使用者會(huì)把docker的socket(/run/docker.sock)掛載到Pod中,并在Pod中調(diào)用docker的api構(gòu)建鏡像或創(chuàng)建編譯容器等,官方在這里的建議是使用Kaniko、Img或Buildah。我們可以通過(guò)把docker daemon作為DaemonSet或者給想要使用docker的Pod添加一個(gè)docker daemon的sidecar的方式在任意運(yùn)行時(shí)中使用DinD的方案。TKE也專(zhuān)門(mén)為在containerd集群中使用DinD提供了方案,詳見(jiàn)在containerd中使用DinD[6]。
containerd的今生前世
所以containerd到底是個(gè)啥?和docker又是什么關(guān)系?可能有些同學(xué)看到博客后會(huì)發(fā)出這樣的疑問(wèn),接下來(lái)就給同學(xué)們講解下containerd和docker的淵源。
docker與containerd
2016年,docker把負(fù)責(zé)容器生命周期的模塊拆分出來(lái),并將其捐贈(zèng)給了社區(qū),也就是現(xiàn)在的containerd。docker拆分后結(jié)構(gòu)如下圖所示(當(dāng)然docker公司還在docker中添加了部分編排的代碼)。
在我們調(diào)用docker命令創(chuàng)建容器后,docker daemon會(huì)通過(guò)Image模塊下載鏡像并保存到Graph Driver模塊中,之后通過(guò)client調(diào)用containerd創(chuàng)建并運(yùn)行容器。我們?cè)谑褂胐ocker創(chuàng)建容器時(shí)可能需要使用--volume給容器添加持久化存儲(chǔ);還有可能通過(guò)--network連接我們用docker命令創(chuàng)建的幾個(gè)容器,當(dāng)然,這些功能是docker中的Storage模塊和Networking模塊提供給我們的。但K8s提供了更強(qiáng)的卷掛載能力和集群級(jí)別的網(wǎng)絡(luò)能力,在集群中kubelet只會(huì)使用到docker提供的鏡像下載和容器管理功能,而編排、網(wǎng)絡(luò)、存儲(chǔ)等功能都不會(huì)用到。下圖中可以看出當(dāng)前的模式下各模塊的調(diào)用鏈,同時(shí)圖中被紅框標(biāo)注出的幾個(gè)模塊就是kubelet創(chuàng)建Pod時(shí)所依賴的幾個(gè)運(yùn)行時(shí)的模塊。
containerd被捐贈(zèng)給CNCF社區(qū)后,社區(qū)給其添加了鏡像管理模塊和CRI模塊,這樣containerd不只可以管理容器的生命周期,還可以直接作為K8s的運(yùn)行時(shí)使用。于是containerd在2019年2月從CNCF社區(qū)畢業(yè),正式進(jìn)入生產(chǎn)環(huán)境。下圖中能看出以containerd作為容器運(yùn)行時(shí),可以給kubelet帶來(lái)創(chuàng)建Pod所需的全部功能,同時(shí)還得到了更純粹的功能模塊以及更短的調(diào)用鏈。
從上面的對(duì)比可以看出從containerd被捐贈(zèng)給社區(qū)開(kāi)始,就一直以成為簡(jiǎn)單、穩(wěn)定且可靠的容器運(yùn)行時(shí)為目標(biāo);而docker則是希望能成為一個(gè)完整的產(chǎn)品。官方文檔中也提到了這一點(diǎn),docker為了給用戶更好的交互和使用體驗(yàn)以及更多的功能,提供了很多開(kāi)發(fā)人員所需要的特性,同時(shí)為了給swarm做基礎(chǔ),提供了網(wǎng)絡(luò)和卷的功能。而這些功能其實(shí)都是是K8s用不上的;containerd則相反,僅提供了kubelet創(chuàng)建Pod所需要的基礎(chǔ)功能,當(dāng)然這換來(lái)的就是更高的魯棒性以及更好的性能。在一定程度上講,即使在kubelet 1.23版本之后docker提供了CRI接口,containerd仍然是更好的選擇。
在Kubernetes集群中使用containerd
當(dāng)然現(xiàn)在有諸多的CRI實(shí)現(xiàn)者,比較主要的除了containerd還有CRI-O。CRI-O是主要由Red Hat員工開(kāi)發(fā)的CRI運(yùn)行時(shí),完全和docker沒(méi)有關(guān)系,因此從docker遷移過(guò)來(lái)可能會(huì)比較困難。無(wú)疑containerd才是docker被拋棄后的CRI運(yùn)行時(shí)的最佳人選,對(duì)于開(kāi)發(fā)同學(xué)來(lái)說(shuō)整個(gè)遷移過(guò)程應(yīng)該是無(wú)感知的,不過(guò)對(duì)于部分運(yùn)維同學(xué)可能會(huì)比較在意部署和運(yùn)行中細(xì)節(jié)上的差異。接下來(lái)我們重點(diǎn)介紹下在K8s中使用containerd和docker的幾處區(qū)別。
·容器日志對(duì)比項(xiàng)
cni配置方式的區(qū)別在使用docker時(shí),kubelet中的dockershim負(fù)責(zé)調(diào)用cni插件,而containerd的場(chǎng)景中containerd中內(nèi)置的containerd-cri插件負(fù)責(zé)調(diào)用cni,因此關(guān)于cni的配置文件需要放在containerd的配置文件中(/etc/containerd/containerd.toml):
[plugins.cri.cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
·stream服務(wù)的區(qū)別
說(shuō)明:
Kubectl exec/logs等命令需要在apiserver跟容器運(yùn)行時(shí)之間建立流轉(zhuǎn)發(fā)通道。
如何在containerd中使用并配置Stream服務(wù)?
Docker API本身提供stream服務(wù),kubelet內(nèi)部的docker-shim會(huì)通過(guò)docker API做流轉(zhuǎn)發(fā)。而containerd的stream服務(wù)需要單獨(dú)配置:
[plugins.cri]
stream_server_address="127.0.0.1"
stream_server_port="0"
enable_tls_streaming=false[plugins.cri]stream_server_address="127.0.0.1"stream_server_port="0"enable_tls_streaming=false
K8s 1.11前后版本配置區(qū)別是什么?
containerd的stream服務(wù)在K8s不同版本運(yùn)行時(shí)場(chǎng)景下配置不同。
·在K8s 1.11之前:kubelet不會(huì)做stream proxy,只會(huì)做重定向。即kubelet會(huì)將containerd暴露的stream server地址發(fā)送給apiserver,并讓apiserver直接訪問(wèn)containerd的stream服務(wù)。此時(shí),您需要給stream服務(wù)轉(zhuǎn)發(fā)器認(rèn)證,用于安全防護(hù)。
·在K8s 1.11之后:K8s1.11引入了kubelet stream proxy[7],使containerd stream服務(wù)只需要監(jiān)聽(tīng)本地地址即可。
在TKE集群中使用containerd
從2019年5月份開(kāi)始,TKE就開(kāi)始支持把containerd作為容器運(yùn)行時(shí)選項(xiàng)之一。隨著TKE逐步在containerd集群中支持日志收集服務(wù)和GPU能力,2020年9月份containerd在TKE也摘掉了Beta版本的標(biāo)簽,可以正式用于生產(chǎn)環(huán)境中了。在長(zhǎng)期使用中,我們也發(fā)現(xiàn)了一些containerd的問(wèn)題并且及時(shí)進(jìn)行了修復(fù),如:
·由于錯(cuò)誤處理問(wèn)題導(dǎo)致的Pod Terminating
·由于內(nèi)核版本問(wèn)題導(dǎo)致鏡像文件丟失
想要在TKE集群中使用containerd作為運(yùn)行時(shí)有三種方式:
1.在創(chuàng)建集群時(shí),選擇1.12.4及以上版本的K8s后,選擇containerd為運(yùn)行時(shí)組件即可
2.在已有docker集群中,通過(guò)創(chuàng)建運(yùn)行時(shí)為containerd的節(jié)點(diǎn)池來(lái)創(chuàng)建一部分containerd節(jié)點(diǎn)(新建節(jié)點(diǎn)池>更多設(shè)置>運(yùn)行時(shí)組件)
3.在已有docker集群中,修改集群或者節(jié)點(diǎn)池的"運(yùn)行時(shí)組件"屬性為"containerd"
注意:后兩種方式會(huì)造成同一集群中docker節(jié)點(diǎn)與containerd節(jié)點(diǎn)共存,如果有使用Docker in Docker,或者其他依賴節(jié)點(diǎn)上docker daemon與docker.sock的業(yè)務(wù),需要提前采取措施來(lái)避免產(chǎn)生問(wèn)題,例如通過(guò)按節(jié)點(diǎn)標(biāo)簽調(diào)度,保證這類(lèi)業(yè)務(wù)調(diào)度到docker節(jié)點(diǎn);或者采用如前文所述在containerd集群運(yùn)行Docker in Docker的方案。
現(xiàn)階段關(guān)于containerd和docker選擇問(wèn)題可以查看文檔如何選擇Containerd和Docker[8]。
參考資料
[1]#94624:https://github.com/kubernetes/kubernetes/pull/94624
[2]dims:https://github.com/dims
[3]Don't Panic:Kubernetes and Docker:https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/
[4]Dockershim FAQ:https://kubernetes.io/blog/2020/12/02/dockershim-faq/
[5]image-spec:https://github.com/opencontainers/image-spec
[6]在containerd中使用DinD:https://tencentcloudcontainerteam.github.io/2020/12/08/dind-in-containerd/
[7]kubelet stream proxy:https://github.com/kubernetes/kubernetes/pull/64006
[8]如何選擇Containerd和Docker:https://cloud.tencent.com/document/product/457/35747
[9]Dockershim Removal Kubernetes Enhancement Proposal:https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/1985-remove-dockershim
[10]kubernetes CHANGELOG-1.20:https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation