何时(以及何时不)使用 eBPF

| 2022年10月31日

原文地址:https://www.tigera.io/blog/ebpf-when-and-when-not-to-use-it/

本文介绍了 eBPF 的一些基本应用场景,主要和内核函数,iptables 对比,让大家能理解 eBPF 应该在那些场景下使用,那些场景下不建议使用。我认为可以作为入门 eBPF 的必修课,学习一个技术,必须知道这个技术擅长那些场景,不擅长那些场景,在学习和使用上才不至于走弯路。

前言

扩展伯克利包过滤器 (eBPF) 是 Linux 内核的一个相对较新的特性,这个特性让许多 DevOps、SRE 和工程师感到眼前一亮。但是它是满足所有 Linux 内核需求的一站式解决方案吗?所以本文中我们要分析看看 eBPF 在哪些方面做得很好,以及它与标准 Linux iptables 的对比如何。

eBPF 是什么?

eBPF 是 Linux 内核中一个已经可用的特性,它允许在内核中运行一个虚拟机。这个虚拟机可以让你安全地将程序加载到内核中并执行,这样就可以灵活的在用户层定义各种内核中的操作了。那为什么这很重要呢?

在过去,对内核进行功能更改是非常困难的:可以调用一些内核 api 来获取数据,但不能影响内核内部的内容或执行代码。如果要这样做,那你需要向 Linux 社区提交一个补丁,然后等待它被批准。而使用 eBPF,可以将程序加载到内核中,并让内核在一些情况下执行你的程序,比如收到一个数据包或者其他的事件发生。

有了 eBPF,内核及其行为变得高度可定制,不再是固定的了。如果在合适的情况下使用这种能力,将是非常有价值的。

如何使用 eBPF?

eBPF 有几个使用场景,包括流量控制、创建网络策略、连接时长负载平衡和可观察性。

流量控制

如果没有 eBPF,数据包使用标准的 Linux 网络路径到达最终目的地。如果一个数据包出现在 A 点,并且你知道这个数据包需要到达 B 点,那么你可以通过优化 Linux 内核中的网络路径将数据包直接发送到 B 点来。使用 eBPF,你就可以利用额外的上下文在内核中进行这些更改,这样数据包就可以绕过复杂的路由,直接到达最终目的地。

这在 Kubernetes 容器环境中尤其有用,因为其中有许多网络。(除了主机网络堆栈,每个容器都有自己的迷你网络堆栈)当流量进来时,它通常被路由到一个容器堆栈,并且在从主机堆栈到达那里的过程中必须经过一个复杂的路径。可以使用 eBPF 绕过此复杂的路由。

创建网络策略

在创建网络策略时,有两种情况可以使用 eBPF:

  • eXpress 数据路径(XDP) - 当一个原始数据包缓冲区进入系统时,eBPF 为你提供了一种有效的方法来检查该缓冲区并快速决定如何处理它。
  • 网络策略 - eBPF 允许高效地检查数据包和实施网络策略,无论是 pod 和主机。

连接时长负载平衡

在 Kubernetes 中进行负载均衡服务连接时,一个端口需要与服务进行通信,因此需要进行 NAT 转换。一个数据包被发送到一个虚拟 IP,该虚拟 IP 将其转换为目的 IP 是支持该服务的 pod 上;然后pod 响应虚拟 IP,返回的数据包被转发回源地址。

通过使用 eBPF,可以使用已加载到内核的 eBPF 程序在连接开始处理的地方进行负载平衡,从而避免这种包转换。由于目的网络地址转换(DNAT)不需要在包处理路径上做处理,因此所有来自服务连接的 NAT 开销都被优化掉了。

可观察性

使用 eBPF 实现可观察性的两种有用方法:收集统计信息和深入调试内核。eBPF 程序可以附加到内核中许多不同的函数上,可以访问这些函数正在处理的数据,也允许修改这些数据。例如,使用 eBPF,如果建立了网络连接,则可以在创建套接字时触发一个调用。为什么将套接字调用作为事件接收很重要?因为 eBPF 在打开套接字的程序的上下文中提供了这些函数调用,因此可以获得关于哪个进程打开了它,以及套接字发生了什么事情。

性能的代价

那么 eBPF 比标准的 Linux iptables 更有效吗?简单来说:要分情况来看。

