以 Docker 为代表的容器引擎,是把软件的发布流程从分发二进制安装包,转变为了直接分发虚拟化后的整个运行环境,让应用得以实现跨机器的绿色部署;
以Kubernetes 为代表的容器编排框架,就是把大型软件系统运行所依赖的集群环境也进行了虚拟化,让集群得以实现跨数据中心的绿色部署,并能够根据实际情况自动扩缩。
今天主要介绍k8s诞生与演进。
Kubernetes的前身是 Google 内部已经运行多年的集群管理系统 Borg,在 2014 年 6 月使用 Golang 完全重写后开源。与“云计算概念”相关的巨头IBM、RedHat、Microsoft、VMware 和华为都是它最早期的代码贡献者。虽然此时距离云计算从实验室到工业化应用已经有十个年头,不过大量应用使用云计算的方式,还是停滞在了传统的 IDC(Internet Data Center)时代,它们仅仅是用云端的虚拟机代替了传统的物理机而已。(此时的云计算听上去更像是云盘和“肉机”结合)
2015 年 7 月,Kubernetes 发布了第一个正式版本 1.0 版,Google宣布与 Linux 基金会共同筹建云原生基金会(Cloud Native Computing Foundation,CNCF),并且把 Kubernetes 托管到 CNCF,成为其第一个项目。随后,Kubernetes 就消灭了容器编排领域的其他竞争对手,哪怕 Docker Swarm 有着 Docker 在容器引擎方面的先天优势。
与Docker取得容器领域的成功不同,Kubernetes 的成功,不仅有 Google 深厚的技术功底作支撑、有领先时代的设计理念,更加关键的是 Kubernetes 的出现,符合所有云计算大厂的切身利益,有着业界巨头不遗余力地广泛支持,所以它的成功便是一种必然。
在 Kubernetes 开源的早期,它是完全依赖且绑定 Docker 的,并没有过多地考虑日后有使用其他容器引擎的可能性。直到 Kubernetes 1.5 之前,Kubernetes 管理容器的方式都是通过内部的 DockerManager,向 Docker Engine 以 HTTP 方式发送指令,通过 Docker 来操作镜像的增删改查的,如上图最右边线路的箭头所示(图中的 kubelet 是集群节点中的代理程序,负责与管理集群的 Master 通信)。
这个阶段的 Kubernetes 与容器引擎的调用关系为:
Kubernetes Master → kubelet → DockerManager → Docker Engine → containerd → runC
2016年,Kubernetes 1.5 版本开始引入“容器运行时接口”(Container Runtime Interface,CRI),这是一个定义容器运行时应该如何接入到 kubelet 的规范标准,从此 Kubernetes 内部的 DockerManager,就被更为通用的 KubeGenericRuntimeManager 所替代了(实际上在 1.6.6 之前都仍然可以看到 DockerManager),kubelet 与 KubeGenericRuntimeManager 之间通过 gRPC 协议通信。
不过,由于 CRI 是在 Docker 之后才发布的规范,Docker 是肯定不支持 CRI 的,所以 Kubernetes 又提供了 DockerShim 服务作为 Docker 与 CRI 的适配层,由它与 Docker Engine 以 HTTP 形式通信,从而实现了原来 DockerManager 的全部功能。(这太令人绝望了)
此时,Docker 对 Kubernetes 来说就只是一项默认依赖,而非之前的不可或缺了,现在它们的调用链为:
Kubernetes Master → kubelet → KubeGenericRuntimeManager → DockerShim → Docker Engine → containerd → runC
2017 年,由 Google、RedHat、Intel、SUSE、IBM 联合发起的CRI-O(Container Runtime Interface Orchestrator)项目发布了首个正式版本。毋庸置疑,它是完全遵循 CRI 规范来实现的;另一方面,它可以支持所有符合 OCI 运行时标准的容器引擎,默认仍然是与 runC 搭配工作的,如果要换成Clear Containers、Kata Containers等其他 OCI 运行时,也完全没有问题。(绕过巨头反而接入标准)
开源版的 Kubernetes 虽然完全支持用户去自由选择(根据用户宿主机的环境选择)是使用 CRI-O、cri-containerd,还是 DockerShim 来作为 CRI 实现,但在 RedHat 自己扩展定制的 Kubernetes 企业版,即OpenShift 4中,调用链已经没有了 Docker Engine 的身影:
Kubernetes Master → kubelet → KubeGenericRuntimeManager → CRI-O→ runC
此时 Docker 在容器引擎中的市场份额仍然占有绝对优势,对于普通用户来说,如果没有明确的收益,也并没有什么动力要把 Docker 换成别的引擎。所以 CRI-O 即使摆出了直接挖掉 Docker 根基的凶悍姿势,实际上也并没有给 Docker 带来太多即时可见的影响。不过,我们能够想像此时 Docker 心中肯定充斥了难以言喻的危机感。(这就是大厂的打压么,虽然你牛,但我可以搞一个高于你的标准,通过培养你的竞争对手,从而把你逐渐边缘。。。开源是不是导致现在的大厂可以活得更好,因为完全可以靠人力弥补产品创新力的停滞)
2018 年,由 Docker 捐献给 CNCF 的 containerd,在 CNCF 的精心孵化下发布了 1.1 版,1.1 版与 1.0 版的最大区别是此时它已经完美地支持了 CRI 标准,这意味着原本用作 CRI 适配器的 cri-containerd 从此不再被需要。(行业扛把子对产业巨头的妥协)
我们再观察 Kubernetes 到容器运行时的调用链,就会发现调用步骤会比通过 DockerShim、Docker Engine 与 containerd 交互的步骤要减少两步,这又意味着用户只要愿意抛弃掉 Docker 情怀的话,在容器编排上就可以至少省略一次 HTTP 调用,获得性能上的收益。Kubernetes 从 1.10 版本宣布开始支持 containerd 1.1,在调用链中就已经能够完全抹去 Docker Engine 的存在了:
Kubernetes Master → kubelet → KubeGenericRuntimeManager → containerd → runC
感觉k8s的崛起,就有一种“我是先进,你要对我的改造表示积极接纳的感恩”的既视感。
此文章为3月Day07学习笔记,内容来源于极客时间《周志明的软件架构课》