Linkerd 创始人:eBPF 将影响服务网格的未来

推荐序:eBPF (Extended Berkeley Packet Filter) 提供了非常灵活且强大的机制,能够从内核层面无差别地做统一控制和监控,给云原生和现代化应用带来了一些全新的解决方案和巨大的技术红利,包括可观的性能提升、CPU 开销降低。「DaoCloud 道客」作为云原生领域的创新领导者,非常重视 eBPF 技术给 Linux 社区、kubernetes 社区带来的技术革命。

「DaoCloud 道客」在 2021 年 11 月加入了 eBPF 基金会,是第一家正式获准加入的中国公司。并且,基于 eBPF 技术开源了 Merbridge 项目,旨在使用 eBPF 提供的一系列优秀的特性,来帮助服务网格提升效率。Merbridge 从设计之初,就支持了 Linkerd、 Istio、Kuma 等主流网格实现,未来,还可以支持更多网格实现。

正如 Merbridge 所努力的方向, eBPF 将影响服务网格的未来,Linkerd 创始人也是这样认为的,并对此进行了深入全面的分析。接下来,听听他怎么说。

01 前言

eBPF 是一种相当酷的技术,为云原生世界提供了很多帮助。它已经成为 Kubernetes 集群里 CNI 层的主流选择,譬如说 Cilium 这样的项目。Linkerd 这样的服务网格部署了和 Cilium 一样的 CNI 层,结合了 Linkerd 强大的 L7 处理和 Cilium 超快的 L3/4 处理。

但 eBPF 的网络技术到底有多强大呢?比如,它是否允许我们完全替换 Linkerd 的 sidecar 代理,并且将所有的事务都在内核中运行?

接下来,我们会尽量来评估这种可能性,特别是它对用户的影响。我将描述eBPF是什么以及它能做什么和不能做什么。我将阐述 sidecar 与其他模型的深入研究,并从运维和安全角度对它们进行对比。最后,我将阐述我的结论——我们 Linkerd 团队认为服务网格的未来与 eBPF 相关。

02 自我介绍

我是威廉·摩根 (William Morgan),Linkerd 创始人之一,Linkerd 是第一个服务网格,也是它定义了服务网格的各个术语。我同时还是 Buoyant 公司的首席执行官,该公司帮助世界各地的组织采用 Linkerd。没准大家可能还记得我曾经发表的又长又枯燥的技术文章:《Service Mesh:每个软件工程师都需要知道的这个被全世界高估的技术[1]》和《Kubernetes 工程师关于 mTLS 的指南:相互认证所带给你的愉悦和效益[2]》。

我一直在强调 Linkerd,也许是我的偏见。但我也很高兴以务实的态度从实施的层面看待它。Linkerd 的最终目标是为我们的用户提供最简单的服务网格,Linkerd 如何实现这种简化,其实是一个实施细节。例如,今天 Linkerd 使用 sidecar,但更早的 Linkerd 的 1.x 版本是作为基于每个主机的代理部署的,这是出于操作性和安全的考虑才做出这样的变化的。eBPF 被我们关注的是,它有可能让我们进一步简化 Linkerd,尤其是在操作性层面。

03 eBPF 是什么?

在我们进入服务网格细节之前,让我们先从 eBPF 开始。这个席卷推特圈的热门新技术到底是什么?

eBPF 是 Linux 内核的一个设计,它允许应用程序在内核本身中执行某些类型的工作。eBPF 起源于网络世界,但它不限于网络,这正是它的亮点:在其他事务中,eBPF 解锁了整个网络的可观察性,这在过去是不可能的,因为它们会对性能产生影响。

假设你希望应用程序处理网络数据包。你不能直接访问宿主机的网络缓冲区。这个缓冲区由内核管理,因为内核必须保护它。例如,它必须确保一个进程不能读取另一个进程的网络数据包。相反,应用程序可以通过一个系统调用 (syscall) 来请求网络数据包信息,这本质上是一个内核的 API 调用:你的应用程序调用 syscall,内核会检查你是否有权限获得你请求的数据包;如果有,就返还给你。

