2. eBPF 基础:虚拟机架构、程序类型与 Map 数据结构
eBPF(extended Berkeley Packet Filter)是 Linux 内核中一种革命性的沙箱化运行时环境。它允许用户在无需修改内核源码或加载内核模块的情况下,安全、高效地扩展内核功能。在 Cilium 中,eBPF 是实现网络策略、负载均衡、可观测性以及缓存命中等关键能力的核心技术基石。理解 eBPF 的底层架构是进行故障排查的前提。
2.1 eBPF 虚拟机架构
eBPF 并非直接运行用户编写的 C 代码,而是通过一个轻量级的、运行在内核空间的虚拟指令集(eBPF ISA)来执行。其架构设计确保了安全性与可移植性。
核心组件: eBPF 虚拟机由 11 个 64 位寄存器(R0-R10)、一个程序计数器(PC)、一个 512 字节的栈空间以及一个辅助函数(Helper Function)调用接口组成。
2.2 eBPF 程序类型
eBPF 程序通过挂载到内核的不同事件钩子(Hook)来触发执行。不同的程序类型决定了其上下文、能力以及触发时机。在 Cilium 网络场景中,最核心的三种类型是 XDP、TC 和 Kprobe。
| 程序类型 | 挂载点 | 触发时机 | Cilium 中的典型用途 |
|---|---|---|---|
| XDP | 网卡驱动层(最早) | 接收数据包时,在 SKB 分配前 | DDoS 防护、负载均衡(Maglev)、早期丢包 |
| TC | 内核网络协议栈(ingress/egress) | 数据包进入/离开协议栈时 | 网络策略执行、封装/解封装(VXLAN/Geneve)、带宽管理 |
| Kprobe | 内核函数入口/出口 | 内核函数被调用时 | 动态追踪、性能分析、调试内核函数调用链 |
故障排查提示: 当遇到 Cilium 缓存未命中时,首先应确认 eBPF 程序是否正确挂载。使用
bpftool prog list 可以查看当前系统中所有加载的 eBPF 程序及其类型。例如,查找类型为 xdp 或 sched_cls(TC)的程序。
2.3 eBPF Map 数据结构
eBPF Map 是 eBPF 程序与用户空间程序之间,以及不同 eBPF 程序之间进行数据交换的核心机制。它是一种通用的键值对存储结构,支持多种类型。Cilium 大量使用 Map 来存储连接跟踪表、策略规则、端点信息以及缓存条目。
2.3.1 常用 Map 类型对比
| Map 类型 | 数据结构 | 特点 | Cilium 应用场景 |
|---|---|---|---|
| BPF_MAP_TYPE_HASH | 哈希表 | 通用键值查找,支持动态增删 | 策略规则表、端点映射表 |
| BPF_MAP_TYPE_ARRAY | 数组 | 索引访问,预分配固定大小,查找速度快 | 全局配置、统计计数器 |
| BPF_MAP_TYPE_LRU_HASH | LRU 哈希表 | 自动淘汰最近最少使用的条目 | 连接跟踪表(CT Map)、缓存 |
| BPF_MAP_TYPE_PERCPU_HASH | Per-CPU 哈希表 | 每个 CPU 核心一份副本,无锁并发 | 高并发统计、性能敏感路径 |
2.3.2 Map 操作与缓存命中
在 Cilium 的数据路径中,eBPF 程序通过 bpf_map_lookup_elem() 辅助函数来查询 Map。缓存命中的本质就是一次成功的 Map 查找。如果查找失败(返回 NULL),则意味着缓存未命中,数据包将触发一次完整的策略查找或状态创建流程,这会增加延迟。
排查要点: 缓存未命中通常由以下原因导致:
- Map 大小不足: LRU Map 容量过小,导致活跃条目被过早淘汰。
- 键值构造错误: 查找时使用的 Key 与存储时不一致(例如,IP 地址字节序错误)。
- 程序逻辑缺陷: eBPF 程序未正确更新或删除 Map 中的条目。
bpftool map dump 可以查看 Map 的当前内容,是定位此类问题的利器。
2.4 关键辅助函数
eBPF 程序不能直接调用内核函数,而是通过一组精心设计的辅助函数(Helper)与内核交互。以下是与 Cilium 缓存和网络处理最相关的几个函数:
bpf_map_lookup_elem(map, key):从 Map 中查找键对应的值。返回指向值的指针,若未找到则返回 NULL。bpf_map_update_elem(map, key, value, flags):更新或创建 Map 中的条目。bpf_map_delete_elem(map, key):从 Map 中删除一个条目。bpf_skb_load_bytes(skb, offset, to, len):从网络数据包(SKB)中安全地读取数据到 eBPF 栈中。bpf_ktime_get_ns():获取当前时间戳(纳秒),常用于计算缓存条目的老化时间。
性能基准: 一次成功的
bpf_map_lookup_elem 在 LRU 哈希表中的平均耗时通常在几十纳秒级别。如果排查中发现单次查找耗时超过 1 微秒,则可能存在 Map 冲突严重或 Per-CPU 数据局部性不佳的问题。