2. eBPF基础回顾:eBPF程序类型、验证器、Map与Helper函数
在深入租户隔离与网络观测之前,必须重新夯实eBPF的核心基础。eBPF并非单一技术,而是一个由程序类型(Program Type)、验证器(Verifier)、Map和Helper函数构成的完整执行体系。本节将拆解这四大支柱,并重点分析它们在网络观测场景中的角色。
2.1 eBPF程序类型:挂载点决定能力边界
eBPF程序类型决定了程序可以挂载到内核的哪个事件点,以及能够访问哪些上下文数据。对于网络观测,以下类型最为关键:
| 程序类型 | 挂载点 | 网络观测用途 |
|---|---|---|
| XDP | 网卡驱动层(最早) | 高速包过滤、DDoS防御、原始包捕获 |
| TC(ingress/egress) | 内核协议栈流量控制层 | 包改写、流量镜像、QoS标记 |
| Socket Filter | 套接字层 | 按进程/连接粒度过滤数据包 |
| Kprobe / Tracepoint | 内核函数入口/出口 / 静态跟踪点 | 观测TCP状态机、套接字内存、连接生命周期 |
| cgroup/skb | cgroup层级网络控制器 | 容器/租户级别的网络带宽与访问控制 |
设计要点: 在租户隔离场景中,XDP 用于租户入口流量的早期丢弃,cgroup/skb 用于按租户cgroup进行出向限速,Kprobe 用于无侵入地采集每个租户的连接数、重传率等观测指标。
2.2 eBPF验证器:安全执行的守门人
验证器是eBPF安全性的核心保障。它确保加载到内核的程序不会导致内核崩溃、死锁或信息泄露。验证过程分为两个阶段:
- CFG(控制流图)检查: 确保程序无循环(或循环有界)、无不可达指令、无越界跳转。
- 静态分析: 模拟执行所有路径,检查指针引用、内存访问范围、栈深度、Helper函数参数类型。
租户隔离影响: 验证器强制eBPF程序必须无状态(除非通过Map)且终止性可证明。这意味着网络观测程序不能依赖全局变量,所有跨租户的计数、速率统计必须通过Map完成,且Map的访问范围受验证器严格检查。
2.3 eBPF Map:内核态与用户态的数据桥梁
Map是eBPF程序与用户态程序共享数据的核心结构。它支持多种数据类型,并提供了原子操作保证。在网络观测中,Map用于存储连接跟踪表、包计数、速率统计和租户元数据。
| Map类型 | 数据结构 | 网络观测典型用途 |
|---|---|---|
| BPF_MAP_TYPE_HASH | 哈希表(key-value) | 连接跟踪表(五元组 → 租户ID) |
| BPF_MAP_TYPE_ARRAY | 固定大小数组 | 按CPU核的包计数器、租户速率桶 |
| BPF_MAP_TYPE_PERCPU_HASH | 每CPU哈希表 | 高性能连接统计,避免锁竞争 |
| BPF_MAP_TYPE_RINGBUF | 环形缓冲区 | 向用户态推送包事件、连接事件 |
| BPF_MAP_TYPE_STACK_TRACE | 栈跟踪 | 诊断网络延迟的调用栈 |
Map访问限制: 验证器会检查eBPF程序对Map的访问是否越界。例如,
ARRAY 类型的索引必须在编译时或运行时被证明在合法范围内。在租户隔离场景中,Map的key通常包含租户ID,验证器无法验证运行时key的合法性,因此必须通过边界检查或条件分支来确保安全。
2.4 Helper函数:内核功能的受控暴露
Helper函数是eBPF程序与内核交互的唯一合法途径。每个程序类型都有其可用的Helper函数集合。网络观测常用的Helper包括:
bpf_skb_load_bytes()/bpf_skb_store_bytes():读取/修改包数据。bpf_map_lookup_elem()/bpf_map_update_elem():操作Map。bpf_get_current_cgroup_id():获取当前cgroup ID,用于租户识别。bpf_perf_event_output():向用户态发送观测事件。bpf_ktime_get_ns():获取高精度时间戳,用于延迟计算。
// 示例:在XDP程序中获取cgroup ID并更新租户计数器
struct bpf_map_def SEC("maps") tenant_packets = {
.type = BPF_MAP_TYPE_PERCPU_ARRAY,
.key_size = sizeof(u32), // 租户ID
.value_size = sizeof(u64), // 包计数
.max_entries = 1024,
};
SEC("xdp")
int xdp_count_tenant(struct xdp_md *ctx) {
u32 cgroup_id = bpf_get_current_cgroup_id();
u32 tenant_id = cgroup_id & 0xFFFF; // 简化映射
u64 *count = bpf_map_lookup_elem(&tenant_packets, &tenant_id);
if (count) {
__sync_fetch_and_add(count, 1);
}
return XDP_PASS;
}
2.5 关键设计约束总结
在后续的租户隔离蓝图设计中,以下eBPF基础特性将直接影响架构决策:
- 程序类型决定挂载点: XDP用于租户入口,cgroup/skb用于容器边界,Kprobe用于无侵入观测。
- 验证器强制安全性: 所有Map访问必须可证明安全,租户ID作为Map key时需额外边界检查。
- Map是唯一状态载体: 租户计数器、速率限制器、连接表必须通过Map实现,且优先使用PERCPU类型避免锁竞争。
- Helper函数受限: 网络观测程序只能使用白名单内的Helper,例如
bpf_get_current_cgroup_id()是租户识别的关键入口。