Syscall 是可移植的,你的代码可以在非 Linux 机器上运行,但这会很慢。在现代网络环境中,你的机器可能每秒处理数千万个数据包,编写基于 syscall 的代码来处理每个数据包是不可能的。

来说说 eBPF。代码不是在一个紧密的循环中调用 syscall,并在 “内核空间” 和 “用户空间” 之间来回传递,而是直接将我们的代码交给内核,让它自己执行!瞧:现在没有更多的 syscall了,我们的应用程序应该可以全速运行了。(当然,正如下面将看到的,事情并没有这么简单。)

eBPF 是最近一系列内核设计中的一个,比如 io_uring (Linkerd 在重度使用中),它改变了应用程序和内核交互的方式。(ScyllaDB 的 Glauber Costa 对此有一篇很棒的文章:《io_uring 和 eBPF 将如何彻底改变 Linux 编程[3]》。) 这些设计以截然不同的方式工作着:io_uring 使用一个特定的数据结构,允许应用程序和内核以一种安全的方式共享内存;eBPF 的工作原理是允许应用程序直接向内核提交代码。但在这两种情况下,目标都是通过 “越过 syscall” 的方法来提高性能。

eBPF 虽然是一个巨大的进步,但并不是能解决一切问题的灵丹妙药。不可能将任意一个应用程序都作为 eBPF 运行。事实上,能使用 eBPF 做的事情也是非常有限的,请看下文。

04 多租户争用是很困难的

在我们理解 eBPF 为什么如此有限之前,我们需要讨论一下为什么内核本身如此有限。为什么像syscall这样的东西会存在?为什么程序不能直接访问网络 (或内存或磁盘)?

内核在一个多租户争用的世界中运行。多租户意味着多个 “租户”(例如人、账户、其他形式的参与者) 来共享机器,每个人都运行自己的程序。争用意味着这些租客不是 “朋友”。他们不应该访问彼此的数据,或者相互干扰。内核需要在它们执行任意程序的时候提供强制约束的行为。换句话说,内核需要隔离租户。

这意味着内核不能真正信任任何程序。在任何时候,一个租户的程序都可能试图对另一个租户的数据或程序做一些不好的事情。内核必须确保任何程序都不能停止或破坏另一个程序,或拒绝它的资源,亦或是干扰它的运行能力,或从内存、网络或磁盘读取数据,除非得到明确的许可需要这样做。

这是一个非常关键的需求!世界上几乎所有与软件相关的安全保证最终都归结于——内核在执行着此类保护措施。一个程序可以在未经允许的情况下读取另一个程序的内存或网络流量,这是一个数据泄露的行径,也有可能更糟。一个可以写入另一个程序的内存或网络流量的程序是欺诈的行径,甚至更糟。允许程序打破规则的内核调用是一个非常大的问题。而其中一种打破这些规则的方法就是访问内核的内部状态——如果你可以读写内核内存,那么你就可以绕过这些规则。

这就是为什么应用程序和内核之间的每一次交互,都要受到高度审查的原因。失败的后果是极其严重的。内核开发人员们已经为这个问题,付出了日以继夜的努力。

这也是容器如此强大的原因——它们采用相同的隔离策略,并将其应用于任意一个应用程序和依赖包。多亏了这个先进的内核策略,我们可以彼此隔离地运行容器,并充分利用内核处理多租户争用的能力。以前使用虚拟机实现这种隔离的方法很慢,而且成本很高。容器的神奇之处在于,它们以一种非常便宜的方式为我们提供了 (大部分) 相同的保证。

我们所认为的 “原生云” 几乎每个方面都依赖于这个隔离保证。

05 eBPF 的局限性

回到 eBPF。正如我们所讨论的,eBPF 允许我们交出内核代码,并说 “给,请在内核中运行它”。从内核安全的角度来看,我们知道这是一件令人难以置信的可怕的事情——它将绕过应用程序和内核之间的所有障碍 (如 syscall),并将我们直接置于安全漏洞的区域。