如果要对 iptables 在大量 IP 地址(即 ipset)的进行网络策略时的工作方式进行微基准测试,iptables 在很多情况下比 eBPF 更好。但是如果想在 Linux 内核中做一些事情,比如需要更改内核中的数据包流向,那么 eBPF 将是更好的选择。标准的 Linux iptables 是一个复杂的系统,当然有它的局限性,但同时它提供了操控流量的能力;如果你知道如何编写 iptables 规则,那么你可以做很多事情。eBPF 允许将自己的程序加载到内核中,并可以根据你的需要定制影响内核中的行为,因此它比 iptables 更灵活,因为它不局限于是一组规则。

另外需要考虑的是,虽然 eBPF 允许运行程序、添加逻辑、重定向流向和绕过处理(这是肯定的优势),但它是一个虚拟机,因此必须转换为字节码运行。相比之下,Linux 内核的 iptables 运行的是已经编译好的代码。

正如你所看到的,eBPF 和 iptables 并不是一个直接的比较。我们需要评估的是性能,这里需要关注的两个关键因素是延迟(速度)和消耗。如果 eBPF 非常快,但占用了 80% 的资源,那么它就像兰博基尼——一辆昂贵的跑车。如果这对你有用,那太好了(也许你真的喜欢昂贵的跑车)。只要记住,更多的 CPU 使用就意味着你要在云提供商这里花更多的钱。因此,虽然兰博基尼可能比很多其他汽车都快,但如果你需要遵守日常通勤的速度限制,它可能不是最好的花钱方式。

何时使用 eBPF(何时不使用)

使用 eBPF,你可以获得性能——但这是有代价的。你需要通过计算性能的代价,并从 eBPF 的功能角度决定你是否接受,要从而找到两者之间的平衡。

来看一些使用 eBPF 有意义的具体情况,以及一些不建议使用 eBPF 的情况。

✘ 何时不用 eBPF
  • 实现应用层策略 - 由于价格与性能的权衡,使用 eBPF 执行深度协议检查和实现应用层策略的效率不是很高。你可以利用 Linux 内核的连接跟踪器( connection tracker)来实现策略,对每个流应用一次策略(无论流有 5 个包还是5000 个包),并在 Linux conntrack 表中将其标记为允许或拒绝。不需要不断检查流中的每个包。如果要使用 eBPF 实现策略,它允许在单个 TCP 连接上有多个 HTTP 事务,为了检测这些事务需要检查每个包,并且要实现第 7 层控制。要做到这一点,需要执行 CPU 周期,这个代价非常大。一种更有效的方法是使用像 Envoy 这样的代理,并使用eBPF 优化到 Envoy 的流量,同时让 Envoy 为来翻译应用程序协议。Iptables + Envoy 这样的代理是更好的设计,在这种情况下也是更好的选择。
  • 构建服务网格控制平面 - 类似地,服务网格依赖于像 Envoy 这样的代理。多年来,我们在设计这一过程上花了很多心思。这样做的主要原因是,在许多情况下,对于像 HTTP 这样的应用协议,在集群内高速进行内置处理是不可行的。因此,应该考虑使用 eBPF 以一种有效的方式将通信路由到像 Envoy 这样的代理,而不是使用它替换代理本身。
  • 逐包处理 — 使用 eBPF 执行 CPU 密集型或逐包处理,例如对加密流进行解密和重新加密,并不高效,因为需要构建一个结构并对每个包进行查找,代价非常大。
✔ When to use eBPF
  • XDP - eBPF 在原始数据包进入系统时提供了一种有效的处理方法,允许快速决定如何处理数据包。
  • 连接时间负载平衡 - 使用 eBPF 可以在源端使用加载到内核的程序进行负载平衡,而不是使用虚拟 IP。由于 DNAT 不需要发生在包处理路径上,所有来自业务连接的 NAT 开销就都被优化了。
  • 可观察性 - epf程序是在Linux内核中添加探测器作为传感器的极好方法,以获得上下文丰富的数据。这是一个巨大的好处,因为不需要更改内核来支持跟踪和分析。您可以很容易地在打开套接字的程序的上下文中接收套接字调用,或者添加程序来跟踪内核中的系统调用。在我们看来,可观察性是eBPF最有用的用例。

总结

eBPF 是 iptables 的替代品吗?我想不完全是。很难想象所有东西在 eBPF 中都能像在 iptables 中一样高效地工作。目前,这两种功能是共存的,取决于用户如何权衡代价和性能,并根据他们的特定需求决定何时使用哪个功能。

我们相信正确的解决方案是利用 eBPF 加上 Linux 内核中的现有机制来实现你想要的结果。既然我们已经确定 eBPF 和 iptables 都是有用的,那么在我们看来,唯一符合逻辑的事情就是同时支持两者。