Loading

[03] Docker 往事

《深入剖析 Kubernetes》张磊

“容器”这个概念从来就不是什么新鲜的东西,也不是 Docker 公司发明的。即使在当时最热门的 PaaS 项目 Cloud Foundry 中,容器也只是其最底层、最没人关注的那一部分。说到这里,我正好以当时的事实标准 Cloud Foundry 为例,来解说一下 PaaS 技术。

PaaS 项目被大家接纳的一个主要原因,就是它提供了一种名叫“应用托管”的能力。 在当时,虚拟机和云计算已经是比较普遍的技术和服务了,那时主流用户的普遍用法,就是租一批 AWS 或者 OpenStack 的虚拟机,然后像以前管理物理服务器那样,用脚本或者手工的方式在这些机器上部署应用。

当然,这个部署过程难免会碰到云端虚拟机和本地环境不一致的问题,所以当时的云计算服务,比的就是谁能更好地模拟本地服务器环境,能带来更好的“上云”体验。而 PaaS 开源项目的出现,就是当时解决这个问题的一个最佳方案。

举个例子,虚拟机创建好之后,运维人员只需要在这些机器上部署一个 Cloud Foundry 项目,然后开发者只要执行一条命令就能把本地的应用部署到云上,这条命令就是:

$ cf push " 我的应用"

是不是很神奇?

事实上,像 Cloud Foundry 这样的 PaaS 项目,最核心的组件就是一套应用的打包和分发机制。 Cloud Foundry 为每种主流编程语言都定义了一种打包格式,而“cf push”的作用,基本上等同于用户把应用的可执行文件和启动脚本打进一个压缩包内,上传到云上 Cloud Foundry 的存储中。接着,Cloud Foundry 会通过调度器选择一个可以运行这个应用的虚拟机,然后通知这个机器上的 Agent 把应用压缩包下载下来启动。

这时候关键来了,由于需要在一个虚拟机上启动很多个来自不同用户的应用,Cloud Foundry 会调用操作系统的 Cgroups 和 Namespace 机制为每一个应用单独创建一个称作“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程。这样,就实现了把多个用户的应用互不干涉地在虚拟机里批量地、自动地运行起来的目的。

这,正是 PaaS 项目最核心的能力。 而这些 Cloud Foundry 用来运行应用的隔离环境,或者说“沙盒”,就是所谓的“容器”。

而 Docker 项目,实际上跟 Cloud Foundry 的容器并没有太大不同,所以在它发布后不久,Cloud Foundry 的首席产品经理 James Bayer 就在社区里做了一次详细对比,告诉用户 Docker 实际上只是一个同样使用 Cgroups 和 Namespace 实现的“沙盒”而已,没有什么特别的黑科技,也不需要特别关注。

然而,短短几个月,Docker 项目就迅速崛起了。它的崛起速度如此之快,以至于 Cloud Foundry 以及所有的 PaaS 社区还没来得及成为它的竞争对手,就直接被宣告出局了。那时候,一位多年的 PaaS 从业者曾经如此感慨道:这简直就是一场“降维打击”啊。

难道这一次,连闯荡多年的“老江湖”James Bayer 也看走眼了么?

并没有。

事实上,Docker 项目确实与 Cloud Foundry 的容器在大部分功能和实现原理上都是一样的,可偏偏就是这剩下的一小部分不一样的功能,成了 Docker 项目接下来“呼风唤雨”的不二法宝。

这个功能,就是 Docker 镜像。

恐怕连 Docker 项目的作者 Solomon Hykes 自己当时都没想到,这个小小的创新,在短短几年内就如此迅速地改变了整个云计算领域的发展历程。

我前面已经介绍过,PaaS 之所以能够帮助用户大规模部署应用到集群里,是因为它提供了一套应用打包的功能。可偏偏就是这个打包功能,却成了 PaaS 日后不断遭到用户诟病的一个“软肋”。

出现这个问题的根本原因是,一旦用上了 PaaS,用户就必须为每种语言、每种框架,甚至每个版本的应用维护一个打好的包。这个打包过程,没有任何章法可循,更麻烦的是,明明在本地运行得好好的应用,却需要做很多修改和配置工作才能在 PaaS 里运行起来。而这些修改和配置,并没有什么经验可以借鉴,基本上得靠不断试错,直到你摸清楚了本地应用和远端 PaaS 匹配的“脾气”才能够搞定。

