我抛弃 tcpdump 改用 eBPF 抓包,把生产网络排障时间从 1 小时压到 5 分钟
凌晨 1 点,告警群里又炸了:订单服务的 P99 突然飙到 1.8s,但应用日志一片祥和。
我深吸一口气,把已经敲了无数遍的 tcpdump -i eth0 -w /tmp/cap.pcap 又敲了一遍。30 秒后我意识到这次不一样——容器网络是 veth,没法直接抓 host 的 eth0;换成 nsenter 进容器抓,又因为抓包点不对看不到 client 真实源 IP;折腾了 40 分钟,最后用 tcpdump -i any 抓了一大堆无关流量,扔进 Wireshark 过滤得头秃。
那次以后,我把生产网络排障的"瑞士军刀"从 tcpdump 换成了 eBPF。不是装 X,是真的被现实打服了:eBPF 能在不抓包的情况下直接读内核协议栈的关键数据,定位速度比 tcpdump + Wireshark 快一个量级。今天聊聊我用的几个套路。
为什么 tcpdump 在容器时代越来越难用
先说痛点,tcpdump 不是不好,是它太"老实"了。
第一,丢包率高。 tcpdump 本质是 libpcap 抓包,碰到高 QPS(我们这个订单服务单机 2 万 QPS)就抓不全。我那次抓的 30 秒流量里,TCP 重传只抓到 3 个,运维补抓发现实际有 200 多个——一抓就丢,丢的就是你想看的那部分。
第二,容器网络抓不到"上下文"。 容器是 veth 接到 bridge 上的,tcpdump -i eth0 只能看到 host 那端,看不到容器内部 socket 关联。想知道是哪个 Pod 的哪个连接出的问题,得 nsenter 进容器、ss -tnp 对照、然后再 tcpdump -i eth0 同步抓——三步串起来,5 分钟就没了。
第三,事后分析成本高。 抓完 pcap 还得下载到本地,丢 Wireshark 里过滤、追踪流、读 TCP 标志位。我经常开着 Wireshark 半小时,眼睛都花了才看出一个 RST。
eBPF 解决的就是这三个问题:内核态直接读、零拷贝、可以带 PID/容器 ID/进程名等结构化上下文。
我常用的三件套:bpftrace + bcc + bpftool
工具体系上,我推荐先从 bpftrace 上手,类 awk 的语法,写一行就能跑,不需要编译。
1. bpftrace:单行命令定位 TCP 重传和丢包
看 TCP 重传统计:
bpftrace -e '
kprobe:tcp_retransmit_skb {
@retrans[comm, pid] = count();
printf("%s[%d] retrans on %s\\n", comm, pid, ksym(args->sk->__sk_common.skc_addr));
}
'
跑 10 秒,输出会按进程聚合重传次数。如果某个 nginx-worker 的重传数远高于别的,那就是它的问题;如果是 containerd-shim 自己很高,问题可能在 host 网络栈。
看 TCP 连接建立延迟(connect 系统调用耗时):
bpftrace -e '
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:connect /pid > 1000/ {
@usecs = hist((int64)(arg0));
}
'
这会在退出时打印 P50/P95/P99 直方图。我们那次 P99 异常就是这么发现的:connect 系统调用本身 P99 才 80 微秒,说明问题不在握手,而是在应用层拿到 socket 之后的处理。
2. bcc:看 conntrack 表和 socket buffer
有些东西 bpftrace 一行写不完,我用 bcc 的 Python 工具。
conntrack 表使用率监控(这个排查过载特别有用):
/usr/share/bcc/tools/conntrack -L 2>/dev/null | wc -l
# 或者更细的统计
/usr/share/bcc/tools/tcpconnlat -d
socket buffer 占用:
/usr/share/bcc/tools/sockstat
# 输出类似:
# TCP: active 2300, passive 1
# TCP6: active 0, passive 0
# UDP: active 7
# UDPLITE: active 0
# RAW: active 0
# FRAG: active 0 buffers
# TX: rx 0/0, tx 0/0, allocated 0, mem 0
内存打满的时候一眼能看出来。
3. bpftool:查 prog 和 map 的状态
有时候想确认 eBPF 程序有没有挂上去:
bpftool prog show
bpftool map show
bpftool net show
bpftool net show 能看到 XDP 程序挂在哪个网卡上,对排查"我明明启动了 XDP,怎么没生效"特别管用。
真实案例:用 eBPF 定位 DNS 解析慢
那次 P99 异常,最后根因是 DNS 解析慢(业务逻辑里有一段同步的 DNS 查询)。如果用 tcpdump 抓包,至少要 30 分钟——要过滤 UDP/53、要解码 DNS query、还要把 socket 关联回进程。
我直接上 eBPF,5 分钟搞定:
第一步,看 sendto/recvfrom 耗时分布:
bpftrace -e '
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:sendto {
@start[tid] = nsecs;
}
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:recvfrom /@start[tid]/ {
@usecs[comm] = hist((int64)((nsecs – @start[tid]) / 1000));
delete(@start[tid]);
}
'
直方图一打出来,问题立刻浮出水面:有一个进程的 recvfrom P99 是 380 毫秒,其他都是 1 毫秒级。
第二步,看是哪个域名解析慢:
bpftrace -e '
kprobe:udp_sendmsg /arg2->sin_family == AF_INET/ {
$dport = arg2->sin_port;
$daddr = arg2->sin_addr.s_addr;
if ($dport == 53) {
printf("dns query from %s[%d] to %d.%d.%d.%d:%d\\n",
comm, pid,
($daddr >> 24) & 0xff, ($daddr >> 16) & 0xff,
($daddr >> 8) & 0xff, $daddr & 0xff, $dport);
}
}
'
跑 5 秒,输出能看到所有向 53 端口发包的进程和目标地址。
第三步,用 getsockopt 读 DNS TTL 和重试次数:
这步 bpftrace 一行写不了,我用 bcc 写了一个小工具(从 conntrack 读),直接拿到对端 DNS server 的平均响应时间——是 220 毫秒。
根因:业务代码里有一段旧的 DNS 解析走的是 8.8.8.8 兜底,而 8.8.8.8 在我们生产环境有网络策略限制,部分包会被丢。换成内网 DNS 后,recvfrom P99 从 380 毫秒降到 1.2 毫秒,订单服务整体 P99 从 1.8s 回到 110ms。
整个过程 5 分钟(其中 3 分钟是敲命令,2 分钟是写 bcc 脚本)。
tcpdump 还能用吗?能,但别当主力
我不是说 tcpdump 没用,它在做协议层取证、复现特定 payload、导出 pcap 给安全团队分析时还是首选。但 80% 的"线上排障"场景,eBPF 更合适。
我的经验是:
- 日常告警定位:eBPF(bpftrace + bcc),快
- 协议层取证、payload 抓取:tcpdump + Wireshark,稳
- 长期监控:eBPF + Prometheus exporter(pixie 那种),可观测
把这三件事分开,思路会清晰很多。
踩坑记录(4 条)
坑 1:bpftrace 报错 failed to load BPF program
90% 是因为内核没开 CONFIG_BPF=y。检查:
grep CONFIG_BPF /boot/config-$(uname -r)
zgrep CONFIG_BPF /proc/config.gz 2>/dev/null
我们之前的生产机器是阿里云的内核镜像,BTF 是开的,bpftrace 跑得起来。但有一次在新机器上死活加载不上,最后发现是 vendor 内核禁了 kprobe 调优。结论:在生产环境启用 eBPF 前,先在一个非业务机器上跑一遍 bpftrace 的 sample,确认内核支持。
坑 2:bpftrace 输出里有大量 <<unknown>>
这是因为 /sys/kernel/debug/kprobes 没挂载或者权限不够。挂上就行:
mount -t debugfs debugfs /sys/kernel/debug
chmod 755 /sys/kernel/debug
如果是容器里跑 bpftrace,需要 –privileged 或者加 SYS_ADMIN capability,并且把 /sys/kernel/debug 挂进去。不要在生产 pod 上这么干——单独搞一个带这些权限的"诊断 sidecar"。
坑 3:bcc 工具慢/卡死
bcc 工具里有几个是 interval 模式持续运行的(比如 tcpconnect、tcpaccept),跑在低 QPS 服务上没事,跑在 10 万 QPS 上可能把 CPU 跑满。看 top 是不是 python3 占了大量 CPU 就能确认。
解决方案:用 bpftrace 自带的采样 + histogram 替代,或者用 bcc 的 –duration 参数限制运行时间。
坑 4:eBPF 程序一直挂着,忘记清
# 列出所有 eBPF 程序
bpftool prog show
# 干掉特定程序
bpftool prog delete id <id>
我有个习惯是用一个 shell 脚本统一管,每次排障起一个任务名 net-trace-<pid>,完了就 killall bpftrace 兜底。
写在最后
tcpdump 不会消失,但 eBPF 是"在生产环境真正可用"的内核观测工具——它带结构化上下文(PID/容器 ID/进程名/网络命名空间)、能聚合直方图、不会丢包、还不需要导文件。
我现在的排障顺序基本固定了:
按这个顺序,90% 的网络排障能在 10 分钟内出根因。
如果你也想试 eBPF,强烈建议从 bpftrace –info 那个 tools/ 目录的 sample 脚本开始,30 个左右的小工具覆盖了绝大多数场景。
有问题评论区交流,看到就回。





