我抛弃 tcpdump 改用 eBPF 抓包,把生产网络排障时间从 1 小时压到 5 分钟
凌晨 3 点,告警群又响了。
“线上支付服务接口偶发超时,3 秒才返回,用户在疯狂投诉。”
我打开终端,习惯性地输入tcpdump -i eth0 -w /tmp/cap.pcap host 10.20.30.40。跑 30 秒,下载 pcap 用 Wireshark 打开,翻了 20 分钟才找到几条可疑的 RST。但 1 小时过去了,根因没定位到,老板已经开始催第三次。
这次我没再用 tcpdump。
我直接在目标 Pod 上挂了一个 eBPF 探针,3 分钟内锁定了根因:容器内 glibc 的 DNS 解析器对并发请求加锁,高并发下重试把 5ms 的 DNS 查询拖成了 2.8s。
排障时间从 1 小时压到 5 分钟。这不是玄学,是 eBPF 给运维人的武器升级。
今天就把我现在生产环境用的 eBPF 排障四件套讲清楚:抓包、连接跟踪、DNS 延迟归因、SSL/TLS 握手监控。所有脚本可以直接拷走用。
为什么 tcpdump 不够用了
先说清楚痛点,再说 eBPF 怎么解决的。
1. tcpdump 抓包代价大、容易丢包。在高 QPS(>5k)的服务上,tcpdump 抓包的开销是肉眼可见的——一次抓包可能让 P99 延迟翻倍,丢包率能到 30%。你抓到的包本身就是"被污染的现场"。
2. tcpdump 看不到内核态。丢包发生在网卡驱动?socket buffer 满?conntrack 表满?tcpdump 只能告诉你"包到没到",不能说"为什么没到"。
3. tcpdump 抓不到容器网络细节。在 K8s 集群里,跨节点通信走 veth/calico/flannel,tcpdump 在宿主机上抓的是 overlay 网络,根本看不到 Pod 内部的 socket buffer 和 syscall。
4. 抓完还要下到本地用 Wireshark 离线分析。慢,效率低。
而 eBPF 解决的核心问题是:让你在内核态"以零侵入方式"观测网络行为——抓包、计数、延迟归因、协议解析,全部在内核里完成,结果直接打到用户态日志。开销 < 1%,而且能拿到 tcpdump 永远拿不到的内核态指标。
eBPF 排障第一式:bpftrace 一行命令抓 TCP 重传
bpftrace 是最简单上手的 eBPF 工具。直接一行命令就能看 TCP 重传统计:
bpftrace-e' kprobe:tcp_retransmit_skb { $skb = (struct sk_buff *)arg0; $saddr = ntop($skb->sk->__sk_common.skc_rcv_saddr); $daddr = ntop($skb->sk->__sk_common.skc_dport); printf("retrans: %s:%d -> %s, state=%d\n", $saddr, $skb->sk->__sk_common.skc_dport, $daddr, $skb->sk->__sk_common.skc_state); } '跑 10 秒,看到几十条重传记录,源头是支付服务连 Redis 集群的 6379 端口——瞬间就知道问题出在 Redis 链路。
更精炼的版本(按目标地址聚合重传次数):
bpftrace-e' kprobe:tcp_retransmit_skb { @retrans[arg1] = count(); } interval:s:5 { time(); print(@retrans); clear(@retrans); } '这玩意儿比netstat -s | grep retrans强 10 倍——后者只能给你总数字,前者能告诉你"是哪些连接在重传"。
eBPF 排障第二式:跟踪 connect() 系统调用延迟
上次排 DNS 那个问题,靠的就是这个脚本。它能告诉你"每个 socket 连接花了多久",特别是 DNS 解析这种容易慢在用户态的场景:
cat>/tmp/connect_latency.bt<<'EOF' #include <net/sock.h> #include <net/inet_sock.h> BEGIN { printf("Tracing connect() latency... Ctrl-C to end.\n"); } kprobe:inet_stream_connect { $sk = (struct sock *)arg0; $start = nsecs(); @start[tid] = $start; @sk[tid] = $sk; } kretprobe:inet_stream_connect /@start[tid]/ { $sk = @sk[tid]; $delta = (nsecs() - @start[tid]) / 1000; $saddr = ntop($sk->__sk_common.skc_rcv_saddr); $daddr = ntop($sk->__sk_common.skc_dport); $state = $sk->__sk_common.skc_state; @usecs[$saddr . ":" . $daddr] = hist($delta); delete(@start[tid]); delete(@sk[tid]); } END { printf("\nConnect latency (us) by remote endpoint:\n"); print(@usecs); } EOFbpftrace /tmp/connect_latency.bt跑 30 秒后,我看到一组连接花 280 万微秒(2.8 秒)才完成——这就是 DNS 解析的瓶颈点。connect()系统调用本身不慢,慢的是 glibcgetaddrinfo()内部的串行重试。
锁定根因后,修复就是把getaddrinfo()换成getaddrinfo_a()异步接口,并加上 nscd 缓存。当晚发版,第二天没再报警。
eBPF 排障第三式:实时统计 conntrack 表使用率
K8s 集群里,conntrack 表满是最常见的"莫名其妙丢包"原因。tcpdump 抓不到,netstat 也看不到,但 eBPF 能直接看内核计数器:
bpftrace-e' kprobe:nf_conntrack_in { @active = count(); } interval:s:2 { $max = (uint64)512000; // /proc/sys/net/netfilter/nf_conntrack_max $usage = @active * 100 / $max; printf("conntrack: active=%d, max=%d, usage=%d%%\n", @active, $max, $usage); clear(@active); } '我曾经在生产看到 usage 飙到 87%,剩下的 13% 槽位被短连接快速耗光,新连接直接被 drop——这就是为啥服务"莫名其妙超时"。
conntrack 满的修复不是扩容,是找出谁在疯狂创建短连接。配合下面的脚本,能直接看到是哪个 PID 在创建连接:
bpftrace-e' kprobe:tcp_v4_connect { @creates[comm, pid] = count(); } interval:s:5 { print(@creates); clear(@creates); } 'eBPF 排障第四式:xdp 抓包器,零拷贝高性能抓包
如果你真要抓全量包做协议分析,又不想 tcpdump 那样拖慢系统,用 XDP(eXpress Data Path)抓包器。
最简单的例子,用 bcc 工具xdpdump:
# 抓 eth0 上目的端口 443 的包,输出到 pcapxdpdump-ieth0 --rx-capture port443-w/tmp/https.pcapxdpdump 在驱动层(网卡收到包的瞬间)就开始抓,比 tcpdump 在协议栈抓包性能高 3-5 倍,丢包率从 30% 压到 < 1%。
抓 HTTPS 流量时,再配合tcplife看 TCP 连接生命周期:
tcplife-t# 按时间排序能直接看到每条 TCP 连接的持续时间、收发字节数、状态——比 Wireshark 的 Statistics → Conversations 快得多。
我现在生产环境的 eBPF 工具箱
实战中我整理了 4 个最高频的脚本,存在/usr/local/bin/下:
| 工具 | 用途 | 典型场景 |
|---|---|---|
bpftrace-connect-latency.bt | 连接延迟归因 | DNS 解析慢、Redis 握手慢 |
bpftrace-tcp-retrans.bt | TCP 重传统计 | 网络抖动、防火墙丢包 |
bpftrace-conntrack-usage.bt | conntrack 表监控 | K8s 节点丢包、短连接风暴 |
bpftrace-softirq.bt | 软中断 CPU 占用 | 网卡多队列不均、单机 PPS 打满 |
每个 30 行内,可以直接抄。完整代码我放在 GitHub Gist 上了。
写在最后
eBPF 不是取代 tcpdump,而是把"抓包"升级成"观测"。
tcpdump 告诉你"包是什么",eBPF 告诉你"包为什么这样、是谁发的、卡在哪一步"。
生产环境排障,时间是命。eBPF 把 1 小时的活压到 5 分钟,对我这种凌晨 3 点被叫醒的人来说,是救命稻草。
如果你之前没用过 eBPF,建议从bpftrace入手——语法像 awk,5 分钟就能写出第一个有用的脚本。装好后第一件事,跑下我上面那个connect_latency.bt,看看你的服务里有没有被忽视的连接慢点。
—— 凌晨 4 点补完 eBPF 工具箱的运维人