eBPF与内核可观测性
章节概述
eBPF (extended Berkeley Packet Filter) 是Linux内核中革命性的技术,它允许在内核中安全地运行自定义程序,为系统可观测性、网络、安全等领域带来了前所未有的能力。本章将介绍eBPF的基础知识和实用工具。
学习目标:
- 理解eBPF的工作原理和应用场景
- 掌握bcc工具集的使用
- 学会使用eBPF追踪内核事件
- 能够编写简单的eBPF程序
核心概念
1. 什么是eBPF?
传统调试方法的问题:
strace:
- 只能追踪系统调用
- 性能开销大
- 无法追踪内核内部
perf:
- 采样式追踪
- 精度有限
- 难以追踪特定事件
内核模块:
- 需要重新编译
- 不安全
- 难以部署
eBPF的优势:
安全性:
- 验证器检查代码
- 不会导致内核崩溃
- 沙箱执行
性能:
- 内核态执行,开销小
- JIT编译
- 高效的事件过滤
灵活性:
- 动态加载/卸载
- 无需重启
- 支持多种事件源
架构图:
┌─────────────────────────────────────┐
│ 用户空间应用 │
│ (bpftrace, bcc tools) │
└──────────────┬──────────────────────┘
│ bpf系统调用
┌──────────────▼──────────────────────┐
│ eBPF虚拟机 │
│ ┌────────────────────────────────┐ │
│ │ eBPF验证器 │ │
│ │ - 检查代码安全性 │ │
│ │ - 确保有限循环 │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ JIT编译器 │ │
│ │ - 编译为机器码 │ │
│ └────────────────────────────────┘ │
│ ┌────────────────────────────────┐ │
│ │ Maps (数据存储) │ │
│ │ - hash, array, perf_buffer │ │
│ └────────────────────────────────┘ │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ 内核事件钩子 │
│ - kprobe (内核函数) │
│ - tracepoint (静态追踪点) │
│ - uprobe (用户态函数) │
│ - XDP (网络包处理) │
└─────────────────────────────────────┘
2. eBPF程序类型
kprobe / kretprobe:
作用:追踪内核函数
- kprobe: 函数入口
- kretprobe: 函数返回
示例:
- 追踪sys_read系统调用
- 追踪tcp_v4_connect
tracepoint:
作用:内核静态追踪点
- 稳定的接口
- 不依赖函数名
示例:
- sched:sched_process_exec
- net:net_dev_queue
uprobe / uretprobe:
作用:追踪用户态函数
- uprobe: 函数入口
- uretprobe: 函数返回
示例:
- 追踪libc的malloc
- 追踪Go程序的函数
XDP (eXpress Data Path):
作用:网络包处理
- 最早的包处理点
- 极高性能
- 可以丢弃/转发/修改包
示例:
- DDoS防护
- 负载均衡
3. Maps - 数据存储
Map类型:
BPF_HASH:
- 哈希表
- 用于存储统计数据
BPF_ARRAY:
- 数组
- 固定大小
BPF_PERF_OUTPUT:
- 环形缓冲区
- 向用户空间传递事件
BPF_STACK_TRACE:
- 栈追踪
- 用于火焰图
️ BCC工具集
1. 常用工具
execsnoop - 追踪进程执行:
# 追踪所有exec系统调用
sudo execsnoop
# 输出示例:
# PCOMM PID PPID RET ARGS
# ls 12345 1234 0 /bin/ls -l
# python 12346 1234 0 /usr/bin/python test.py
opensnoop - 追踪文件打开:
# 追踪open系统调用
sudo opensnoop
# 输出:
# PID COMM FD ERR PATH
# 1234 nginx 3 0 /var/log/nginx/access.log
# 1235 mysqld 4 0 /var/lib/mysql/data.db
biolatency - 块I/O延迟:
# 追踪磁盘I/O延迟分布
sudo biolatency
# 输出:
# usecs : count distribution
# 0 -> 1 : 0 | |
# 2 -> 3 : 0 | |
# 4 -> 7 : 10 |* |
# 8 -> 15 : 50 |****** |
# 16 -> 31 : 100 |************* |
# 32 -> 63 : 200 |**************************|
tcpconnect - 追踪TCP连接:
# 追踪主动TCP连接
sudo tcpconnect
# 输出:
# PID COMM IP SADDR DADDR DPORT
# 1234 curl 4 192.168.1.100 1.2.3.4 80
# 1235 mysql 4 192.168.1.100 192.168.1.200 3306
tcpretrans - 追踪TCP重传:
# 追踪TCP重传包
sudo tcpretrans
# 输出:
# TIME PID IP LADDR:LPORT T> RADDR:RPORT STATE
# 10:30:15 0 4 192.168.1.100:45678 R> 1.2.3.4:80 ESTABLISHED
tcptop - TCP流量Top:
# 实时显示TCP连接流量
sudo tcptop
# 输出:
# PID COMM LADDR LPORT RADDR RPORT RX_KB TX_KB
# 1234 nginx 192.168.1.100 80 1.2.3.4 45678 1000 5000
# 1235 mysql 192.168.1.100 3306 192.168.1.200 56789 2000 500
2. 网络相关工具
# tcplife - TCP会话统计
sudo tcplife
# tcpdrop - TCP丢弃包
sudo tcpdrop
# tcpsynbl - SYN backlog
sudo tcpsynbl
# tcpsubnet - 按子网统计流量
sudo tcpsubnet
# sofdsnoop - Socket FD分配追踪
sudo sofdsnoop
3. 文件系统工具
# vfsstat - VFS统计
sudo vfsstat
# filelife - 文件生命周期
sudo filelife
# filetop - 文件I/O Top
sudo filetop
# fileslower - 慢文件I/O
sudo fileslower 10 # 大于10ms的I/O
4. 内存工具
# memleak - 内存泄漏检测
sudo memleak -p <pid>
# slabratetop - slab分配率
sudo slabratetop
# oomkill - OOM杀进程追踪
sudo oomkill
编写eBPF程序
1. 使用bpftrace
bpftrace是高级追踪语言:
# 安装
sudo apt install bpftrace
# 简单示例:追踪openat系统调用
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
示例1:追踪进程CPU时间:
sudo bpftrace -e '
tracepoint:sched:sched_switch
{
@time[pid] = nsecs;
}
tracepoint:sched:sched_switch
/@time[args->prev_pid]/
{
@cpu_ns[args->prev_pid, comm] = sum(nsecs - @time[args->prev_pid]);
}
END
{
clear(@time);
}
'
示例2:统计系统调用:
sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter
{
@syscall[args->id] = count();
}
interval:s:5
{
print(@syscall);
clear(@syscall);
}
'
示例3:TCP连接延迟:
sudo bpftrace -e '
kprobe:tcp_v4_connect
{
@start[tid] = nsecs;
}
kretprobe:tcp_v4_connect
/@start[tid]/
{
$lat = (nsecs - @start[tid]) / 1000;
@latency_us = hist($lat);
delete(@start[tid]);
}
'
2. 使用BCC (Python接口)
示例:追踪read系统调用
#!/usr/bin/python3
# readlatency.py
from bcc import BPF
from time import sleep
# eBPF程序 (C代码)
prog = """
#include <uapi/linux/ptrace.h>
BPF_HASH(start, u32);
BPF_HISTOGRAM(dist);
int do_entry(struct pt_regs *ctx)
{
u32 pid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
start.update(&pid, &ts);
return 0;
}
int do_return(struct pt_regs *ctx)
{
u32 pid = bpf_get_current_pid_tgid();
u64 *tsp, delta;
tsp = start.lookup(&pid);
if (tsp == 0) {
return 0;
}
delta = bpf_ktime_get_ns() - *tsp;
dist.increment(bpf_log2l(delta / 1000));
start.delete(&pid);
return 0;
}
"""
# 加载eBPF程序
b = BPF(text=prog)
b.attach_kprobe(event="__x64_sys_read", fn_name="do_entry")
b.attach_kretprobe(event="__x64_sys_read", fn_name="do_return")
print("Tracing read() latency... Hit Ctrl-C to end.")
try:
sleep(99999999)
except KeyboardInterrupt:
pass
# 打印直方图
print("\nread() latency (us):")
b["dist"].print_log2_hist("usecs")
运行:
sudo python3 readlatency.py
# 输出:
# Tracing read() latency... Hit Ctrl-C to end.
# ^C
# read() latency (us):
# usecs : count distribution
# 0 -> 1 : 100 |********** |
# 2 -> 3 : 200 |********************|
# 4 -> 7 : 150 |*************** |
# 8 -> 15 : 50 |***** |
3. 性能分析示例
CPU火焰图:
# 采集60秒的CPU栈
sudo /usr/share/bcc/tools/profile -F 99 -f 60 > profile.stacks
# 生成火焰图
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
./flamegraph.pl < profile.stacks > cpu_flame.svg
# 在浏览器中打开cpu_flame.svg
Off-CPU火焰图:
# 追踪阻塞事件
sudo /usr/share/bcc/tools/offcputime -df -p <pid> 60 > offcpu.stacks
# 生成火焰图
./flamegraph.pl --color=io --title="Off-CPU Time" < offcpu.stacks > offcpu_flame.svg
实战场景
场景1:排查网络延迟
# 1. 追踪TCP连接延迟
sudo bpftrace -e '
kprobe:tcp_v4_connect { @start[tid] = nsecs; }
kretprobe:tcp_v4_connect /@start[tid]/ {
printf("Connection latency: %d us\n", (nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
'
# 2. 追踪TCP重传
sudo tcpretrans
# 3. 查看丢包
sudo tcpdrop
场景2:排查磁盘I/O慢
# 1. I/O延迟分布
sudo biolatency -m
# 2. 慢I/O追踪
sudo biosnoop | awk '$4 > 10'
# 3. 文件级I/O追踪
sudo filetop
sudo fileslower 10
场景3:排查内存泄漏
# 1. 追踪内存分配
sudo memleak -p <pid>
# 输出:
# [10:30:15] Top 10 stacks with outstanding allocations:
# 128 bytes in 8 allocations from stack
# 0x7f8b9c0a1b20 malloc+0x0
# 0x5555555551a0 leak_func+0x10
# 0x555555555200 main+0x20
# 2. 追踪特定函数
sudo memleak -p <pid> -t -o 10000 -c 'malloc,free'
常见问题
Q1: eBPF对性能有影响吗?
A: 有影响,但很小:
- 采样式追踪:影响< 1%
- 每事件追踪:影响1-5%
- 复杂过滤:影响可能更大
- 建议:生产环境谨慎使用
Q2: eBPF需要什么内核版本?
A: 基本要求:
- Linux 4.x+: 基本eBPF支持
- Linux 4.14+: BTF (BPF Type Format)
- Linux 5.x+: 更多特性
检查:
uname -r
# 4.15.0 或更高
# 检查eBPF支持
zgrep CONFIG_BPF /proc/config.gz
Q3: bpftrace和bcc有什么区别?
A:
- bpftrace: 单行脚本,快速诊断
- bcc: Python/C编程,功能强大
- 选择:临时诊断用bpftrace,复杂工具用bcc
Q4: eBPF程序会导致内核崩溃吗?
A: 不会:
- 验证器检查所有代码
- 禁止不安全操作
- 强制有限循环
- 但bug可能导致性能下降
复习题
选择题
eBPF的主要优势是?
- A. 比strace快
- B. 安全且灵活
- C. 使用简单
- D. 无性能开销
kprobe和tracepoint的区别?
- A. kprobe更稳定
- B. tracepoint更稳定
- C. 没有区别
- D. kprobe性能更好
BCC工具集使用什么语言?
- A. Shell
- B. Python
- C. Go
- D. Rust
简答题
- 解释eBPF的工作原理。
- 列举至少5个常用的BCC工具及其用途。
- 什么是Maps?它在eBPF中的作用是什么?
- 如何使用eBPF排查TCP重传问题?
实战题
使用bpftrace编写一个程序,统计每个进程的系统调用次数,并按次数排序输出。
下一章预告: 综合实战案例,将所有知识点串联起来解决实际问题。