最后结局就是,“cf push”确实是能一键部署了,但是为了实现这个一键部署,用户为每个应用打包的工作可谓一波三折,费尽心机。

Docker 镜像解决的,恰恰就是打包这个根本性的问题。 所谓 Docker 镜像,其实就是一个压缩包。但是这个压缩包里的内容,比 PaaS 的应用可执行文件 + 启停脚本的组合就要丰富多了。实际上,大多数 Docker 镜像是直接由一个完整操作系统的所有文件和目录构成的,所以这个压缩包里的内容跟你本地开发和测试环境用的操作系统是完全一样的。

这就有意思了:假设你的应用在本地运行时,能看见的环境是 CentOS 7.2 操作系统的所有文件和目录,那么只要用 CentOS 7.2 的 ISO 做一个压缩包,再把你的应用可执行文件也压缩进去,那么无论在哪里解压这个压缩包,都可以得到与你本地测试时一样的环境。当然,你的应用也在里面!

这就是 Docker 镜像最厉害的地方:只要有这个压缩包在手,你就可以使用某种技术创建一个“沙盒”,在“沙盒”中解压这个压缩包,然后就可以运行你的程序了。

更重要的是,这个压缩包包含了完整的操作系统文件和目录,也就是包含了这个应用运行所需要的所有依赖,所以你可以先用这个压缩包在本地进行开发和测试,完成之后,再把这个压缩包上传到云端运行。

在这个过程中,你完全不需要进行任何配置或者修改,因为这个压缩包赋予了你一种极其宝贵的能力:本地环境和云端环境的高度一致!

这,正是 Docker 镜像的精髓。

那么,有了 Docker 镜像这个利器,PaaS 里最核心的打包系统一下子就没了用武之地,最让用户抓狂的打包过程也随之消失了。相比之下,在当今的互联网里,Docker 镜像需要的操作系统文件和目录,可谓唾手可得。

所以,你只需要提供一个下载好的操作系统文件与目录,然后使用它制作一个压缩包即可,这个命令就是:

$ docker build "我的镜像"

一旦镜像制作完成,用户就可以让 Docker 创建一个“沙盒”来解压这个镜像,然后在“沙盒”中运行自己的应用,这个命令就是:

$ docker run "我的镜像"

当然,docker run 创建的“沙盒”,也是使用 Cgroups 和 Namespace 机制创建出来的隔离环境。我会在后面的文章中,详细介绍这个机制的实现原理。

所以,Docker 项目给 PaaS 世界带来的“降维打击”,其实是提供了一种非常便利的打包机制。这种机制直接打包了应用运行所需要的整个操作系统,从而保证了本地环境和云端环境的高度一致,避免了用户通过“试错”来匹配两种不同运行环境之间差异的痛苦过程。

而对于开发者们来说,在终于体验到了生产力解放所带来的痛快之后,他们自然选择了用脚投票,直接宣告了 PaaS 时代的结束。

不过,Docker 项目固然解决了应用打包的难题,但正如前面所介绍的那样,它并不能代替 PaaS 完成大规模部署应用的职责。

遗憾的是,考虑到 Docker 公司是一个与自己有潜在竞争关系的商业实体,再加上对 Docker 项目普及程度的错误判断,Cloud Foundry 项目并没有第一时间使用 Docker 作为自己的核心依赖,去替换自己那套饱受诟病的打包流程。

反倒是一些机敏的创业公司,纷纷在第一时间推出了 Docker 容器集群管理的开源项目(比如 Deis 和 Flynn),它们一般称自己为 CaaS,即 Container-as-a-Service,用来跟“过时”的 PaaS 们划清界限。

而在 2014 年底的 DockerCon 上,Docker 公司雄心勃勃地对外发布了自家研发的“Docker 原生”容器集群管理项目 Swarm,不仅将这波“CaaS”热推向了一个前所未有的高潮,更是寄托了整个 Docker 公司重新定义 PaaS 的宏伟愿望。

