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 则用于无侵入的审计日志采集。
eBPF 程序类型与内核网络栈挂载点 eBPF 程序类型与内核网络栈挂载点 用户态应用 / 容器 / Pod 套接字层(Socket) TC(流量控制)— ingress/egress 内核协议栈(IP/TCP/UDP) XDP(网卡驱动层) 物理/虚拟网卡(NIC) kprobe 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 参数校验 参数类型、大小、可空性必须匹配 加载失败
eBPF 验证器工作流程 eBPF 验证器工作流程 1. 加载 eBPF 字节码 2. 静态分析 3. 模拟执行 4. JIT ❌ 拒绝加载 ✅ 加载成功 验证器确保:无循环、无越界、指针安全、栈不溢出、Helper 参数正确。 通过后,eBPF 程序被 JIT 编译为原生机器码,性能接近内核原生函数。

安全模型的三层保障

  1. 验证器层: 拒绝所有可能破坏内核稳定性的程序,确保控制流和内存访问安全。
  2. 运行时层: 通过 BPF 权限(CAP_BPF) 和 cgroup 绑定,限制 eBPF 程序的作用范围。在 Kubernetes 中,可通过 Pod Security Policy 或 OPA 策略控制哪些租户可以加载 eBPF 程序。
  3. 数据隔离层: 通过 Map 的 key 设计(如使用租户 ID 作为前缀)和 cgroup 过滤,确保一个租户的 eBPF 程序无法访问另一个租户的数据。
租户隔离最佳实践: 在加载 eBPF 程序时,使用 bpf_prog_attach() 将程序绑定到特定 cgroup,配合 bpf_get_current_cgroup_id() Helper 函数,即可实现基于 cgroup 的租户级隔离。