一、eBPF 技术概述
1.1 什么是 eBPF
eBPF(Extended Berkeley Packet Filter)是 Linux 内核中的一项革命性技术,它允许用户在不修改内核源码、不加载内核模块的情况下,安全地在内核中运行自定义程序。eBPF 最初用于网络数据包过滤,如今已扩展为通用的内核观测和编程框架,成为云原生时代可观测性、安全和网络领域的基石技术。
1.2 eBPF 核心架构
eBPF 程序的生命周期包含以下关键步骤:用户空间编写 eBPF 程序(C 语言子集) → 通过 LLVM/Clang 编译为 eBPF 字节码 → 系统调用 bpf() 加载到内核 → 内核验证器(Verifier)进行安全校验 → JIT 编译为原生机器码 → 挂载到内核钩子点执行。验证器确保程序不会崩溃内核、不会无限循环、内存访问安全,这是 eBPF 区别于内核模块的核心安全保障。
1.3 eBPF 的三大应用场景
- 可观测性(Observability): 系统调用追踪、函数调用栈分析、网络请求延迟测量、容器级别资源监控
- 网络(Networking): XDP 高速数据包处理、负载均衡(Cilium)、服务网格数据平面加速
- 安全(Security): 运行时安全监控(Falco)、系统调用过滤、文件访问审计
二、开发工具链实战
2.1 BCC(BPF Compiler Collection)
BCC 是最流行的 eBPF 开发框架,提供 Python/Lua 前端和 C 后端,内含大量开箱即用的观测工具:
# 安装 BCC(Ubuntu/Debian)
sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
# 追踪所有 open() 系统调用
sudo opensnoop-bpfcc
# 统计块 I/O 延迟分布
sudo biosnoop-bpfcc
# 追踪 TCP 连接和接受
sudo tcpconnect-bpfcc
sudo tcpaccept-bpfcc
# 生成 CPU Flame Graph 所需的栈信息
sudo profile-bpfcc -F 99 -af 30 > out.stacks
FlameGraph/flamegraph.pl out.stacks > flame.svg
2.2 bpftrace 单行脚本
bpftrace 提供类似 awk 的高级语言,适合快速编写一行式追踪脚本:
# 统计每个进程的系统调用次数
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'
# 追踪所有 malloc 调用及大小(用户态)
sudo bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { printf("malloc %d bytes\n", arg0); }'
# 监控超过 100ms 的磁盘 I/O
sudo bpftrace -e 'kprobe:blk_account_io_start { @start[tid] = nsecs; } kprobe:blk_account_io_done /@start[tid]/ { $duration = nsecs - @start[tid]; if ($duration > 100000000) { printf("slow I/O: %s %d ms\n", comm, $duration/1000000); } delete(@start[tid]); }'
2.3 libbpf 和 CO-RE
libbpf 是 C 语言原生 eBPF 库,配合 CO-RE(Compile Once, Run Everywhere)技术,通过 BTF(BPF Type Format)实现跨内核版本兼容:
// bootstrap.bpf.c - 最简单的 libbpf 程序
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, u32);
__type(value, u64);
} exec_start SEC(".maps");
SEC("tp/sched/sched_process_exec")
int tracepoint__sched__sched_process_exec(struct trace_event_raw_sched_process_exec *ctx)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
u64 ts = bpf_ktime_get_ns();
bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);
bpf_printk("Process %d started at %llu\n", pid, ts);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
三、系统调用追踪深度实战
3.1 进程Exec行为监控
基于 tracepoint 追踪进程创建,审计所有新启动的进程:
#!/usr/bin/env python3
from bcc import BPF
import time
prog = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct data_t {
u32 pid;
u32 ppid;
char comm[TASK_COMM_LEN];
char filename[256];
};
BPF_PERF_OUTPUT(events);
TRACEPOINT_PROBE(sched, sched_process_exec) {
struct data_t data = {};
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
data.pid = bpf_get_current_pid_tgid() >> 32;
data.ppid = task->real_parent->tgid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
bpf_probe_read_str(&data.filename, sizeof(data.filename), (void *)args->filename);
events.perf_submit(args, &data, sizeof(data));
return 0;
}
"""
b = BPF(text=prog)
print("%-10s %-8s %-8s %-30s %s" % ("TIME(s)", "PID", "PPID", "COMMAND", "FILENAME"))
def print_event(cpu, data, size):
event = b["events"].event(data)
print("%-10.3f %-8d %-8d %-30s %s" % (
time.time(), event.pid, event.ppid,
event.comm.decode(), event.filename.decode()))
b["events"].open_perf_buffer(print_event)
while True:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
3.2 文件访问异常检测
监控敏感目录的异常文件访问行为:
#!/usr/bin/env python3
from bcc import BPF
prog = """
struct data_t {
u32 pid;
u32 uid;
char comm[TASK_COMM_LEN];
char fname[256];
int flags;
};
BPF_PERF_OUTPUT(events);
int trace_do_sys_openat2(struct pt_regs *ctx, int dfd,
const char __user *filename,
struct open_how *how) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid() >> 32;
data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
bpf_probe_read_user_str(&data_fname, sizeof(data.fname), filename);
data.flags = how->flags;
if (data.fname[0] == '/' && data.fname[1] == 'e' &&
data.fname[2] == 't' && data.fname[3] == 'c') {
events.perf_submit(ctx, &data, sizeof(data));
}
return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event="do_sys_openat2", fn_name="trace_do_sys_openat2")
四、网络性能分析实战
4.1 TCP 连接全生命周期追踪
追踪 TCP 三次握手到四次挥手的完整过程,定位连接延迟瓶颈:
// tcp_lifecycle.bpf.c
struct sock_info {
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u32 pid;
u64 timestamp;
};
BPF_HASH(tcp_state, struct sock *, struct sock_info);
SEC("tracepoint/tcp/tcp_set_state")
int trace_tcp_set_state(struct trace_event_raw_tcp_event_sk *ctx) {
struct sock *sk = ctx->skaddr;
u16 new_state = ctx->newstate;
struct sock_info info = {};
bpf_probe_read(&info.saddr, sizeof(info.saddr), &sk->__sk_common.skc_rcv_saddr);
bpf_probe_read(&info.daddr, sizeof(info.daddr), &sk->__sk_common.skc_daddr);
bpf_probe_read(&info.sport, sizeof(info.sport), &sk->__sk_common.skc_num);
bpf_probe_read(&info.dport, sizeof(info.dport), &sk->__sk_common.skc_dport);
info.pid = bpf_get_current_pid_tgid() >> 32;
info.timestamp = bpf_ktime_get_ns();
bpf_map_update_elem(&tcp_state, &sk, &info, BPF_ANY);
return 0;
}
4.2 HTTP 请求延迟测量
通过 uprobe 追踪 nginx/Go HTTP 框架的请求处理延迟:
# 追踪 Go HTTP 请求处理延迟
sudo bpftrace -e '
uprobe:./app:http.HandlerFunc.ServeHTTP {
@start[tid] = nsecs;
}
uretprobe:./app:http.HandlerFunc.ServeHTTP /@start[tid]/ {
$latency = (nsecs - @start[tid]) / 1000000;
@latency_ms = hist($latency);
if ($latency > 100) {
printf("SLOW: %s took %d ms\n", comm, $latency);
}
delete(@start[tid]);
}'
# 统计各路径的延迟分布
sudo bpftrace -e '
uprobe:./app:github.com/gin-gonic/gin.(*Context).Next {
@path_start[tid] = nsecs;
}
uretprobe:./app:github.com/gin-gonic/gin.(*Context).Next /@path_start[tid]/ {
$ms = (nsecs - @path_start[tid]) / 1000000;
@path_latency[str(arg0)] = hist($ms);
delete(@path_start[tid]);
}'
五、CPU 性能分析与火焰图
5.1 生成火焰图的完整流程
# 步骤1: 使用 profile-bpfcc 采集栈采样数据
sudo profile-bpfcc -F 99 -f 60 > out.stacks 2>&1
# 步骤2: 折叠栈格式转换生成 SVG
cat out.stacks | ./flamegraph.pl --color=java --title="CPU Flame Graph" > flame.svg
# 步骤3: 针对特定进程采集
sudo profile-bpfcc -F 99 -p $(pgrep -n myapp) 30 > myapp.stacks
# 步骤4: 包含用户态+内核态+OFF-CPU栈
sudo profile-bpfcc -F 99 30 -U > full.stacks
# 步骤5: Off-CPU 分析(阻塞时间热力图)
sudo offcputime-bpfcc -df -p $(pgrep -n myapp) 10 > offcpu.stacks
./flamegraph.pl --color=io --title="Off-CPU Time" offcpu.stacks > offcpu.svg
5.2 调度器延迟分析
追踪进程调度延迟,识别 CPU 争用问题:
# 统计调度运行队列延迟
sudo runqlat-bpfcc 1 10
# 输出示例:
# usecs : count distribution
# 0 -> 1 : 0 | |
# 2 -> 3 : 0 | |
# 4 -> 7 : 1 | |
# 8 -> 15 : 12 |** |
# 16 -> 31 : 45 |******** |
# 32 -> 63 : 187 |*****************************************|
# 64 -> 127 : 98 |********************* |
# 128 -> 255 : 23 |**** |
# 256 -> 511 : 5 |* |
# 追踪调度延迟直方图(按进程分组)
sudo runqlen-bpfcc -m 1 5
六、容器与云原生环境监控
6.1 容器级别资源监控
cgroup 级别的 eBPF 程序,为每个容器独立统计资源使用:
// container_monitor.bpf.c
struct container_stat {
u64 cpu_time_ns;
u64 io_read_bytes;
u64 io_write_bytes;
u64 net_rx_bytes;
u64 net_tx_bytes;
u64 mem_usage;
u32 pid_count;
};
BPF_HASH(cgroup_stats, u64, struct container_stat);
SEC("cgroup/skb")
int cgroup_skb_monitor(struct __sk_buff *skb) {
u64 cgroup_id = bpf_skb_cgroup_id(skb);
struct container_stat *stat = cgroup_stats.lookup(&cgroup_id);
if (!stat) {
struct container_stat new_stat = {};
cgroup_stats.update(&cgroup_id, &new_stat);
stat = cgroup_stats.lookup(&cgroup_id);
}
if (skb->ingress_ifindex) {
stat->net_rx_bytes += skb->len;
} else {
stat->net_tx_bytes += skb->len;
}
return 1;
}
6.2 Kubernetes Pod 网络拓扑
自动发现 Kubernetes Pod 间的网络流量关系,生成实时拓扑:
# 使用 Pixie(基于 eBPF 的 K8s 可观测平台)
px deploy
px script px/service_stats # 自动获取所有服务间流量
# 使用 Hubble(Cilium 的 eBPF 可观测层)
hubble observe --namespace default --protocol http
hubble observe --namespace kube-system --verdict DROPPED
# 自定义 bpftrace 脚本追踪 DNS 请求
sudo bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo {
printf("PID %d (%s) DNS lookup: %s\n", pid, comm, str(arg0));
}'
七、高级 eBPF 技术模式
7.1 BPF Maps 数据交换机制
BPF Maps 是内核态 eBPF 程序与用户态程序之间的数据桥梁:
// 丰富的 Map 类型满足不同场景
// 1. BPF_MAP_TYPE_HASH - 通用键值存储
BPF_HASH(connectsock, struct sock *, u64);
// 2. BPF_MAP_TYPE_PERF_OUTPUT - 高性能事件流
BPF_PERF_OUTPUT(events);
// 3. BPF_MAP_TYPE_RING_BUFFER - 新版高效环形缓冲区(推荐)
BPF_RINGBUF_OUTPUT(events, 1 << 12);
// 4. BPF_MAP_TYPE_LRU_HASH - 自动淘汰的有限容量哈希表
BPF_LRU_HASH(cache, u64, struct data, 1024);
// 5. BPF_MAP_TYPE_STACK_TRACE - 内核栈采样存储
BPF_STACK_TRACE(stack_traces, 1024);
// 6. BPF_MAP_TYPE_LPM_TRIE - 最长前缀匹配(路由/防火墙)
BPF_LPM_TRIE(subnets, struct subnet_key, u32, 10000);
// 7. BPF_MAP_TYPE_SOCKMAP / BPF_MAP_TYPE_DEVMAP - 网络转发加速
BPF_SOCKMAP(sock_map, 65535);
// 环形缓冲区示例
SEC("tp/sched/sched_process_exit")
int trace_process_exit(struct trace_event_raw_sched_process_template *ctx) {
struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e) return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
e->exit_code = ctx->exit_code;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_ringbuf_submit(e, 0);
return 0;
}
7.2 Tail Call 尾调用链
通过尾调用将复杂的 eBPF 程序拆分为多个独立模块:
// 尾调用映射表
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__type(key, u32);
__type(value, u32);
__uint(max_entries, 8);
} prog_array SEC(".maps");
// 主入口函数
SEC("xdp")
int xdp_main(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
// IPv4 → 跳转到路由模块(index=0)
if (eth->h_proto == bpf_htons(ETH_P_IP))
bpf_tail_call(ctx, &prog_array, 0);
// IPv6 → 跳转到 IPv6 处理模块(index=1)
else if (eth->h_proto == bpf_htons(ETH_P_IPV6))
bpf_tail_call(ctx, &prog_array, 1);
// ARP → 跳转到 ARP 处理模块(index=2)
else if (eth->h_proto == bpf_htons(ETH_P_ARP))
bpf_tail_call(ctx, &prog_array, 2);
return XDP_PASS;
}
// 子模块:IPv4 路由
SEC("xdp")
int xdp_ipv4_route(struct xdp_md *ctx) { return XDP_PASS; }
// 子模块:IPv6 处理
SEC("xdp")
int xdp_ipv6_route(struct xdp_md *ctx) { return XDP_PASS; }
八、生产环境部署最佳实践
8.1 性能开销控制
eBPF 程序引入的性能开销极低,但生产中仍需注意采样频率和程序复杂度:
- 采样频率: CPU profiling 推荐 99Hz(而非更高频率),足以捕获热点同时开销可控
- 程序复杂度: 验证器限制程序不超过 100 万指令,避免在热路径上执行复杂计算
- 批量化输出: 优先使用 Ring Buffer 替代 Perf Buffer,减少事件传输的 per-event 开销
- 条件过滤: 尽早过滤无关事件,减少向用户态推送的数据量
8.2 安全权限管理
# eBPF 系统调用需要的能力
sudo setcap cap_sys_admin+ep /usr/sbin/bpftrace
# 或者(Linux 5.8+)拆分细粒度能力
sudo setcap cap_bpf,cap_perfmon,cap_net_admin,cap_sys_resource+ep /path/to/binary
# 通过 sysctl 控制 eBPF 对非特权用户的访问
sysctl -w kernel.unprivileged_bpf_disabled=1
# 查看当前 eBPF 程序加载情况
sudo bpftool prog show
sudo bpftool map show
sudo bpftool net show
九、总结
eBPF 正在重塑 Linux 内核可观测性的格局。相比传统工具(strace、tcpdump、perf),eBPF 提供以下核心优势:极致低开销(纳秒级执行、可安全用于生产环境)、全栈可见性(同时观测用户态+内核态+硬件事件)、动态性(无需重启即可部署观测策略)。从 BCC 快速原型到 libbpf CO-RE 生产部署,eBPF 工具链已覆盖了从开发调试到生产监控的完整生命周期。在云原生时代,eBPF 已成为 Cilium、Pixie、Falco 等明星项目的底层基石,是每一位 Linux 工程师必须掌握的核心技术。

发表评论 取消回复