那么,Docker 公司为什么一定要发布 Swarm 项目呢?

通过我对 Docker 项目崛起背后原因的分析,你应该能发现这样一个有意思的事实:虽然通过“容器”这个概念完成了对经典 PaaS 项目的“降维打击”,但是 Docker 项目和 Docker 公司,兜兜转转了一年多,却还是回到了 PaaS 项目原本深耕了多年的那个战场:如何让开发者把应用部署在我的项目上。

没错,Docker 项目从发布之初就全面发力,从技术、社区、商业、市场全方位争取到的开发者群体,实际上是为此后吸引整个生态到自家“PaaS”上的一个铺垫。只不过这时,“PaaS”的定义已经全然不是 Cloud Foundry 描述的那个样子,而是变成了一套以 Docker 容器为技术核心,以 Docker 镜像为打包标准的、全新的“容器化”思路。

这,正是 Docker 项目从一开始悉心运作“容器化”理念和经营整个 Docker 生态的主要目的。

而 Swarm 项目,正是接下来承接 Docker 公司所有这些努力的关键所在。

Swarm 的最大亮点,则是它完全使用 Docker 项目原本的容器管理 API 来完成集群管理,比如:

  • 单机 Docker 项目:docker run "我的容器"
  • 多机 Docker 项目:docker run -H "我的Swarm集群API地址" "我的容器"

所以在部署了 Swarm 的多机环境下,用户只需要使用原先的 Docker 指令创建一个容器,这个请求就会被 Swarm 拦截下来处理,然后通过具体的调度算法找到一个合适的 Docker Daemon 运行起来。

这个操作方式简洁明了,对于已经了解过 Docker 命令行的开发者们也很容易掌握。所以,这样一个“原生”的 Docker 容器集群管理项目一经发布,就受到了已有 Docker 用户群的热捧。当然,Swarm 项目只是 Docker 公司重新定义“PaaS”的关键一环而已。在 2014 年到 2015 年这段时间里,Docker 项目的迅速走红催生出了一个非常繁荣的“Docker 生态”。在这个生态里,围绕着 Docker 在各个层次进行集成和创新的项目层出不穷。

而此时已经大红大紫到“不差钱”的Docker 公司,开始及时地借助这波浪潮通过并购来完善自己的平台层能力。其中一个最成功的案例,莫过于对 Fig 项目的收购。要知道,Fig 项目基本上只是靠两个人全职开发和维护的,可它却是当时 GitHub 上热度堪比 Docker 项目的明星。

而 Fig 项目之所以受欢迎,在于它在开发者面前第一次提出了“容器编排”(Container Orchestration)的概念。

其实,“编排”(Orchestration)在云计算行业里不算是新词汇,它主要是指用户如何通过某些工具或者配置来完成一组虚拟机以及关联资源的定义、配置、创建、删除等工作,然后由云计算平台按照这些指定的逻辑来完成的过程。

而容器时代,“编排”显然就是对 Docker 容器的一系列定义、配置和创建动作的管理。而 Fig 的工作实际上非常简单:假如现在用户需要部署的是应用容器 A、数据库容器 B、负载均衡容器 C,那么 Fig 就允许用户把 A、B、C 三个容器定义在一个配置文件中,并且可以指定它们之间的关联关系,比如容器 A 需要访问数据库容器 B。

接下来,你只需要执行一条非常简单的指令:fig up

Fig 就会把这些容器的定义和配置交给 Docker API 按照访问逻辑依次创建,你的一系列容器就都启动了;而容器 A 与 B 之间的关联关系,也会交给 Docker 的 Link 功能通过写入 hosts 文件的方式进行配置。更重要的是,你还可以在 Fig 的配置文件里定义各种容器的副本个数等编排参数,再加上 Swarm 的集群管理能力,一个活脱脱的 PaaS 呼之欲出。

Fig 项目被收购后改名为 Compose,它成了 Docker 公司到目前为止第二大受欢迎的项目,一直到今天也依然被很多人使用。

