kubernetes移除Docker?
两周前,Kubernetes在其最新的Changelog中宣布1.20之后将要弃用dockershime,也就说Kubernetes将不再使用Docker做为其容器运行时。
Dockershim deprecation Docker as an underlying runtime is being deprecated. Docker-produced images will continue to work in your cluster with all runtimes, as they always have.
这一消息持续发酵,掀起了不小的波澜,毕竟Kubernetes+Docker的经典组合是被市场所认可的,大量企业都在使用。看上去这个“弃用”的决定有点无厘头,那么为什么Kubernetes会做出这样的决定?这样做的原因又是如何?又会带来那些影响呢?今天,咱们就来掰扯一下。
首先,看一下目前在Kubernetes上如何使用Docker运行时来创建一个容器,如下:
-
Kubelet 通过 CRI (Container Runtime Interface)接口调用 dockershim,来创建一个容器。从图上不难看出kubelet的这一步调用,是自己在调用自己,也确实如此,这是因为dockershim的代码目前是内嵌在 Kubelet中的,所以请求的发送和接收都是Kubelet;
-
dockershim收到请求后,会转换成Docker API请求发给Docker Daemon去创建一个容器;
-
DockerDaemon收到后,在将请求发送给containerd创建一个容器
-
containerd收该请求后,会创建一个叫做containerd-shim的进程,对容器的操作统统交给containerd-shim去完成;
-
containerd-shim再调用runC来实现容器的的启动
-
runC完成容器的启动后便会退出,containerd-shim会成为容器的父进程
所以不难看出,一旦从kubelet中移除dockershim,上述流程中的第一步就会无法完成,创建容器的请求自然就无法发送到Docker daemo上,真可谓釜底抽薪,一招毙命。不过,这个流程确实繁琐,要在CRI,dockershim,docker daemo之间绕一下,聪明的你肯定会对此产生各种疑问,“为什么kubelet不直接去调用containerd呢?这个CRI又是什么呢?为什么kubelet不能直接发给Docker Dameon呢? 如果在Kubernetes上使用rkt又应该怎么做呢?”
有道是有人的地方就有江湖,有利益,有帮派,有明争暗斗。想当年kubelet想要创建容器直接跟Docker Daemon说一声就行,而那时containerd还没出生,Docker Daemon自己调一下libcontainer这个库就可以让容器跑起来。但随着容器市场的逐渐火热,每个厂家都想扎进来,“这大蛋糕可不能让Docker一家吃完啊,倒不是馋那一两口蛋糕,主要是考虑到胆固醇太高对Docker的身体不好,所以大家应该踊跃的为Docker分担一下”。Docker也知道众人拾柴火焰高的道理,这把大火能否将传统技术厂商的城池化为灰烬,成为吾之沃土,靠一个人是远远不够的,于是振臂高呼“我要健康,我要放火”,接着就联合各路的带头大哥,鼓捣出一个OCI标准,用来规范容器市场,营造更为和谐的容器生态环境。而且Docker为了符合OCI标准,更是以身作则,直接挥刀自宫,把xx切了,额。。。错了,是把runtime及其管理功能从Docker daemon中剥离了出来,随后Docker又把libcontainer封装了一下,变成runC,做了投名状,捐给了OCI。
Docker所作的可谓仁至义尽,但还是有人不满足,CoreOS还想搞事,也想成为Kubernetes的默认runtime,与Docker并驾齐驱。Kubernetes也在考虑我不能和某一家的容器技术栈绑死,用户的口味也各异,连区区豆腐脑都有甜咸之分,我这高科技产物也应该满足用户的差异化需求,再加上Google和CoreOS的亲密关系,结果就真给合到kubelet里了,从而实现了对多个容器引擎的兼容,但这事费力不讨好,每次更新kubelet都得考虑Docker和rkt,负责维护kubelet的SIG Node小组越做越难受,直接掀了桌子。
这活还得继续干,不过得换个思路,干脆把kubelet对容器的操作抽象成一个接口,kubelet只要和这个接口交互就行了,Docker,rkt等容器项目提供一个该接口的实现并暴露给kubelet不就妥了!于是Kubernetes在1.5推出了CRI,有了CRI接口后kubelet就不再直接和容器运行时Say Hi了,而是与CRI进行通信,从而实现了平台和容器运行时的解耦。响应CRI的组件称为CRI shim,它主要实现CRI规定的每个接口,然后把请求转换成对后端容器,比如Docker或rkt的请求或者操作。
这个CRI接口只是Kubernetes自家标准,而当时Kubernetes也没有现在今的地位,所以Docker觉得“凭啥你一个平台说风就是雨?”,不用你,我还有Swarm,并未立即针对CRI进行相应的开发。
Anyway,无论容器厂家们怎么来实现,容器运行时的三层抽象此时已经很清晰了,如下:
Orchestration API -> Container API -> Kernel API
具体一点就是:
Kubernetes API -> CRI-runtime > OCI-runtime
那么在Kubernetes中所划分的CRI-runtime/OCI-runtime都有那些呢?
-
cri-runtime: containerd/CRI-O
-
oci-runtime: runC/Kata/gVisor
下图很清晰的解释了这三者是如何协同工作的:
但如今Kubernetes的市场表现相当亮眼,占有率一度接近8成,而Swarm的表现就相当的一般了。按理说都这样了,得认怂,不能硬刚,毕竟人家拳头大啊。但Docker到现在都没有实现对CRI支持,依旧通过dockershim这样一个桥接服务来实现。你是不是觉得Docker的头太铁了,非要撞到南墙才肯回头?其实也不是,这里就要谈谈CNCF和containerd了。
Kubernetes最初的时候使用Docker作为运行时,一是因为Docker拥有大量的用户,考虑到技术惯性,能够很容易的获得这部分用户;二是当时也没有其他的选择了,所以你才能看到kubelet中有dockershim的代码。但随着容器技术的快速发展,以及CRI的出现,Docker已经不在是唯一的选择了。Docker也考虑是否要实现CRI,但毕竟Docker本身包含了太多功能,如果仅仅作为一个运行时,说好听点是“大材小用”,说直白点是“你不够纯”。
前面我提到过,为了符合OCI标准,Docker daemo拆分出了runC以及containred。显然,containerd作为一个单纯的high-level容器运行时更贴合Kubernetes的需求。在2017年Docker把containerd年捐给了CNCF,随后一年Kubernetes开始支持containerd作为容器运行时管理器,2019年containerd从CNCF毕业,所以单从技术上看,此时的containerd已经可以作为Kubernetes的容器运行时了,替换Docker运行时的条件已经成熟。这么一看,在Docker上去实现对CRI的支持确实没啥必要,这应该就“头铁”的原因吧。BTW,我不是阴谋论者,但这一步一步的掏空Docker怎么看都像一个局啊!
另外,在2017年,迫切渴望盈利的Docker公司做了一个重要的决定,宣布将原来开源的Docker项目更名为Moby,交给社区维护,自己仍然持有Docker的注册商标,这是什么骚操作呢?说白了,世上再无Docker的开源项目,无论是Docker EE还是CE,都属于是Docker公司的商业产品。这样一来,多年来所积累的庞大用户团体和资源顷刻间转移到Docker公司的商业产品上。
其实Docker想盈利,谁都能理解,但依靠开源软件赚钱本身就是一件难事,成功者寥寥无几,Redhat能成是因为手上有个OS,但Docker没有啊,而且云原生时代的OS,目前看恰恰就是Kubernetes。更何况容器所采用的cgroup,namespace,aufs,overlay2等技术,都是Linux Kernel所提供的能力,并不是Docker独创。另外,Docker能如此快速的发展壮大,离不开社区多年以来的贡献,但这手分的一点都不graceful,本以为能“我挥一挥衣袖,不带走一片云彩”,Docker却“我改一改名,把项目全部带走”,如此激进的做法,伤了人心,丢了人心。
从此,Docker告别了开源,这自然与强调技术开放的Kubernetes站在了两条道上,“道不同不相为谋”。所以,去除dockershim的举动看似突然,实则早已埋下伏笔,有技术上的可能性,也有商业上的利益驱使,更有价值观上的不同。
后面的故事我们就都知道了,Kubernetes社区表明由于维护dockershim对于开发以及运维人员来说是一项繁重的任务,所以在1.20中正式弃用dockershime,不在支持Docker作为运行时,建议大家使用包含CRI完整实现的容器运行时。听上去有理有据,合情合理,但我相信真实的情况应该是这样:
仅供娱乐,别当真
那么Docker运行时没了,在Kubernetes中还能使用那些运行时呢?我们知道Runtime负责提取和运行容器镜像,而Docker只是众多容器运行时中的一种,还有很多选择,比如Kubernetes所提到的CRI的完整实现containerd以及CRI-O,如下图:
相对于dockershim的方案,这两种方案确实要简洁很多。第一种,就是kubelet直接调containerd,如果之前使用Docker,那么迁移到containerd应该是个不错的选择;第二种,CRI-O是Kubernetes的运行时,它的目的式绕过现有的机制,直接操作Linux容器,但在稳定性上目前可能无法满足商用的需求。
如果你担心以后无法使用Docker镜像,也Duck不必。因为Docker镜像是符合OCI标准的,对于Kubernetes来说,只要镜像符合OCI标准,就OK,其他的runtime也是如此。
如果用户近期不想替换运行时怎么办?在1.20上仍然可以使用Docker作为runtime,只不过在启动kubelet时会出现一条告警。直到2021年末Kubernetes才会在1.23版本中将完全移除dockershim。如果你很固执,在1.23后仍然像继续使用Docker,部署一个dockershim呗,自己去集成起来,而且Mirantis和Docker也会合作出一个开源的dockershim组件。
综上,这个移除会有影响,但有限,并不是毁天灭地。相反,商业化的Docker被移除,对于云原生技术发展可能会起到更为正面的影响,推动其他开源容器项目的发展。
好了,就扯到这里了,下次咱们再聊聊容器运行时。
参考资料:
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#whats-new-major-themes
https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/
https://kubernetes.io/blog/2020/12/02/dockershim-faq/