关于后端:译eBPF-和服务网格还不能丢掉-Sidecar

8次阅读

共计 6373 个字符,预计需要花费 16 分钟才能阅读完成。

服务网格以典型的 sidecar 模型为人熟知,将 sidecar 容器与利用容器部署在同一个 Pod 中。虽说 sidecar 并非很新的模型(操作系统的 systemd、initd、cron 过程;Java 的多线程),然而以这种与业务逻辑拆散的形式来提供服务治理等根底能力的设计还是让人一亮。

随着 eBPF 等技术的引入,最近对于服务网格是否须要 sidecar(也就是 sidecarless)的探讨渐增。

笔者认为任何问题都有其起因,短暂困扰服务网格的不外乎性能和资源占用。这篇文章翻译自 Buoyant 的 Flynn 文章 eBPF and the Service Mesh: Don’t Dismiss the Sidecar Yet。心愿这篇文章能帮忙大家穿透迷雾看透事物的实质。


本文要点

  • eBPF 是一个旨在通过(审慎地)容许在内核中运行一些用户代码来进步性能的工具。
  • 在可预感的将来,服务网格所需的第 7 层解决在 eBPF 中不太可能实现,这意味着网格依然须要代理。
  • 与 sidecar 代理相比,每个主机代理减少了操作复杂性并升高了安全性。
  • 能够通过更小、更快的 Sidecar 代理来解决无关 Sidecar 代理的典型性能问题。
  • 目前,sidecar 模型对服务网格仍是最有意义的。

对于 eBPF 的故事曾经在云原生世界中泛滥了一段时间,有时将其形容为自切片面包以来最平凡的事物,有时则讥笑它是对事实世界的无用烦扰。当然,事实要奥妙得多,因而认真钻研一下 eBPF 能做什么和不能做什么仿佛是有必要的——技术毕竟只是工具,应用的工具应该适宜手头的工作。

最近经常出现的一项特殊任务是服务网格所需的简单的第 7 层解决。将其交给 eBPF 可能对服务网格来说是一个微小的胜利,所以让咱们认真看看 eBPF 可能表演的角色。

到底什么是 eBPF?

让咱们先把这个名字弄清楚:“eBPF”最后是“extended Berkeley Packet Filter”(扩大的伯克利包过滤器),只管当初它 基本不代表任何货色。Berkeley 数据包过滤器能够追溯到近 30 年前:它是一种容许用户应用程序间接在操作系统内核中运行某些代码(能够必定是通过严格审查和高度束缚的代码)的技术。BPF 仅限于网络堆栈,但它依然使一些惊人的事件成为可能:

  • 典型的例子是,它能够使试验新型防火墙之类的货色变得更加容易。无需一直地从新编译内核模块,只需对 eBPF 代码进行编辑并从新加载。
  • 同样,它能够为轻松开发一些十分弱小的网络分析关上大门,包含那些不想在内核中运行的。例如,如果想应用机器学习对传入数据包进行分类,能够应用 BPF 抓取感兴趣的数据包并将它们交给运行 ML 模型的应用程序。

还有其余例子:这只是 BPF 实现的两件非常明显的事件 1  — eBPF 采纳了雷同的概念并将其扩大到网络以外的畛域。然而所有这些探讨都提出了一个问题,即为什么这种事件首先须要特地留神。

简短的答复是“隔离”。

隔离

计算——尤其是云原生计算——在很大水平上依赖于硬件同时为多个实体做多项事件的能力,即便其中一些实体对其余实体怀有敌意。这是 竞争性多租户,咱们通常应用能够调解对内存自身的拜访的硬件进行治理。例如,在 Linux 中,操作系统为本人创立一个内存空间(_内核空间_),并为每个用户程序创立一个独自的空间(用户空间),只管每个程序都有本人的空间但统称为用户空间。操作系统而后应用硬件来避免任何跨空间拜访 2

放弃零碎各局部之间的这种隔离对于安全性和可靠性都是十分要害的——基本上 所有 计算平安都依赖于它,事实上,云原生世界更加依赖它,也要放弃内核容器之间的隔离。因而,内核开发人员独特破费了数千人年的工夫来审查围绕这种隔离的每一次交互,并确保内核都能正确处理。这是一项辣手的、精密的、艰辛的工作,遗憾的是,在发现错误之前经常被忽视,而且它是操作系统理论所做工作的 重要 组成部分 3

