本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
为什么 Kubernetes 不使用 libnetwork
Kubernetes 早在 1.0 版本发布之前就有了非常基本的网络插件形式——大约在 Docker 的 libnetwork 和容器网络模型 (CNM) 引入的同时。与 libnetwork 不同,Kubernetes 插件系统仍然保留其“alpha”称号。既然 Docker 的网络插件支持已经发布并得到支持,我们自然会遇到一个明显的问题:为什么 Kubernetes 尚未采用它。毕竟,供应商几乎肯定会为 Docker 编写插件——我们都使用相同的驱动程序会更好,对吗?
在深入讨论之前,重要的是要记住 Kubernetes 是一个支持多种容器运行时的系统,其中 Docker 只是其中之一。配置网络是每个运行时的一个方面,所以当人们问“Kubernetes 会支持 CNM 吗?”时,他们真正的意思是“Kubernetes 会支持 Docker 运行时中的 CNM 驱动程序吗?” 如果我们能在不同运行时之间实现通用的网络支持,那将是极好的,但这并不是一个明确的目标。
事实上,Kubernetes 尚未为 Docker 运行时采用 CNM/libnetwork。实际上,我们一直在研究 CoreOS 提出的替代方案容器网络接口 (CNI) 模型,该模型是 App Container (appc) 规范的一部分。为什么?原因有很多,包括技术性和非技术性。
首先,Docker 网络驱动设计中的一些基本假设给我们带来了问题。
Docker 有“本地”和“全局”驱动的概念。本地驱动(如“bridge”)以机器为中心,不进行跨节点协调。全局驱动(如“overlay”)依赖 libkv(一个键值存储抽象)来协调跨机器。这个键值存储是另一个插件接口,级别很低(只有键和值,没有语义含义)。要在 Kubernetes 集群中运行像 Docker 的 overlay 驱动这样的东西,我们需要集群管理员运行一个完全不同的 consul、etcd 或 zookeeper 实例(参见 多主机网络),或者我们必须提供自己的由 Kubernetes 支持的 libkv 实现。
后者听起来很有吸引力,我们也尝试过实现它,但是 libkv 接口非常底层,而且其模式在 Docker 内部定义。我们必须要么直接暴露底层的键值存储,要么提供键值语义(在我们的结构化 API 之上,而结构化 API 本身又是基于键值系统实现的)。由于性能、可伸缩性和安全原因,这两种选择都不是很吸引人。最终结果是,整个系统将变得更加复杂,而使用 Docker 网络的目的是简化事情。
对于那些愿意并能够运行满足 Docker 全局驱动所需的基础设施并自行配置 Docker 的用户来说,Docker 网络应该“开箱即用”。Kubernetes 不会阻碍这种设置,无论项目走向何方,这种选项都应该可用。然而,对于默认安装而言,实际结论是这对用户来说是不必要的负担,因此我们无法使用 Docker 的全局驱动(包括“overlay”),这大大降低了使用 Docker 插件的价值。
Docker 的网络模型做了很多不适用于 Kubernetes 的假设。在 Docker 1.8 和 1.9 版本中,它包含了一个有根本性缺陷的“发现”实现,导致容器中的 /etc/hosts
文件损坏(docker #17190)——而且这不容易关闭。在 1.10 版本中,Docker 计划捆绑一个新的 DNS 服务器,目前尚不清楚是否可以将其关闭。容器级别的命名不是 Kubernetes 的正确抽象——我们已经有了自己的服务命名、发现和绑定概念,并且我们已经有了自己的 DNS 模式和服务器(基于成熟的 SkyDNS)。捆绑的解决方案不足以满足我们的需求,而且无法禁用。
除了本地/全局分离之外,Docker 还有进程内和进程外(“远程”)插件。我们研究了是否可以绕过 libnetwork(从而跳过上述问题)并直接驱动 Docker 远程插件。不幸的是,这意味着我们无法使用任何 Docker 进程内插件,特别是“bridge”和“overlay”,这再次消除了 libnetwork 的大部分实用性。
另一方面,CNI 在哲学上与 Kubernetes 更为契合。它比 CNM 简单得多,不需要守护进程,并且至少在理论上是跨平台的(CoreOS 的 rkt 容器运行时支持它)。跨平台意味着有机会实现跨运行时(例如 Docker、Rocket、Hyper)以相同方式工作的网络配置。它遵循了 UNIX 的“做好一件事”的哲学。
此外,封装 CNI 插件并生成更定制化的 CNI 插件非常简单——只需一个简单的 shell 脚本即可完成。CNM 在这方面要复杂得多。这使得 CNI 成为快速开发和迭代的诱人选择。早期的原型已经证明,可以将 kubelet 中目前硬编码的网络逻辑几乎 100% 地提取到插件中。
我们研究了为 Docker 编写一个运行 CNI 驱动的“bridge”CNM 驱动。结果证明这非常复杂。首先,CNM 和 CNI 模型截然不同,所以没有任何“方法”可以对齐。我们仍然面临上述的全局与本地以及键值问题。假设这个驱动程序声明自己是本地的,我们必须从 Kubernetes 获取逻辑网络信息。
不幸的是,Docker 驱动程序很难映射到像 Kubernetes 这样的其他控制平面。具体来说,驱动程序不知道容器要连接的网络名称——只有一个 Docker 内部分配的 ID。这使得驱动程序很难映射回存在于其他系统中的任何网络概念。
网络供应商已向 Docker 开发人员提出了这个问题以及其他问题,但这些问题通常以“按预期工作”为由关闭(libnetwork #139、libnetwork #486、libnetwork #514、libnetwork #865、docker #18864),尽管它们使得非 Docker 第三方系统更难集成。在整个调查过程中,Docker 明确表示他们对偏离当前路线或下放控制权的想法不甚开放。这让我们非常担忧,因为 Kubernetes 补充了 Docker 并增加了许多功能,但它存在于 Docker 本身之外。
出于所有这些原因,我们选择将 CNI 作为 Kubernetes 的插件模型进行投入。这会带来一些不幸的副作用。其中大部分相对较小(例如,docker inspect
将不会显示 IP 地址),但有些则非常重要。特别是,由 docker run
启动的容器可能无法与由 Kubernetes 启动的容器通信,并且网络集成商如果希望与 Kubernetes 完全集成,则必须提供 CNI 驱动程序。另一方面,Kubernetes 将变得更简单、更灵活,并且早期引导(例如配置 Docker 使用我们的桥接)的许多丑陋之处都将消失。
在我们沿着这条道路前进时,我们一定会保持开放的心态,寻找更好的集成和简化方式。如果您对我们如何做到这一点有任何想法,我们非常乐意倾听——请在 slack 或我们的 网络 SIG 邮件列表上找到我们。