当时的这个容器生态里,还有很多令人眼前一亮的开源项目或公司。比如,专门负责处理容器网络的 SocketPlane 项目(后来被 Docker 公司收购),专门负责处理容器存储的 Flocker 项目(后来被 EMC 公司收购),专门给 Docker 集群做图形化管理界面和对外提供云服务的 Tutum 项目(后来被 Docker 公司收购)等等。一时之间,整个后端和云计算领域的聪明才俊都汇集在了这个“小鲸鱼”的周围,为 Docker 生态的蓬勃发展献上了自己的智慧。

所以后来,Google、RedHat 等开源基础设施领域玩家们,共同牵头发起了一个名为 CNCF(Cloud Native Computing Foundation)的基金会。这个基金会的目的其实很容易理解:它希望,以 Kubernetes 项目为基础,建立一个由开源基础设施领域厂商主导的、按照独立基金会方式运营的平台级社区,来对抗以 Docker 公司为核心的容器商业生态。

而为了打造出这样一个围绕 Kubernetes 项目的“护城河”,CNCF 社区就需要至少确保两件事情:

  1. Kubernetes 项目必须能够在容器编排领域取得足够大的竞争优势;
  2. CNCF 社区必须以 Kubernetes 项目为核心,覆盖足够多的场景。

我们先来看看 CNCF 社区如何解决 Kubernetes 项目在编排领域的竞争力的问题。

在容器编排领域,Kubernetes 项目需要面对来自 Docker 公司和 Mesos 社区两个方向的压力。不难看出,Swarm 和 Mesos 实际上分别从两个不同的方向讲出了自己最擅长的故事:Swarm 擅长的是跟 Docker 生态的无缝集成,而 Mesos 擅长的则是大规模集群的调度与管理。

这两个方向,也是大多数人做容器集群管理项目时最容易想到的两个出发点。也正因为如此,Kubernetes 项目如果继续在这两个方向上做文章恐怕就不太明智了。

所以这一次,Kubernetes 选择的应对方式是:Borg。

如果你看过 Kubernetes 项目早期的 GitHub Issue 和 Feature 的话,就会发现它们大多来自于 Borg 和 Omega 系统的内部特性,这些特性落到 Kubernetes 项目上,就是 Pod、Sidecar 等功能和设计模式。

这就解释了,为什么 Kubernetes 发布后,很多人“抱怨”其设计思想过于“超前”的原因:Kubernetes 项目的基础特性,并不是几个工程师突然“拍脑袋”想出来的东西,而是 Google 公司在容器化基础设施领域多年来实践经验的沉淀与升华。这,正是 Kubernetes 项目能够从一开始就避免同 Swarm 和 Mesos 社区同质化的重要手段。

于是,CNCF 接下来的任务就是,如何把这些先进的思想通过技术手段在开源社区落地,并培育出一个认同这些理念的生态?这时,RedHat 就发挥了重要作用。

当时,Kubernetes 团队规模很小,能够投入的工程能力也十分紧张,而这恰恰是 RedHat 的长处。更难得的是,RedHat 是世界上为数不多的、能真正理解开源社区运作和项目研发真谛的合作伙伴。

所以,RedHat 与 Google 联盟的成立,不仅保证了 RedHat 在 Kubernetes 项目上的影响力,也正式开启了容器编排领域“三国鼎立”的局面。

这时,我们再重新审视容器生态的格局,就不难发现 Kubernetes 项目、Docker 公司和 Mesos 社区这三大玩家的关系已经发生了微妙的变化。

其中,Mesos 社区与容器技术的关系,更像是“借势”,而不是这个领域真正的参与者和领导者。这个事实,加上它所属的 Apache 社区固有的封闭性,导致了 Mesos 社区虽然技术最为成熟,却在容器编排领域鲜有创新。

这也是为何,Google 公司很快就把注意力转向了动作更加激进的 Docker 公司。

有意思的是,Docker 公司对 Mesos 社区也是类似的看法。所以从一开始,Docker 公司就把应对 Kubernetes 项目的竞争摆在了首要位置:一方面,不断强调“Docker Native”的“重要性”,另一方面,与 Kubernetes 项目在多个场合进行了直接的碰撞。

不过,这次竞争的发展态势,很快就超过了 Docker 公司的预期。