因此,为了确保安全,内核对所执行的代码施加了一些非常重要的约束。在运行它们之前,所有 eBPF 程序必须通过一个验证器,它检查它们是否有不正常的行为。如果验证程序拒绝这个程序,内核就不会运行它。

程序的自动验证是比较难实现的,而且验证器也有可能因为过于严格,导致出现报错。因此,eBPF 的程序非常有限。例如,它们不能有阻拦其他程序的设定;它们不能有无界循环;它们不能超过预设的大小。同时也因为复杂性也会受到限制——验证器需要在所有可能的执行路径上运行,如果它不能在某些限制内完成,或者不能证明每个循环都有退出机制,程序则不能通过。

有许多安全的程序完美地违反了这些限制。如果你想将其中一个程序作为 eBPF 运行,那太糟糕了!你需要重写程序以满足验证器。如果你是 eBPF 的粉丝,有个好消息是,随着每个内核版本中的验证器变得更智能,这些限制将逐渐放宽,同时也开始有一些创造性的方法,可以绕过这些限制。

但总的来说,eBPF 项目所能做的事情非常有限。一些非常重要的事务,例如处理全范围的 HTTP/2 流量,或协商 TLS 握手,这些都不能在 eBPF 中完成。比较好的情况下,eBPF 也只能完成这项工作的一小部分,剩下大部分还是需要通过调用用户空间的应用程序来处理,因为 eBPF 无法处理过于复杂的部分。

06 eBPF 与服务网格对比

了解了 eBPF 的基础知识之后,让我们回到服务网格。

服务网格用来处理现代云原生网络的复杂性。以 Linkerd 为例,启动和终止双方的 TLS;跨连接重试请求;为提高性能,透明地在代理之间从 HTTP/1.x 升级到 HTTP/2;强制基于工作负载标识的访问策略;跨 Kubernetes 集群边界发送流量;还有很多很多。

与大多数服务网格一样,Linkerd 通过在每个应用程序的 Pod 中,插入一个代理来实现这一点,该代理拦截并增加了与 Pod 之间的 TCP 通信。这些代理与应用程序容器一起,在它们自己的容器中运行——“sidecar” 模型。Linkerd 的代理是超轻、超快、基于 Rust 的微代理,但也有其他办法可行。

十年前,在集群上部署数百或数千个代理,并将它们与每个应用程序的每个实例连起来的想法,在操作层面上看简直是一场噩梦。但多亏了 Kubernetes,突然变得非常简单。多亏了 Linkerd 巧妙的工程技术 (如果我可以自夸的话),它也是可管理的:Linkerd 的微代理不需要调优,因为它仅仅消耗极少的系统资源。

在这种情况下,eBPF 已经和服务网格配合很多年了。Kubernetes 给世界的礼物,是一个具有清晰层间边界的可组合平台,eBPF 和服务网格之间的关系正好符合该模型:CNI 负责 L3/L4 流量,而服务网格负责 L7。

服务网格对于平台的所有者来说是非常棒的。它在平台层面提供了 mTLS、请求重试、“黄金指标” 等功能,这意味着他们不再需要依赖应用开发人员来构建这些功能,但代价当然是在各处添加大量代理。

所以回到我们最初的问题:我们能做得更好吗?我们是否可以通过 “eBPF 服务网格” 获得服务网格的功能,而不需要 sidecar?

07 eBPF 服务网格仍然需要代理

现在有了我们对 eBPF 的理解,我们可以跳进这些浑浊的水域,探索可能潜伏在里面的东西。

不幸的是,我们很快就触底了:eBPF 的限制意味着,L7 流量代理仍然需要用户空间网络代理,来完成繁重的工作。换句话说,任何 eBPF 服务网格仍然需要代理。

08 基于每个主机的代理明显比 sidecar 差

因此,我们的 eBPF 服务网格需要代理。但它是否特别需要 sidecar 代理?如果我们使用基于每个主机的代理会怎么样?这会给我们一个没有 sidecar的、eBPF 支持的服务网格吗?

不幸的是,我们在 Linkerd 1.x 中了解了太多,关于为什么这不是一个好主意。同 sidecar 相比,基于主机的代理在操作、维护和安全性方面都更差。