这项工作如此辣手和精密的局部起因是内核和用户程序不能齐全隔离:用户程序显然须要拜访某些操作系统性能。从历史上看,这是 零碎调用 的畛域。

零碎调用

零碎调用或 syscall 是操作系统内核向用户代码公开 API 的原始形式。对大量细节进行了润饰,用户代码将申请打包并将其交给内核。内核仔细检查以确保其遵循了所有规定,并且——如果所有看起来失常——内核将代表用户执行零碎调用,并依据须要在内核空间和用户空间之间复制数据。对于零碎调用的要害是:

  1. 内核管制着所有。用户代码能够提出申请,而不是要求。
  2. 查看、复制数据等须要工夫。这使得零碎调用比运行一般代码慢,无论是用户代码还是内核代码:是逾越边界的行为让执行变慢。随着工夫的推移,事件变得越来越快,然而对于忙碌的零碎来说对每个网络数据包进行零碎调用是不可能的。

这就是 eBPF 的亮点:无需对每个网络数据包(或跟踪点和其余)进行零碎调用,只需将一些用户代码间接放入内核!而后内核能够全速运行它,只有在真正须要时才将数据分发给用户空间。(最近对 Linux 中的用户 / 内核交互进行了相当多相似的思考,通常成果很好。[io_uring](https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/) 正是该畛域的另一个例子。)

当然,在内核中运行用户代码的确很危险,因而内核破费了大量精力来验证用户代码的理论用处。

eBPF 验证

当用户过程启动时,内核基本上会认为其没问题的并启动运行它。内核在它四周设置了围栏,并且会立刻杀死任何试图毁坏规定的用户过程,然而用户代码基本上被认为是有权执行的。

对于 eBPF 代码,没有这样的冷遇。在内核自身中,保护性围栏基本上不存在,并且自觉地认为用户代码是能够平安运行的,这会为每个安全漏洞敞开大门(以及容许谬误使整个机器解体)。相同,eBPF 代码只有在内核可能果决地证实它是平安的时能力运行。

证实一个程序是平安的 十分 艰难 4。为了使它更易于解决,内核极大地限度了 eBPF 程序能够做什么。例如:

  • eBPF 程序不容许阻塞。
  • 他们不容许有有限循环(事实上,直到 最近才容许他们有循环)。
  • 它们不容许超过某个最大尺寸。
  • 验证者必须可能评估所有可能的执行门路。

验证者齐全是严苛的,并最终做出决定:它必须如此,以维持咱们整个云原生世界所依赖的隔离保障。它还必须在申明程序不平安时报错:如果不能 齐全 确定程序是平安的,它就会回绝。可怜的是,有一些 eBPF 程序是平安的,然而验证者不够聪慧,无奈通过——如果你处于那个地位,你须要重写程序直到验证者能够承受,或者你将须要修补验证程序并构建本人的内核 5

最终后果是 eBPF 是一种 高度 受限的语言。这意味着尽管对每个传入的网络数据包进行简略查看等事件很容易,但在多个数据包之间缓冲数据等看似简略的事件却很难。在 eBPF 中实现 HTTP/2 或终止 TLS 基本不可能:它们太简单了。

最初,所有这些带来了问题:将 eBPF 的网络性能利用于服务网格会是什么样子。

eBPF 和服务网格

服务网格必须解决云原生网络的所有复杂性。例如,它们通常必须发动和终止 mTLS、重试失败的申请、通明地将连贯从 HTTP/1 降级到 HTTP/2、基于工作负载身份执行拜访策略、逾越集群边界发送流量等等。云原生世界还会有更多的复杂性。

大多数服务网格应用 边车 模型来实现。网格将在本人的容器中运行的代理附加到每个应用程序 pod,并且代理拦挡进出应用程序 pod 的网络流量,执行网格性能所需的任何操作。这意味着网格能够解决任何工作负载并且不须要更改应用程序,这对开发人员来说是一个相当大的胜利。这也是平台方面的胜利:他们不再须要依赖应用程序开发人员来实现 mTLS、重试、黄金指标 6 等,因为网格在整个集群中提供了所有这些以及更多。