Kubernetes 项目并没有跟 Swarm 项目展开同质化的竞争,所以“Docker Native”的说辞并没有太大的杀伤力。相反地,Kubernetes 项目让人耳目一新的设计理念和号召力,很快就构建出了一个与众不同的容器编排与管理的生态。

就这样,Kubernetes 项目在 GitHub 上的各项指标开始一骑绝尘,将 Swarm 项目远远地甩在了身后。

有了这个基础,CNCF 社区就可以放心地解决第二个问题了。

在已经囊括了容器监控事实标准的 Prometheus 项目之后,CNCF 社区迅速在成员项目中添加了 Fluentd、OpenTracing、CNI 等一系列容器生态的知名工具和项目。

而在看到了 CNCF 社区对用户表现出来的巨大吸引力之后,大量的公司和创业团队也开始专门针对 CNCF 社区而非 Docker 公司制定推广策略。

面对这样的竞争态势,Docker 公司决定更进一步。在 2016 年,Docker 公司宣布了一个震惊所有人的计划:放弃现有的 Swarm 项目,将容器编排和集群管理功能全部内置到 Docker 项目当中。

显然,Docker 公司意识到了 Swarm 项目目前唯一的竞争优势,就是跟 Docker 项目的无缝集成。那么,如何让这种优势最大化呢?那就是把 Swarm 内置到 Docker 项目当中。

实际上,从工程角度来看,这种做法的风险很大。内置容器编排、集群管理和负载均衡能力,固然可以使得 Docker 项目的边界直接扩大到一个完整的 PaaS 项目的范畴,但这种变更带来的技术复杂度和维护难度,长远来看对 Docker 项目是不利的。

不过,在当时的大环境下,Docker 公司的选择恐怕也带有一丝孤注一掷的意味。

Kubernetes 的应对策略则是反其道而行之,开始在整个社区推进“民主化”架构,即:从 API 到容器运行时的每一层,Kubernetes 项目都为开发者暴露出了可以扩展的插件机制,鼓励用户通过代码的方式介入到 Kubernetes 项目的每一个阶段。

Kubernetes 项目的这个变革的效果立竿见影,很快在整个容器社区中催生出了大量的、基于 Kubernetes API 和扩展接口的二次创新工作,比如:

  • 目前热度极高的微服务治理项目 Istio;
  • 被广泛采用的有状态应用部署框架 Operator;
  • 还有像 Rook 这样的开源创业项目,它通过 Kubernetes 的可扩展接口,把 Ceph 这样的重量级产品封装成了简单易用的容器存储插件。

就这样,在这种鼓励二次创新的整体氛围当中,Kubernetes 社区在 2016 年之后得到了空前的发展。更重要的是,不同于之前局限于“打包、发布”这样的 PaaS 化路线,这一次容器社区的繁荣,是一次完全以 Kubernetes 项目为核心的“百家争鸣”

面对 Kubernetes 社区的崛起和壮大,Docker 公司也不得不面对自己豪赌失败的现实。但在早前拒绝了微软的天价收购之后,Docker 公司实际上已经没有什么回旋余地,只能选择逐步放弃开源社区而专注于自己的商业化转型。

所以,从 2017 年开始,Docker 公司先是将 Docker 项目的容器运行时部分 Containerd 捐赠给 CNCF 社区,标志着 Docker 项目已经全面升级成为一个 PaaS 平台;紧接着,Docker 公司宣布将 Docker 项目改名为 Moby,然后交给社区自行维护,而 Docker 公司的商业产品将占有 Docker 这个注册商标。

Docker 公司这些举措背后的含义非常明确:它将全面放弃在开源社区同 Kubernetes 生态的竞争,转而专注于自己的商业业务,并且通过将 Docker 项目改名为 Moby 的举动,将原本属于 Docker 社区的用户转化成了自己的客户。

2017 年 10 月,Docker 公司出人意料地宣布,将在自己的主打产品 Docker 企业版中内置 Kubernetes 项目,这标志着持续了近两年之久的“编排之争”至此落下帷幕。

posted @ 2021-09-21 11:45  tree6x7  阅读(31)  评论(0编辑  收藏  举报