原文:https://docs.projectcalico.org/about/about-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 开销。