另一方面,就在不久前,部署所有这些代理的想法齐全是疯狂的,人们依然放心运行额定容器带来的开销。然而 Kubernetes 使部署变得容易,只有放弃代理的轻量级和足够快,它就能够很好地工作。(当然,“轻量级和疾速”是主观的。许多网格应用通用 Envoy 代理作为 sidecar;Linkerd 仿佛是惟一应用专门构建的轻量级代理的。笔者注:还有 Flomesh 应用 可编程的轻量级代理 Pipy。)

那么,一个显著的问题是,咱们是否能够将 sidecar 中的性能下沉到 eBPF 中,以及这样做是否会有所帮忙。在 OSI 第 3 层和第 4 层——IP、TCP 和 UDP——咱们曾经看到 eBPF 获得了一些显著的胜利。例如,eBPF 能够使简单的动静 IP 路由变得相当简略。它能够进行十分智能的数据包过滤,或进行简单的监控,并且能够疾速且高效地实现所有这些工作。在网格须要与这些层的性能交互的中央,eBPF 仿佛必定能够帮忙网格实现。

然而,OSI 第 7 层状况有所不同。eBPF 的执行环境受到如此严格的限度,以至于 HTTP 和 mTLS 级别的协定 远远 超出了它的能力,至多在明天是这样。鉴于 eBPF 一直倒退,兴许将来的某个版本能够治理这些协定,但值得记住的是,编写 eBPF 自身就十分艰难,调试可能更加艰难。许多第 7 层协定是简单的野兽,在绝对宽容的用户空间环境中体现得十分蹩脚。即便在 eBPF 的受限环境中能够重写它们,但目前尚不分明否实用。

当然,咱们能够做的是将 eBPF 与代理配对:将外围低级性能放在 eBPF 中,而后将其与用户空间代码配对以治理简单的性能。这样咱们就有可能在底层取得 eBPF 性能的劣势,同时将真正令人讨厌的货色留在用户空间中。这实际上是当今每个现存的“eBPF 服务网格”所做的,只管它通常没有被宽泛宣传。

这引发了新的问题:这样的代理应该在哪?

主机级代理与边车

与其在 sidecar 模型中那样在每个应用程序 pod 上部署一个代理,不如思考为每个主机(或者,用 Kubernetes 来说,每个节点)部署一个代理。它为治理 IP 路由的形式减少了一点复杂性,但乍一看仿佛提供了一些经济劣势,因为须要代理少了。

然而,sidecar 比主机级代理有一些显著的益处。这是因为 sidecar 就像应用程序的一部分一样,而不是独立于应用程序:

  • Sidecar 资源占用与应用程序负载成正比,因而如果应用程序没有做太多事件,sidecar 的资源占用将放弃在较低水平 7。当应用程序接受大量负载时,Kubernetes 的所有现有机制(资源申请和限度、OOMKiller 等)都会齐全依照习惯的形式工作。
  • 如果 sidecar 产生故障,它只会影响一个 pod,并且现有的 Kubernetes 的机制会响应 pod 故障使其再次失常工作。
  • sidecar 操作与应用程序 pod 操作基本相同。例如,通过失常的 Kubernetes 滚动重启降级到新版本的 sidecar。
  • sidecar 与它的 pod 具备完全相同的平安边界:雷同的平安上下文、雷同的 IP 地址等。例如,它只须要为它的 pod 做 mTLS,这意味着它只须要那个繁多 pod 的密钥数据。如果代理中存在谬误,它只能透露该单个密钥。

对于主机级代理,所有这些事件都会隐没。请记住,在 Kubernetes 中,集群调度程序决定将哪些 pod 调度到给定节点上,这意味着每个节点都能够取得一组随机的 pod。这意味着给定的代理将与应用程序 齐全 解耦,这很重要:

  • 实际上不可能推断单个代理的资源应用状况,因为它将由到应用程序 pod 随机子集的随机的流量子集决定。反过来,这意味着代理最终会因为一些难以了解的起因而失败,而网格团队将承担责任。
  • 应用程序忽然更容易受到 嘈杂街坊 的影响,因为给定主机上调度的每个 pod 的流量都必须流经单个代理。一个高流量的 pod 可能会齐全耗费该节点的所有代理资源,让所有其余 pod 都饿死。代理能够尝试确保偏心解决,但如果高流量 Pod 也耗费了所有节点的 CPU,这也会失败。
  • 如果代理失败,它会影响应用程序 pod 的随机子集——并且该子集将一直变动。同样,尝试降级代理将影响相似随机的、一直变动的应用程序 pod 子集。任何故障或保护工作都会忽然产生不可预知的副作用。
  • 代理当初会逾越节点上调度的每个应用程序 pod 的平安边界,这比仅仅耦合到单个 pod 简单得多。例如,mTLS 须要为每个调度的 pod 保留密钥,而不是混同哪个密钥与哪个 pod 一起应用。代理中的任何谬误都是更可怕的事件。

