作为后端研发工程师,之前的工作中还是涉及到一部分K8S的工作。在当时的工作场景下,我们通过使用Kubernetes(简称k8s)+docker编排部署的架构方案来提供云原生的在线服务。工作内容包括:编写k8s的yaml文件,通过maven和gradle将工程项目打包成docker镜像,使用kuberctl命令进行运维部署等。在工作过程中学习到了很多Docker和K8s相关的知识,比如写时复制、共享内存、etcd、kube proxy、负载均衡等等,后来还接触了一些K9S。当时在我的心中,Kubernetes+Docker的方案已然是一个很成熟的解决方案。
当本人看到下述标题(参考链接2):
心里难免咯噔一下。
我才刚刚接触这技术不久啊……
“开始了吗(指基于K8S编排Docker的分布式服务架构)”。
“已经结束了(K8S弃用Docker)”
在这篇文章中分享一下关于“k8s弃用docker”的相关学习笔记。
另外:后文提到的"美国小百度"指代google,请不要太在意这个名称。
相比于网络上的众说纷纭,首先我们应该先查找的还是官方的blog、CHANGELOG或者release note等权威信息。在官方的GitHub CHANGELOG中,我们可以看到官方的声明:
"Changes by kind"一章的"Deprecation"这一小节开篇直接表示kubelet废弃了对Docker的支持。kubelet使用了"dockershim"模块,该模块实现了CRI(容器运行时接口)接口,以支持Docker。官方同时表示:Docker容器的用户最好评估一下容器迁移的成本,将原先的Docker容器迁移为一个更成熟的实现CRI接口的容器。
注:kubelet是在每个 Node 节点上运行的主要 “节点代理”。基于 PodSpec 来工作的。每个 PodSpec 是一个描述 Pod 的 YAML 或 JSON 对象。kubelet 接受通过各种机制(主要是通过 apiserver)提供的一组 PodSpec,并确保这些 PodSpec 中描述的容器处于运行状态且运行状况良好。kubelet 不管理不是由 Kubernetes 创建的容器(参考链接4)。
同样官方在官方的CHANGELOG当中我们可以看到,Docker编译的镜像依然可以在Kubernetes集群环境下运行:
在Kubernetes停止支持Dockershim之后,在Kubernetes新版本中运行Docker镜像依然是没有问题的,那么停止支持Dockershim 到底意味着什么呢?
k8s官方CHANGELOG1.20.md链接
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation
第一步,我们需要明白,原生的Docker并不支持CRI(容器运行时接口)这一Kubernetes运行时API,而Kubernetes用户一直以来所使用的其实是名为“dockershim”的桥接服务。Dockershim能够转换Docker API与CRI,但在后续版本当中,Kubernetes将不再提供这项桥接服务。
另一方面Docker的强大无需赘述。作为地球上第一个实用化的虚拟化容器技术,Docker可用于创建开发环境、中间件部署、进程隔离等方方面面的应用。曾经的萌新安装一个MySQL或者Mongo需要找到官方教程一步步走,如今只要docker pull一下,docker run一下即可,极大减少了研发的工作量和运维成本。
所以又回到那个问题,既然Docker这么强大,那么K8s到底为什么要弃用Docker呢?
02 为什么弃用Docker?
关于“k8s弃用docker”这一现状,我们首先分析Docker在现有的kubernetes架构中的作用
前文所述,K8s底层需要与CRI通信,而Docker又不支持CRI,为了使用Docker,就必须使用桥接模式对Docker进行兼容。
此处贴一下Docker的架构图。
由于k8s本身提供了网络、proxy、volume、namespace等功能,docker中对应的这些功能对于k8s而言就是累赘。Kubernetes对docker的功能需求,实际上只有红框之内的那部分。Docker网络与存储卷都被排除在外。而这些用不到的功能本身就可能带来安全隐患(注:虽然的确功能越多安全隐患越大,但走到这一步难免不让人觉得是各大厂围剿Docker的阴谋,理论上可以完全基于Docker本身的网络、挂载、代理等原生机制进行二次开发)。
解决方案就是基于k8s的CRI(容器运行时接口)。
目前有两种CRI解决方案:containerd和CRI-O。由于containerd是从Docker项目中衍生出来的解决方案,本文只介绍一下containerd,对于CRI-O不做过多介绍。
有读者可能会问,既然兼容Docker这么麻烦,为什么不在一开始Kubernetes就设计好相应规范,直接在更底层支持Docker呢?
背后故事就是一个可可爱爱没有脑袋的商业战争故事啦。
Docker作为最早的容器化技术,于2013年推出并大杀特杀。众多大厂(比如美国小百度)对Docker公司提出了收购意愿,然而倔强的Docker公司并不愿意委身于大厂,于是以美国小百度为首的一系列大公司祭出了K8s对Docker展开了胡萝卜加大棒的策略。
最开始的阶段,由于Docker实在是太火,K8s只能被动兼容Docker,采用的技术包括硬编码和shim。与此同时,随着云原生技术的发展,越来越多更轻量级更定制化的容器技术出现了,为了兼容这些容器,同时也为了围剿Docker,以美国小百度为首的一系列公司在背地里偷偷地提出并完善CRI接口,为杀死Docker慢慢磨刀。
这一操作实质上麻痹了Docker公司,导致Docker公司在kubernetes面前逐渐变得被动。
另一方面,当Docker刚刚推出的时候,没有人预想到容器还需要编排。为了和kubernetes竞争,Docker公司推出了Docker Swarm技术,并推出了containerd技术。然而,正如大家今天所看到的的,Docker Swarm在Kubernets面前不能说是没有赢,只能说是体无完肤并且轰轰烈烈地失败了。
于是Docker公司只能后退一步,将containerd技术捐献给CNCF基金会(Cloud Native Computing Foundation,云原生计算基金会)。而Kubernetes也算见好就收,既然Docker已先退了一步,那就优先支持脱胎于原生Docker容器技术的containerd容器吧。
这背后的故事本文不做过多赘述,有兴趣的读者请自行阅读参考链接6(https://www.qikqiak.com/post/containerd-usage/)。
containerd是什么
首先查看Docker的官方博客,原文如下(引用字数有限制,不全部引用):
Containerd was designed to be used by Docker and Kubernetes as well as any other container platform that wants to abstract away syscalls or OS specific functionality to run containers on linux, windows, solaris, or other OSes.
https://www.docker.com/blog/what-is-containerd-runtime/
containerd被设计于能够被Docker、Kubernetes或其他容器平台使用,并在linux、Windows、solaris等操作系统使用的容器。containerd的设计者希望能将containerd设计成一个没有冗余功能的容器。这里面的冗余功能包括网络、volume挂载等。以容器的网络功能为例,在如今的分布式服务环境下,相比于早期的基于linux的抽象netlinking技术,基于SDN技术和服务发现技术的新兴网络技术更具有平台稳定性。因此,在containerd中,设计者所做的只是选择了一个健壮的event系统,以便多个消费者可以订阅他们关心的event。设计者还公开了一个任务API,它允许用户创建一个正在运行的任务,能够向容器的网络名称空间添加接口,然后启动容器的进程,而无需在容器生命周期的各个点上使用复杂的hook技术。
此外,containerd设计了一个支持OCI(Open Container Initiative)和Docker镜像格式的存储分发系统。通过ContainerDAPI,用户有一个完整的内容寻址存储系统,它不仅适用于图像,还适用于元数据、检查点和附加到容器的任意数据。
(调皮一下,OCI的官网页面,看到某菊花厂。)
引用参考链接6,containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,containerd 可以负责干下面这些事情:
管理容器的生命周期(从创建容器到销毁容器)
拉取/推送容器镜像
存储管理(管理镜像及容器数据的存储)
调用 runc 运行容器(与 runc 等容器运行时交互)
管理容器网络接口及网络
下图是K8s在Docker和containerd环境下的调用链路图,可见通过使用containerd替代Docker,移除了调用链路中的dockershim与docker两个节点,使K8s的底层调用链路得到了优化。
使用containerd替换Docker之后,此时不能再使用 docker ps 或 docker inspect 命令来获取容器信息。由于不能列出容器,因此也不能获取日志、停止容器,甚至不能通过 docker exec 在容器中执行命令(注:全部使用kubectl命令来完成就可以啦)。
当然使用者仍然可以下载镜像,或者用 docker build 命令构建镜像,但用 Docker 构建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。为了在 Kubernetes 中使用,需要把镜像推送到镜像仓库中去。
到了 containerd 1.1 版本后,containerd进一步去掉了 CRI-Contained 这个 shim,直接把适配逻辑作为插件的方式集成到了 containerd 主进程中,现在这样的调用就更加简洁了。
将K8s中的Docker容器替换为containerd容器并不困难。此处请读者自行阅读参考链接7-11进行containerd+k8s的安装和使用,本文对于k8s结合containerd不做过多的阐述。
此处只贴一下安装完containerd之后的ctr命令的结果(直接输入ctr命令,输出containerd支持的操作)。可见ctr关于容器的操作比docker少了很多,主要是容器的创建、删除、运行,没有log、执行(exe)、进程打印(ps)等操作。
ctr
NAME:
ctr -
__
_____/ /______
/ ___/ __/ ___/
/ /__/ /_/ /
\___/\__/_/
containerd CLI
USAGE:
ctr [global options] command [command options] [arguments...]
VERSION:
v1.5.5
DESCRIPTION:
ctr is an unsupported debug and administrative client for interacting
with the containerd daemon. Because it is unsupported, the commands,
options, and operations are not guaranteed to be backward compatible or
stable from release to release of the containerd project.
COMMANDS:
plugins, plugin provides information about containerd plugins
version print the client and server versions
containers, c, container manage containers
content manage content
events, event display containerd events
images, image, i manage images
leases manage leases
namespaces, namespace, ns manage namespaces
pprof provide golang pprof outputs for containerd
run run a container
snapshots, snapshot manage snapshots
tasks, t, task manage tasks
install install a new package
oci OCI tools
shim interact with a shim directly
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--debug enable debug output in logs
--address value, -a value address for containerd's GRPC server (default: "/run/containerd/containerd.sock") [$CONTAINERD_ADDRESS]
--timeout value total timeout for ctr commands (default: 0s)
--connect-timeout value timeout for connecting to containerd (default: 0s)
--namespace value, -n value namespace to use with commands (default: "default") [$CONTAINERD_NAMESPACE]
--help, -h show help
--version, -v print the version
另外关于弃用Dockershim带来的影响和FAQ,请详细阅读Kubernetes的官方链接(参考链接13和参考链接14)。
官方给出的Docker迁移CRI容器注意事项如下(参考链接13):
根据Kubernetes提供的官方数据(参考链接15),可以看出,使用containerd容器替代Docker容器之后,系统的启动时间、CPU使用、内存使用均得到了一定程度的优化。
Kubernetes弃用Docker,实际上是Kubernetes弃用了Dockershim这一模块,减少了对Docker容器的完整的支持,Docker编译出的镜像依然是可以在Kubernetes中编排使用的。弃用Dockershim的背后的技术原因,主要是因为Docker不支持Kubernetes底层的CRI接口。
与此同时,为了和Kubernetes对接,Docker公司开源并捐献了containerd。Kubernetes官方也推荐使用containerd容器替换现有的Docker容器。containerd其实算是一个精简版本的Docker,通过将网络、volume挂载、代理(proxy)等功能剥离,提供了k8s运行环境下的最小的容器单元。
梳理k8s启用Docker的完整流程,包括containerd从提出到演化的流程,我们可以看出,导致Docker被弃用的主要原因并不是技术问题,更多的还是商业竞争。长期来看,随着Docker Swarm的溃败,K8s已经逐渐占领了云原生的垄断地位,containerd大规模取代Docker只是时间问题。
此外,我们需要持续关注其他的容器解决方案,比如Podman、Buildah等。但是从目前Kubernetes官方对containerd的推荐,以及containerd脱胎于Docker的"高贵血统"观察,其他的容器技术想要在k8s容器市场分一杯羹并不容易。包括Podman和Buildah在内的其他容器技术,具体请参考链接12。
下图左为Podman的标识(是你,三地鼠!),下图右为Buildah的标识(所以鬼佬就这么喜欢小动物吗)。
ps:未来一段时间内可能都不会涉及到K8s的部署、安装和运维等工作,短期内可能不会再更新k8s、云原生、容器等相关的文章了。
http://dockone.io/article/450984
https://mp.weixin.qq.com/s/J21CLwgF1xLy-6fo1f6Lgw
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation
https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kubelet/
https://www.docker.com/blog/what-is-containerd-runtime/
https://www.qikqiak.com/post/containerd-usage/
https://www.modb.pro/db/100271
https://segmentfault.com/a/1190000039692099
https://zhuanlan.zhihu.com/p/359719736
https://kubernetes.io/zh/docs/setup/production-environment/container-runtimes/#containerd
https://cloud.tencent.com/developer/article/1810553
https://www.infoq.cn/article/ggimepmcvdqw4eos2cig
https://kubernetes.io/zh/blog/2020/12/02/dockershim-faq/
https://kubernetes.io/zh/docs/tasks/administer-cluster/migrating-from-dockershim/check-if-dockershim-deprecation-affects-you/
https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga/