本文超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已失效。

为什么 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 提出并作为 App Container(appc)规范一部分的替代容器网络接口(CNI)模型。为什么?原因有很多,既有技术上的也有非技术上的。

首先,Docker 网络驱动程序的设计中存在一些基本假设,这些假设给我们带来了问题。

Docker 有“本地”和“全局”驱动程序的概念。本地驱动程序(例如“bridge”)以机器为中心,不进行任何跨节点协调。全局驱动程序(例如“overlay”)依赖于 libkv(一个键值存储抽象)来跨机器协调。这个键值存储是另一个插件接口,并且非常底层(键和值,没有语义)。要在 Kubernetes 集群中运行 Docker 的 overlay 驱动程序之类的东西,我们需要集群管理员运行一个完全不同的 consuletcdzookeeper 实例(请参阅多主机网络),或者我们必须提供我们自己的由 Kubernetes 支持的 libkv 实现。

后者听起来很有吸引力,我们也尝试过实现它,但 libkv 接口非常底层,而且模式是在 Docker 内部定义的。我们必须直接公开我们底层的键值存储,或者提供键值语义(在我们基于键值系统的结构化 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 #139libnetwork #486libnetwork #514libnetwork #865docker #18864),即使它们使非 Docker 第三方系统更难以集成。在整个调查过程中,Docker 明确表示他们对偏离当前路线或委托控制的想法不太开放。这对我们来说非常令人担忧,因为 Kubernetes 补充了 Docker 并添加了如此多的功能,但却存在于 Docker 本身之外。

由于所有这些原因,我们选择投资 CNI 作为 Kubernetes 插件模型。这将会产生一些不幸的副作用。其中大部分都相对较小(例如,docker inspect 不会显示 IP 地址),但有些则很重要。特别是,由 docker run 启动的容器可能无法与 Kubernetes 启动的容器通信,如果网络集成商希望与 Kubernetes 完全集成,则必须提供 CNI 驱动程序。另一方面,Kubernetes 将变得更简单、更灵活,并且许多早期引导的丑陋之处(例如将 Docker 配置为使用我们的网桥)都将消失。

在我们沿着这条道路前进的过程中,我们一定会保持开放的态度,寻找更好的集成和简化方法。如果您对我们如何做到这一点有任何想法,我们真的希望听到您的意见——在 slack 或我们的 网络 SIG 邮件列表 上找到我们。