基本上,sidecar 应用容器模型来施展其劣势:内核和 Kubernetes 致力在容器级别强制执行隔离和偏心,且失常。主机级代理超出了该模型,这意味着它们必须本人解决所有竞争性多租户问题。

主机级代理的确有劣势。首先,在 sidecar 世界中,从一个 Pod 到另一个 Pod 总是两次通过代理;在主机的世界中,有时它只有一个跳跃 8,这能够缩小一点提早。此外,最终能够运行更少的代理,如果代理在闲暇时资源使用率很高,则能够节俭资源耗费。然而,与经营和平安问题的老本相比,这些改良相当小,而且它们在很大水平上能够通过应用更小、更快、更简略的代理来缓解。

咱们是否还能够通过改良代理以更好地解决竞争性多租户来缓解这些问题?兴许。这种办法有两个次要问题:

  1. 竞争多租户是一个平安问题,最好应用更小、更简略、更易于调试的代码来解决平安问题。增加大量代码以更好地解决竞争性多租户基本上与平安最佳实际截然相同。
  2. 即便平安问题能够齐全解决,操作问题依然存在。每当咱们抉择进行更简单的操作时,咱们都应该问为什么,以及谁受害。

总体而言,这些类型的代理更新可能须要大量工作 9,这引发了无关进行这项工作的价值的真正问题。

把所有都绕回来,让咱们回顾一下咱们最后的问题:将服务网格性能下沉到 eBPF 会是什么样子?咱们晓得咱们须要一个代理来保护咱们须要的第 7 层性能,并且咱们进一步晓得 sidecar 代理能够在操作系统的隔离爱护范畴内工作,其中主机级的代理必须本人治理所有内容。这不是一个小区别:主机级代理的潜在性能劣势基本不会超过额定的平安问题和操作复杂性,因而无论是否波及 eBPF,咱们都将 sidecar 作为最可行的抉择。

展望未来

不言而喻,任何服务网格的首要任务都必须是用户的操作体验。咱们能够在哪里应用 eBPF 来取得更高的性能和更低的资源应用,太棒了!然而咱们须要留神不要在这个过程中就义用户体验。

eBPF 最终会可能笼罩服务网格的全副范畴吗?不太可能。如上所述,十分不分明在 eBPF 中实现所有须要的第 7 层解决是否可行,即便在某些时候它的确有可能。同样,可能还有一些其余机制能够将这些 L7 性能转移到内核中——不过,从历史上看,这方面并没有很大的推动力,也不分明是什么真正使这种能力引人注目。(请记住,将性能移入内核意味着移除咱们为确保用户空间平安而依赖的围栏。)

那么,在可预感的将来,服务网格的最佳前进方向仿佛是踊跃寻找依赖 eBPF 来进步性能的中央,但承受用户空间 sidecar 的需要代理,并加倍努力使代理尽可能小、疾速和简略。

脚注

对于作者

Flynn 是 Buoyant 的技术布道者,次要关注 Linkerd 服务网格、Kubernetes 和云原生开发。他还是 Emissary-ingress API 网关的原作者和维护者,并且在通信和平安方面的软件工程畛域投入了数十年的工夫。

关注 ” 云原生指北 ” 微信公众号
(转载本站文章请注明作者和出处盛世浮生,请勿用于任何商业用途)


  1.  或者,至多,要容易得多。↩
  2.  至多,不是没有节目之间的预先安排。这超出了本文的范畴。↩
  3.  其余的大部分是调度。↩
  4. 事实上,在个别状况下是不可能的。如果你想清理你的 CS 课程作业,首先要解决的是进展问题。↩
  5. 其中一件事可能比另一件事更容易。特地是如果您想让您的验证程序补丁被上游承受!↩
  6. 流量、提早、谬误和饱和度。↩
  7. 再次假如,一个足够轻的边车。↩
  8. 不过,有时它依然是两个,所以这有点喜忧参半。↩
  9. 例如,有一个乏味的 twitter 帖子 说要为 Envoy 做到这一点有多难。↩
正文完
 0