在 sidecar 模型中,应用程序的单个实例的所有流量,都通过它的 sidecar 代理处理。这允许代理作为应用程序的一部分,这是理想的:

  • 代理对资源的消耗是随着应用程序负载的变化而变化。随着对实例的通信增加,sidecar 会消耗更多的资源,就像应用程序一样。如果应用程序占用的流量很小,则 sidecar 不需要消耗很多资源。(Linkerd 的代理在低流量水平下有 2-3MB 的内存占用。)在 Kubernetes 现有的管理资源消耗的机制下,例如资源请求、限制以及 OOM 终止,都在继续工作。
  • 代理失效的爆炸半径仅限于一个 Pod。代理失败与应用程序失败是一样的,并由现有的 Kubernetes 机制处理失败的 Pod。
  • 代理维护,例如代理版本的升级,是通过与应用程序本身相同的机制完成的:滚动更新 Deployments 等。
  • 安全的周界是很明确的 (也是非常小的):它就在 Pod 层;Sidecar 与应用程序实例在相同的安全周界中运行;它是同一个 Pod 的一部分;它得到相同的 IP 地址;它执行策略并将 mTLS 应用于往返于该Pod的流量,它只需要该 Pod 的关键信息。

在基于主机的模型中,这些细节都不复存在。现在,代理不再是单一的应用程序实例,而是为任意一个应用实例的一组有效随机的 Pod 处理通信。同时,这些 Pod 是由 Kubernetes 在某个主机上调度的。代理现在与应用程序完全解耦,这就引入了各种微妙和不那么微妙的问题:

  • 代理资源消耗现在是高度可变的:它取决于 Kubernetes 在任意时间点在哪个主机上安排了什么。这就意味着你不能有效地预测或推断,某个特定代理的资源消耗,那么这就意味着它最终可能会崩溃,服务网格团队也将受到指责。
  • 应用程序现在很容易受到 “噪音邻居” 流量的影响。由于通过主机的所有流量都通过一个代理,因此一个高流量的 Pod 可能会消耗所有的代理资源,而代理必须确保公平性,否则应用程序将面临资源短缺的风险。
  • 代理的爆炸半径很大,而且是不断变化的。代理的故障和升级现在会影响随机应用程序集上的随机 Pod 集群,这意味着任何故障或维护任务都难以预测其影响。
  • 安全问题现在要复杂得多。例如,要执行 TLS,基于主机的代理必须包含每个应用程序的密钥信息,这种成为易受混淆的代理问题,并且成了新的漏洞攻击的向量,也就是说,代理中的任何 CVE 或漏洞现在都是潜在的密钥泄漏。

简而言之,sidecar 保持了从转移到容器中获得的隔离保证:内核可以在容器级别执行多租户的所有安全性和公平性考虑因素,而且一切都可以正常工作。基于主机的模型使我们完全脱离了这个世界,也给我们留下了多租户争用的所有问题。

当然,基于每个主机的代理也有一些优点。你可以将请求必须通过的代理数量,从 sidecar 模型中的每跳两个,减少到每跳一个,这样可以节省延迟。你可以使用数量更少、但更大的代理,如果你的代理具有较高的基础架构成本,那么这样可能更有利于资源消耗。 (Linkerd 1.x 就是一个很好的例子——很擅长扩大流量规模但不擅长缩小规模)。而且你的网络架构图也变得更简单了,因为你有更少的节点。

但是与你所遇到的运维性和安全问题相比,这些优点是次要的。而且,除了在网络图中减少节点之外,我们可以通过良好的工程设计来减少这些差异——确保我们的 sidercar 尽可能快、尽可能的小。

09 我们能不能改进一下代理

关于基于每个主机的代理,我们列出的一些问题涉及到我们的多租户争用。在 sidecar 领域,我们使用内核的现有解决方案,通过容器来解决多租户争用。但在基于主机的代理模型中,我们不能这样做,然而我们可以通过让基于主机的代理本身能够处理多租户的争用,来解决这些问题吗?例如,一个流行的代理是 Envoy。我们是否可以通过调整 Envoy,来处理多租户争用来解决每个主机代理的问题?

