2. eBPF 基础:程序类型、Map、Helper 与安全模型
eBPF 并非单一技术,而是一套由程序类型(Program Type)、Map、Helper 函数以及验证器(Verifier)构成的完整执行体系。理解这四部分,是设计租户隔离方案的前提。
2.1 eBPF 程序类型:挂载点决定视野
eBPF 程序必须挂载到内核的特定事件上才能执行。不同的程序类型决定了你能“看到”什么数据,以及能“干预”哪个网络层次。在云原生场景中,最常用的四种类型如下:
| 程序类型 | 触发点 | 典型用途 | 租户隔离价值 |
|---|---|---|---|
| kprobe | 内核函数入口/出口 | 监控系统调用、文件操作、网络栈内部函数 | 细粒度审计租户进程的内核行为 |
| tracepoint | 内核预定义静态桩点 | 跟踪网络收包、内存分配、进程调度 | 稳定且低开销的租户事件采集 |
| xdp | 网卡驱动层(最早) | 高性能包过滤、DDoS 防护、负载均衡 | 在租户流量进入网络栈前完成隔离决策 |
| tc | 内核协议栈流量控制层 | 流量整形、QoS、容器网络策略 | 在 cgroup/veth 出口/入口实施带宽与访问控制 |
选型建议: 在租户隔离场景中,XDP 适合南北向流量的快速丢包/重定向,tc 适合东西向容器间流量的精细化策略,tracepoint 则用于无侵入的审计日志采集。
2.2 eBPF Map:内核与用户态的数据桥梁
eBPF Map 是内核中的键值存储结构,用于在 eBPF 程序之间、以及 eBPF 程序与用户态之间共享数据。Map 的类型决定了数据结构和访问方式。
常用 Map 类型对比
| Map 类型 | 数据结构 | 典型场景 |
|---|---|---|
| BPF_MAP_TYPE_HASH | 哈希表 | 存储租户 ID 与策略的映射关系 |
| BPF_MAP_TYPE_ARRAY | 固定大小数组 | 统计计数器、全局标志位 |
| BPF_MAP_TYPE_PERCPU_HASH | 每 CPU 哈希 | 高性能租户流量统计,避免锁竞争 |
| BPF_MAP_TYPE_RINGBUF | 环形缓冲区 | 高效传递事件日志到用户态 |
| BPF_MAP_TYPE_STACK_TRACE | 栈跟踪 | 记录租户进程的内核调用栈 |
隔离设计要点: 在租户隔离场景中,建议为每个租户分配独立的 Map 实例,或在同一个 Map 中使用租户 ID 作为 key 前缀,避免数据交叉污染。
2.3 Helper 函数:安全的内核能力调用
eBPF 程序不能直接调用内核函数,必须通过 Helper 函数 来获取数据或执行操作。Helper 函数是内核提供的、经过安全审查的 API 接口。
- 数据获取类:
bpf_get_current_pid_tgid()、bpf_get_current_cgroup_id()— 用于识别当前操作所属的租户。 - 网络操作类:
bpf_redirect()、bpf_skb_load_bytes()— 用于流量重定向和包解析。 - Map 操作类:
bpf_map_lookup_elem()、bpf_map_update_elem()— 用于读写隔离策略。 - 辅助输出类:
bpf_printk()、bpf_ringbuf_output()— 用于调试和日志采集。
注意: Helper 函数的可用性取决于 eBPF 程序类型。例如,XDP 程序不能调用
bpf_get_current_pid_tgid(),因为 XDP 运行在网卡驱动层,没有进程上下文。
2.4 eBPF 验证器与安全模型
eBPF 的安全核心在于 验证器(Verifier)。任何 eBPF 程序在加载到内核之前,都必须通过验证器的静态分析和模拟执行,确保不会破坏内核稳定性。
验证器检查项
| 检查维度 | 具体规则 | 违反后果 |
|---|---|---|
| 控制流完整性 | 禁止循环(除非有界)、禁止死代码、禁止递归 | 加载失败 |
| 指针安全 | 禁止解引用未验证的指针、禁止越界访问 | 加载失败 |
| 栈大小限制 | 最大 512 字节 | 加载失败 |
| 指令数限制 | 最大 100 万条指令(复杂程序需验证更久) | 加载失败 |
| Helper 参数校验 | 参数类型、大小、可空性必须匹配 | 加载失败 |
安全模型的三层保障
- 验证器层: 拒绝所有可能破坏内核稳定性的程序,确保控制流和内存访问安全。
- 运行时层: 通过 BPF 权限(CAP_BPF) 和 cgroup 绑定,限制 eBPF 程序的作用范围。在 Kubernetes 中,可通过 Pod Security Policy 或 OPA 策略控制哪些租户可以加载 eBPF 程序。
- 数据隔离层: 通过 Map 的 key 设计(如使用租户 ID 作为前缀)和 cgroup 过滤,确保一个租户的 eBPF 程序无法访问另一个租户的数据。
租户隔离最佳实践: 在加载 eBPF 程序时,使用
bpf_prog_attach() 将程序绑定到特定 cgroup,配合 bpf_get_current_cgroup_id() Helper 函数,即可实现基于 cgroup 的租户级隔离。