关于 eBPF
eBPF 是一种 Linux 内核功能,允许将快速而安全的微型程序加载到内核中以自定义其操作。
通过本文档,您将了解:
- eBPF 的一般背景。
- eBPF 的各种用途。
- Calico 如何在 eBPF 数据平面中使用 eBPF。
什么是 eBPF?
eBPF 是嵌入在 Linux 内核中的 “虚拟机”。它允许将微型程序加载到内核中,并附加到钩子上,而钩子会在发生某些事件时触发。这样可以自定义内核的行为(有时是很严格的)。尽管每种钩子的 eBPF 虚拟机都相同,但是钩子的功能有很大差异。虽然将程序加载到内核中可能很危险,但内核通过非常严格的静态验证器运行所有程序。验证程序会将程序沙盒化,以确保它只能访问允许的内存部分,并确保它必须快速终止。
为什么称为 eBPF?
eBPF 是 “扩展 Berkeley 包过滤器” 的缩写。Berkeley 包过滤器是一种更早,更专业的虚拟机,专门用于过滤数据包。诸如tcpdump此类使用此“经典” BPF VM 来选择应发送到用户空间进行分析的数据包的工具。eBPF 是 BPF 的一个扩展版本,适用于内核内部的通用用途。尽管名称不为人知,但 eBPF 的用途不仅仅限于数据包过滤。
eBPF 可以做什么?
eBPF 类型
内核中可以将 eBPF 程序附加到几类钩子。eBPF 程序的功能在很大程度上取决于它所连接的钩子:
- Tracing 程序可以附加到内核中很大一部分功能上。跟踪程序对于收集统计信息和内核的深入调试很有用。 大多数跟踪钩子仅允许对该函数正在处理的数据进行只读访问,但是有些跟踪钩子允许修改数据。Calico 团队使用跟踪程序来帮助在开发过程中调试 Calico。例如,找出内核为何意外丢弃数据包的原因。
- Traffic Control (
tc
) 程序可以在入口和出口附加到给定的网络设备。内核为每个数据包执行一次程序。由于钩子是用于数据包处理的,因此内核允许程序修改或扩展数据包、删除数据包,将其标记为排队或将数据包重定向到另一个接口。Calico 的 eBPF 数据平面基于这种钩子。我们使用 tc 程序对 Kubernetes 服务进行负载平衡,实施网络策略,并为已建立连接的流量创建快速路径。 - XDP或“ eXpress 数据路径”实际上是 eBPF 钩子的名称。每个网络设备都有一个 XDP 入口钩子,在内核为数据包分配套接字缓冲区之前,它会为每个传入数据包触发一次。XDP 可以为诸如 DoS 保护(在 Calico 的标准 Linux 数据平面中支持)和入口负载平衡(在 facebook 的 Katran 中使用)之类的用例提供出色的性能。XDP 的缺点是,它需要网络设备驱动程序支持才能获得良好的性能,并且与 Pod 网络的互操作性不是很好。
- 几种类型的套接字程序可以在套接字上进行各种操作。例如,允许 eBPF 程序更改新创建的套接字的目标 IP,或强制套接字绑定到“正确的”源 IP 地址。Calico 使用此类程序进行 Kubernetes Services 的连接时负载平衡。这减少了开销,因为在数据包处理路径上没有DNAT。
- 有各种与安全性相关的钩子,允许以各种方式管理程序行为。例如,seccomp钩子允许以细粒度的方式管理系统调用。
- 而且……当您准备好此功能时,可能还会有更多的问题。eBPF 正在内核中进行大量开发。
内核通过“辅助功能”公开每个钩子的功能。例如,该 tc
钩子具有帮助程序功能以调整数据包的大小,但是该助手在跟踪钩子中不可用。使用 eBPF 的挑战之一是不同的内核版本支持不同的帮助程序,而缺少帮助程序可能导致无法实现特定功能。
BPF 映射
附加到 eBPF 钩子的程序可以访问 BPF maps。BPF 映射有两个主要用途:
- 它们允许 BPF 程序存储和检索长期存在的数据。
- 它们允许 BPF 程序和用户空间程序之间的通信。BPF 程序可以读取由用户空间写入的数据,反之亦然。
BPF 映射有很多类型,包括一些允许在程序之间跳转的特殊类型,还有一些充当队列和堆栈而不是严格用作键/值映射。Calico 使用映射来跟踪活动连接,并使用策略和服务 NAT 信息配置 BPF 程序。由于映射访问可能相对昂贵,因此 Calico 的目标是仅对已建立的流中的每个数据包执行单个映射查找。
可以使用 bpftool
内核随附的命令行工具检查 bpf 映射的内容。
Calico 的 eBPF 数据平面
Calico 的 eBPF 数据平面是标准 Linux 数据平面(基于 iptables)的替代方案。虽然标准数据平面通过与 kube-proxy 以及您自己的 iptables 规则互通来关注兼容性,但是 eBPF 数据平面着重于性能,延迟和改善用户体验,而这些功能是标准数据平面所无法实现的。作为其一部分,eBPF 数据平面用 eBPF 实现代替了 kube-proxy。主要的“用户体验”功能是在流量到达 NodePort 时保留来自群集外部流量的源 IP。这使您的服务端日志和网络策略在该路径上更加有用。
功能比较
尽管 eBPF 数据平面具有一些标准 Linux 数据平面所缺少的功能,但反之亦然:
功能 | 标准 Linux 数据平面 | eBPF 数据平面 |
---|---|---|
吞吐量 | 专为 10GBit +设计 | 专为 40GBit +设计 |
第一个封包延迟 | 低(kube-proxy 服务延迟是主要的因素) | 更低 |
后续数据包延迟 | 低 | 更低 |
保留群集中的源 IP | 是 | 是 |
保留外部源 IP | 只有 externalTrafficPolicy: Local |
是 |
Direct Server Return | 不支持 | 支持(需要兼容的基础网络) |
连接跟踪 | Linux 内核的 conntrack 表(大小可以调整) | BPF 地图(固定大小) |
策略规则 | 映射到 iptables 规则 | 映射到 BPF 指令 |
策略选择器 | 映射到 IP 集 | 映射到 BPF 映射 |
Kubernetes 服务 | kube-proxy iptables 或 IPVS 模式 | BPF program and maps |
IPIP | 支持 | 支持(由于内核限制,没有性能优势) |
VXLAN | 支持 | 支持 |
Wireguard | 支持 | 支持 |
Other routing | 支持 | 支持 |
支持第三方 CNI 插件 | 是(仅兼容插件) | 是(仅兼容插件) |
与其他 iptables 规则兼容 | 是(可以在其他规则之上或之下编写规则) | 部分 iptables 绕过工作负载流量 |
XDP DoS 保护 | 支持 | 不支持(尚未) |
IPv6 | 支持 | 不支持(尚未) |
主机端点策略 | 支持 | 不支持(尚未) |
企业版 | 可用 | 不支持(尚未) |
架构概述
Calico 的 eBPF 数据面附加 eBPF 程序到 tc 钩子上的每个 Calico 接口以及您的数据和隧道接口。这允许 Calico 尽早发现工作负载包,并通过绕过 iptables 和内核通常会进行的其他包处理的快速路径来处理它们。
The logic to implement load balancing and packet parsing is pre-compiled ahead of time and relies on a set of BPF maps to store the NAT frontend and backend information. One map stores the metadata of the service, allowing for externalTrafficPolicy
and “sticky” services to be honoured. A second map stores the IPs of the backing pods.
在 eBPF 模式下,Calico 使用 BPF MAP 存储策略选择器匹配的 IP 集,从而将您的策略转换为优化的 eBPF 字节码。
为了提高服务的性能,Calico 还通过挂接到 BPF 套接字钩子来实现连接到负载平衡。当程序试图连接到 Kubernetes 服务时,Calico 会尝试拦截连接,并将套接字配置为直接连接后端 pod 的 IP。这从服务连接中删除了所有的 NAT 开销。