阅读目录
Kubernetes 弃用 Docker,到底会影响到谁?
Kubernetes 在其最新的 Changelog 中宣布,自 Kubernetes 1.20 之后将弃用 Docker 作为容器运行时。那么这到底是怎么回事?开发者和企业会受到什么样到影响?
近几年,Kubernetes 已经成为自有机房、云上广泛使用的容器编排方案,最广泛的使用方式是 Kubernetes+Docker。从 DevOps 人员的角度,一面用 kubctl 命令、k8s API 来操作集群,一面在单机用 Docker 命令来管理镜像、运行镜像。
单独用 Docker 的情况,在一些公司的场景里面也是有的。一种场景是“只分不合”,把一台机器用 Docker 做资源隔离,但是不需要将多容器“编排”。单独用 Kubernetes,下层不是 Docker 的情况,并不算很多。
Kubernetes 和 Docker 的关系,简单来说,有互补,也有竞争。在一般的认知中,Kubernetes 和 Docker 是互补关系:
1 2 3 |
|
Docker 源于 Linux Container,可以将一台机器的资源分成 N 份容器,做到资源的隔离,并将可运行的程序定义为标准的 docker image;Kubernetes 则可以把不同机器的每份容器进行编排、调度,组成分布式系统。
Kubernetes 和 Docker 并不完全是“泾渭分明”的互补关系,它之间有重叠部分,也可以说成是竞争,主要在于几个点:
1 2 3 |
|
Kubernetes 在如何使用 Docker 方面,存在争议和变数。kubernetes 1.20 ChangeLog 中所谓要废弃 Docker 的传言,也是无风不起浪。换句话说,即便 Kubernetes 一直用 Docker,也不是用 Docker 的全部,多少是不一样的。
而且,“弃用 Docker”这个词本身有多重的含义,Docker 并非一个单层软件,Kubernetes 1.20 启用 dockershim 并不代表弃用了 Docker 的全部,仍有 containerd 可以对接 docker。
1.Kubernetes 有 CRI、OCI 两个容器标准
在目前广泛使用 kubernetes 与 Runtime 的桥接方案,CRI(Container Runtime Interface)与 OCI(Open Container Initiative)是“套娃“关系。Kubernetes 的 kubelet 调用 CRI,OCI 的实现者然后再调用 OCI。
下图也说明了 CRI 与 OCI 的关系:
从 Kubernetes 的角度,CRI 是与 CNI(网络)、CSI(存储)相同层级的接口。
OCI 是个自下而上的标准,也就是从实现抽象出接口,它是 Linux Foundation 主导的。Docker 实现的核心 RunC,也就是 OCI 的典型实现、标准实现。
CRI 是个自上而下的标准,源于 Kubernetes 对移植层(运行时)的要求。
容器引擎层自下而上定义 OCI,容器编排层自上而下定义 CRI,这也让它们出现了“套娃“运行情况。
在 Kubernetes 的 dockershim、cri-containerd、cri-o 三种实现中,RedHat 推崇的 cri-o 已经比较主流,它虽然仍是“套娃“,但已经比较精简。
下面是从 kubernetes 集群运行的全景图看 cri-o 的位置:
2.Docker 本源于 Linux Container
Docker 作为容器引擎,其实现的基础是 Linux Container——从内核到用户空间的机制。
Linux Container 可以分成两个部分,内核里面的 cgroup,用户空间的 lxc。Docker 最初实现的时候,也是完全基于 Linux Container 的,基于 lxc 做更上层的东西。
这张很多人认为“与事实不符“的图,其实代表了过去:
在 Docker 的发展过程中,最终启用了 C 语言写成 lxc,换成了 go 语言写成的 libcontainer。
下面的图也不是很新,但它更能表示 Docker 后续典型的架构,这里面已经没有了 lxc。
然而,万变不离其宗,Docker 实现的本源,还是 Linux Container。即便不用 lxc,当仍要用内核的 cgroup,并且模式也是类似的。
3.Kubernetes 最终如何桥接容器
从纯技术的角度,与其讨 Kubernetes 与 Docker 关系,不如讨论 Kubernetes 与最终容器实现层的关系。因为 Docker 这个名词,在不同的时候,有着不同的内涵、外延。
下面是 Docker 的简图:
从软件模块的角度,图中的 docker Engine、cri-containd、containd-shim、runC 都属于 Docker 体系的软件。
下图中的紫、橙、红三种颜色,代表了 dockershim、cri-containerd、cri-o 三种 CRI 的典型方式——流程在逐渐缩短,这也是 CRI 实现的一个演进过程。
如果是 kubelet 的 dockershim 模式(紫色),流程是这样的:
1 2 3 4 5 6 7 |
|
如果是 kubelet 的 cri-containerd 模式(橙色),流程是这样的:
1 2 3 4 5 |
|
在很多人的印象中,如果不用 docker 守护进程,就相当于“弃用 docker“,这其实也就是从 dockershim 到 containerd 的变化。从另一个角度来说,containerd 这个守护进程,也是 docker 组织做的。
如果是 kubelet 的 cri-o 模式(红色),则更加简练:
1 2 3 |
|
如果以 kubelet 调用 CRI 为起点,OCI 的 runC 调用为终点,三种模式经历的可执行程序分别是:
1 2 3 4 5 |
|
显然在这种 Red Hat 推崇的 cri-o 模式下,Docker 体系的 containerd 也用不着了,只剩 runC 这个命令行的程序。runC 也是用 go 写成的,里面有调用 libcontainer。
当 Docker 萎缩到这个地步,其实也只剩 Linux 内核里面 cgroup、namespace 功能的封装了。
总结来说,由于老技术实现的惯性,在生产环境大量使用的经典 Kubernetes+ Docker 方案依然运行,且运维已经成熟,不会很快升级。对于开发人员、企业,对于 K8S API 的使用频率、变数,远远大于 Docker API,至于 Kubernetes 和 Docker 的桥接,更不用关心。因此,即便“彻底弃用 Docker”,对开发者与企业的影响也非常有限。