如果说是肯定的,是因为 “它不会违背宇宙的物理定律”。但因为 “这将是一项巨大的工作,不会很好地利用任何人的时间”,所以答案应该是否定的。Envoy 不是为多租户争用设计的,它将需要巨大的努力来改变这一点。有一个很长的有趣的 Twitter 帖子[4],探讨了如果你想深入了解 Envoy 的细节,必须做很多事情:需要在项目中增加大量非常棘手的工作,以及必须不断权衡“每个租户只运行一个 Envoy” 的大量更改——也就是 sidecar。

即使你完成了这项工作,到最后,你仍然会遇到爆炸半径和安全的问题。

10 服务网格的未来

综上所述,我们得出了一个结论:不管有没有 eBPF,服务网格可预见的未来都是通过在用户空间中运行的 sidecar 代理构建的。

Sidecar 并不是没有问题,但它们是现有的最好的解决方案,可以在保持容器所提供的隔离性的同时,还要全面处理云本地网络的复杂性。说到 eBPF,它能从服务网格分担工作,它应该通过使用 sidecar 代理来做到这一点,而不是基于主机的代理。

“让现有的基于 sidecar 的方法更快,同时保留容器化的操作和安全优势” 与 “通过摆脱 sidecar 来解决服务网格的复杂性和性能” 两者卖点不太一样,但从用户的角度来看,这是一种胜利。

eBPF 的功能最终会发展到,不需要代理来处理,服务网格提供的 L7 工作的全部范围吗?也许吧。内核将能够通过除 eBPF 以外的机制,来吸收工作范围吗?也许吧。这两种可能性似乎都不是近在眼前,但如果有一天它们真的出现了,或许我们就可以告别 “sidercar 代理” 了。我们期待这种可能性。

与此同时,从 Linkerd 的角度来看,我们将继续努力使我们的 sidecar 微型代理尽可能小、快、操作上可以忽略不计,包括将有意义工作交给 eBPF。我们的基本职责是维护我们的用户,和他们在 Linkerd 的运营体验,通过这个镜头,我们必须始终权衡每一个设计和工程。

相关链接:

[1] https://buoyant.io/service-mesh-manifesto/

[2] https://buoyant.io/mtls-guide/

[3] https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/

[4] https://twitter.com/mattklein123/status/1522925333053272065

Merbridge 
如上文所说,「服务网格可预见的未来,都是通过在用户空间中运行的 sidecar 代理构建的」,Merbridge 并不是想要去代替服务网格,或者生成另外一种服务网格,而是在这种模式下,结合 eBPF 的特性,用来提升 Sidecar 模式在网格中的表现。欢迎对 Merbridge 感兴趣的伙伴,加入 Merbridge 的开源贡献、技术讨论和使用。

项目地址:

https://github.com/merbridge/merbridge

社区交流:

https://join.slack.com/t/merbridge/shared_invite/zt-11uc3z0w7-DMyv42eQ6s5YUxO5mZ5hwQ

微信社群:

DaoCloud 公司简介:「DaoCloud 道客」云原生领域的创新领导者,成立于 2014 年底,拥有自主知识产权的核心技术,致力于打造开放的云原生操作系统为企业数字化转型赋能。产品能力覆盖云原生应用的开发、交付、运维全生命周期,并提供公有云、私有云和混合云等多种交付方式。成立迄今,公司已在金融科技、先进制造、智能汽车、零售网点、城市大脑等多个领域深耕,标杆客户包括交通银行、浦发银行、上汽集团、东风汽车、海尔集团、屈臣氏、金拱门(麦当劳)等。目前,公司已完成了 D 轮超亿元融资,被誉为科技领域准独角兽企业。公司在北京、武汉、深圳、成都设立多家分公司及合资公司,总员工人数超过 400 人,是上海市高新技术企业、上海市“科技小巨人”企业和上海市“专精特新”企业,并入选了科创板培育企业名单。

未经允许不得转载:DaoCloud道客博客 » Linkerd 创始人:eBPF 将影响服务网格的未来

申请试用