用 eBPF 做网络可观测性:原理与实战 原创
本文是 SRE 实战系列第 14 篇,聚焦网络这一层——用 eBPF 把"到底是谁把请求搞慢了"这件事从猜测变成证据。全文约 8500 字,包含内核挂载点原理、四类可观测信号、Cilium/Hubble、Pixie、Tetragon 的完整部署与配置、五步排障法、生产事故案例,以及 eBPF 上生产的六条红线。所有命令和配置都可以直接拿走用。
一、先说一个场景
凌晨两点,值班被叫醒。告警是这么写的:payment-api P99 延迟 1.2s,SLO 燃烧率 14x。
打开 Grafana,你看到的是:
- API 侧的 RED 指标:延迟确实高了,错误率没变
- 下游订单服务:自身指标全绿,CPU 30%,GC 正常
- 数据库:连接数正常,慢查询只有零星几条
- 节点:网络带宽没跑满
四个仪表盘全是绿的,但你明明知道有东西坏了。接下来你会做什么?
大概率是这个顺序:先怀疑下游 → 抓包 → 发现包里看不到 TLS 明文 → 找开发加日志 → 加完日志发布 → 复现不出来 → 等下一次再发生。整个过程 40 分钟到 2 小时,而问题可能只是某个节点的 conntrack 表满了,或者两个 Pod 之间的 MTU 不匹配导致大包被丢。
传统可观测性的盲区就在这里:
| 你能看到的 | 你看不到的 |
|---|---|
| 应用自己上报的指标 | 请求在内核里排队了多久 |
| 应用自己打的日志 | 连接被 NetworkPolicy 拒绝了几次 |
| 采样过的链路追踪 | TCP 重传、乱序、零窗口的真实分布 |
| 节点聚合的网络带宽 | 哪个 Pod 对在偷偷互相打流量 |
网络是唯一一个"应用层指标全绿但用户已经受影响"仍可能成立的层。 因为问题发生在应用和内核的交界处——而那块地方,恰恰是 SDK 和 agent 都站不上去的地方。
eBPF 站上去了。
二、eBPF 到底是什么,为什么这次能成
2.1 一句话原理
eBPF 是 Linux 内核里的一个带安全检查的虚拟机。你写一段小程序的字节码,内核在加载前用 verifier 逐条检查(不能死循环、不能越界访问、不能改变内核状态),检查通过后把它挂到一个事件挂载点上,事件触发时执行。
对可观测性而言,关键的三个性质:
- 零侵入:不需要改应用、不需要重启、不需要注入 sidecar
- 内核态可见:能看到 syscall、socket、TCP 状态机、数据包——这些应用永远看不到
- 代价可控:事件触发才执行,用 per-CPU 哈希和 ring buffer 传数据,不 copy 整个包
再说句更实在的:eBPF 不是新技术。它 2014 年就进了内核(3.18)。真正让它 2020 年后爆发的是三件事:
- BTF / CO-RE(内核 5.x):一次编译,跨内核版本运行,"Write Once Run Everywhere" 才成立,不用给每个内核版本编译一遍
- 生产级工具链成熟:Cilium、Falco、Tetragon、Pixie 把底层复杂度包掉了
- K8s 场景刚需:容器把"哪个 Pod 对应哪个进程"变得动态,iptables 那套静态规则彻底撑不住
一句给决策者的话:eBPF 的工程价值不在于"新技术很酷",而在于它把过去必须停机抓包、必须改代码埋点、必须复现一次才能拿到的信息,变成常态可采、可聚合、可告警的数据。这是 MTTR 从小时级降到分钟级的关键。
2.2 四个挂载点,覆盖你想要的一切
网上讲 eBPF 的文章爱把十几类 program type 全列一遍。实战里你只需要记住四类:
| 挂载点 | 挂在哪 | 能拿到什么 | 典型用途 |
|---|---|---|---|
| kprobe / kretprobe | 任意内核函数入口/返回 | 函数参数、返回值、时间戳 | 拦截 tcp_retransmit_skb、tcp_v4_connect、sock_sendmsg |
| tracepoint | 内核预置的稳定埋点 | 结构化的内核事件 | syscall 进出、sched 切换、net:net_dev_queue |
| XDP | 网卡驱动收包最早处 | 原始报文,可丢可改 | DDoS 清洗、流量采样、四层转发 |
| tc / cgroup | 协议栈出入口、cgroup 层级 | 报文 + cgroup/Pod 归属 | 按 Pod 打标签、限速、流量统计 |
做观测 90% 用前两个(kprobe + tracepoint),因为它们不改包、不改状态,只读。 XDP 和 tc 是数据面程序,改的是流量本身,风险高一个量级——先别碰。
还有两个近两年越来越重要的:
- uprobe:挂到用户态函数上。这是"不埋点也能看协议"的关键——OpenSSL 的
SSL_read/SSL_write就是这么被抓到的,TLS 加密也拦不住(因为明文在加解密函数里) - sockmap / sockops:拿到内核里 socket → cgroup → Pod 的映射关系,这是把流量数据打上 Kubernetes 标签的前提
2.3 一个 map 决定一切:数据怎么从内核出来
eBPF 程序本身不能打印、不能写文件。它只能把数据写进 map(内核和用户态共享的内存结构),由用户态程序(agent)去读。
这条链路决定了所有 eBPF 工具的性能形态:
事件触发 → eBPF 程序(内核态,纳秒级)
→ 聚合(per-CPU hash map,先累加,不全量上报)
→ 输出(ring buffer / perf buffer,按批传)
→ 用户态 agent(Go/Rust,加 K8s 标签、聚合、导出)
→ Prometheus / OTLP / 对象存储两个必须知道的工程细节:
- 上下文切换是主要开销,不是 eBPF 指令本身。所以成熟的工具都是"内核里聚合,用户态只读聚合结果"。你在设计自己的采集器时,千万别把每个包都往用户态扔。
- ring buffer 会满。满了就丢事件,指标就失真。生产上必须监控"丢事件数"这个指标——这是很多自研 eBPF 工具上线后数据对不上的第一原因。
三、四类信号:网络可观测性该采什么
不要一见 eBPF 就想"我把所有 syscall 都采下来"。那是自找麻烦。SRE 需要的网络可观测性只有四类信号,对应四种回答问题的能力:
| 信号 | 回答什么问题 | 对应指标 | 挂载点 |
|---|---|---|---|
| 流量拓扑 | 谁在跟谁说话?跨了哪些命名空间? | 五元组 + Pod/Service 归属 | tc + sockops |
| 请求质量(L7) | 谁的接口慢?状态码多少? | 请求数、延迟分布、错误率 | uprobe / socket filter / 协议解析 |
| TCP 健康度 | 网络本身是不是有问题? | 重传率、RST 数、连接建立耗时、零窗口 | kprobe(tcp_* 函数) |
| 策略与丢弃 | 谁被拒绝了?为什么? | drop 原因、NetworkPolicy 拒绝次数、conntrack 满 | tracepoint(skb:kfree_skb 等) |
下面逐个说清楚:这些指标在真实排障里怎么用。
3.1 TCP 健康度:最快能定位"是不是网络"的一组指标
这是我最推荐团队第一件上线的事。原因:它不依赖任何应用改造,指标稳定,且能一刀切分"网络问题 / 应用问题"。
四个指标,从 kprobe 直接拿:
重传率 = tcp_retransmit_skb 次数 / tcp_sendmsg 次数
RST 率 = tcp_send_reset + tcp_receive_reset 次数 / 新建连接数
建连耗时 = tcp_v4_connect 返回时的耗时分布(P50/P99)
零窗口时长 = tcp_sendmsg 中因窗口为 0 而阻塞的累计时间判读口诀:
- 重传率 > 1% → 网络路径或对端有问题,先别怀疑应用
- RST 集中在某个对端 → 对端主动拒绝,看是不是连接池/最大连接数/策略问题
- 建连 P99 高但重传正常 → 看是不是 DNS 或 SYN 排队(
somaxconn/tcp_max_syn_backlog) - 零窗口多 → 对端应用读得慢,是应用问题,但证据在 TCP 层
实战中最有价值的一次定位:支付服务偶发超时。应用指标全绿,节点指标全绿。上了 TCP 指标后发现某两个节点间的重传率稳定在 3%,其他节点对都是 0.01%。最后查到是其中一台物理机的网卡固件版本不一致,导致 GRO/LRO 行为差异,MTU 分片异常。这类问题用传统手段要抓包抓一整晚。
3.2 流量拓扑:把"未知的东西"变成"已知的东西"
服务之间的依赖图,理论上你从代码、从 K8s Service 清单里能画出来。实践中画不对,因为:
- 有直连 Pod IP 跳过 Service 的历史遗留调用
- 有跨命名空间、跨集群的访问没人登记
- 有定时任务、批处理在半夜打流量,从来不在架构图上
eBPF 采的流量拓扑是事实而不是设计。这也是 Hubble 最有用的部分:
# 看 payment 命名空间出去的所有流量,按对端聚合
hubble observe --namespace production --pod payment-api/* \
--output jsonpb | jq -r '.flow | "\(.source.pod_name) -> \(.destination.pod_name // .destination.ip) \(.l4.TCP.destination_port)"' | sort | uniq -c | sort -rn排障时的用法:P99 抖动,先看这次的调用关系和平时比有没有多出陌生的边。经常能发现"某服务最近改了个配置,开始直连另一个服务的 Pod IP"这种事。
治理时的用法:把它做成"每周 diff",新出现的边自动提 MR 到架构文档。这比让人肉更新架构图有用一百倍。
3.3 L7 请求质量:不埋点也能知道哪个接口慢
这是最多人问的问题:TLS 加密了,eBPF 还能看到 HTTP 吗?
能。两条路:
- 明文 HTTP / gRPC:直接在 socket 层解析协议(Hubble、Pixie 都做)
- TLS:用 uprobe 挂到用户态的 TLS 库函数(OpenSSL 的
SSL_read/SSL_write、BoringSSL、GnuTLS),在加密前/解密后读缓冲区。明文就在这里
所以前提是:应用用的是动态链接的、被支持的那几个 TLS 库。这引出全场最重要的一条避坑:
- 静态链接(Go 默认)→ uprobe 挂不上
SSL_*,只能靠 Go 自己的crypto/tls符号,工具要专门支持 - 用了自研/私有 TLS 实现(部分 Rust/Java JSSE 场景)→ 看不到
- mTLS 走 sidecar(Istio)→ 最好是抓 sidecar 的出入口,不是抓应用
这就是为什么 L7 观测必须"先验证再信任"——部署完先跑一遍这条命令,看看你的核心服务到底有没有被解析出来:
# 验证 Pixie 是否解析到了你的服务
px get http_events -n production | head -20
# 验证 Hubble
hubble observe --namespace production --protocol http --last 20如果列表是空的,别急着写告警规则——先用 curl 打一次确认采集链路通了。
四、三种落地形态:选哪个
eBPF 网络可观测性现在有三条主流路线,选错了会白折腾半年。
| 形态 | 代表 | 采集粒度 | 部署成本 | 适合谁 |
|---|---|---|---|---|
| CNI 自带 | Cilium + Hubble | L3/L4 完整,L7 需开 L7 策略或解析 | 零(已经在用 Cilium) | 已经用 Cilium 的集群,首选 |
| 集群级 agent | Pixie | L3/L4/L7 全自动,含 TLS | 每节点一个 DaemonSet | 需要免埋点链路追踪、遗留系统多 |
| 运行时安全 | Tetragon | L3/L4 + 进程执行行为 | 每节点一个 DaemonSet | 安全与观测合一,Tracepoint 灵活 |
选型建议(按优先级):
- 你已经在用 Cilium → 直接开 Hubble,成本最低,覆盖 80% 需求。别引入第二套
- 没用 Cilium,但要 L7 免埋点 → Pixie,注意它的存储模型是"短保留期 + 自研查询引擎",不适合当长期指标后端
- 安全团队主导、需要进程级审计 → Tetragon(Falco 的底层也是 eBPF,但 Tetragon 的 tracepoint 表达能力和性能更好)
- 只是想看看流量拓扑和 TCP 指标 → 用
bpftrace写十行脚本就够了,不用上平台
下面给三套可直接用的配置。
4.1 Cilium + Hubble:从开启到可用
如果 Cilium 已经装好,开启就是一条 hem upgrade:
helm upgrade cilium cilium/cilium -n kube-system --reuse-values \
--set hubble.enabled=true \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http}" \
--set hubble.metrics.serviceMonitor.enabled=true这里有个坑:hubble.metrics.enabled 里的 http 依赖 L7 解析,会带来额外 CPU 开销(通常 3%-8%,取决于 QPS)。建议先只开 {tcp,drop,dns},跑一周看节点 CPU 增长,再决定要不要开 http。
加上 serviceMonitor.enabled=true 后,Hubble 的指标直接进 Prometheus。下面是四条真正该配的告警规则(不是从文档抄的示例):
groups:
- name: hubble-network
rules:
# 1. 策略丢弃率突增:最常见的"服务突然不通"
- alert: NetworkPolicyDropsHigh
expr: |
sum by (destination_workload, destination_namespace) (
rate(hubble_drop_total{reason="Policy denied"}[5m])
) > 1
for: 5m
labels:
severity: P2
annotations:
summary: "{{ $labels.destination_workload }} 被策略丢弃 >1/s"
# 2. DNS 失败率:比应用报错更早发现
- alert: DNSFailureRateHigh
expr: |
sum(rate(hubble_dns_responses_total{rcode!="No Error"}[5m]))
/ sum(rate(hubble_dns_responses_total[5m])) > 0.02
for: 10m
labels:
severity: P2
# 3. TCP 层面异常:对端 RST 突增
- alert: TCPResetSpike
expr: |
sum by (destination_workload) (rate(hubble_tcp_flags_total{flag="RST"}[5m])) > 5
for: 5m
labels:
severity: P3
# 4. 链路健康:某对服务的 flow 在 5 分钟内掉到 0(服务"静默消失")
- alert: InterServiceTrafficSilent
expr: |
(sum by (source_workload, destination_workload) (rate(hubble_flows_processed_total[1h] offset 1h)) > 10)
unless
(sum by (source_workload, destination_workload) (rate(hubble_flows_processed_total[5m])) > 0)
for: 15m
labels:
severity: P2第四条是我个人最推荐的:"本来有流量的边突然没流量了"是最容易被漏掉、又最容易查的故障形态。 用 offset 和 unless 组合就能做出来,比任何复杂的异常检测算法都实用。
4.2 Pixie:免埋点 L7,但先算清存储账
# 安装(需要集群 admin)
helm install pixie pixie-operator/pixie-operator-chart \
--namespace pl --create-namespace --set deployKey=<your-key>
# 检查每个节点的采集状态
px get viziers
# 看某个 namespace 的 HTTP 黄金指标
px run -n production http_events --start_time="-1h"Pixie 的三个必须知道的事实(很多团队踩过):
- 它是"调试工具"不是"指标后端"。默认数据只在内存里保留几十分钟(PEM 节点本地),要长期保留必须走 export 到 Kafka/OTLP
- CPU 开销和 QPS 线性相关。万级 QPS 的集群,每节点给 1-2 核是底线,别按"零开销"的预期规划
- 它的 eBPF 程序要匹配内核版本,内核太新(或太老、定制化发行版)可能降级成部分功能。上线前在预发节点
px get viziers看是否 Ready
4.3 bpftrace:不装平台也能解决当下这个问题
如果你现在的需求只是"今晚这次故障,我想知道某个进程到底在干嘛",bpftrace 十行脚本就能救火,不用等任何平台审批。
场景一:看谁在频繁重连
# 按进程统计 TCP 建连次数(10 秒窗口)
bpftrace -e '
tracepoint:tcp:tcp_connect {
@[comm, pid] = count();
}
interval:s:10 { print(@); clear(@); }
'场景二:哪个进程在丢包(kfree_skb 的 drop 原因)
bpftrace -e '
tracepoint:skb:kfree_skb {
@[args->reason ? str(args->reason) : "unknown", comm] = count();
}
'场景三:socket 发送阻塞在哪(定位零窗口/背压)
bpftrace -e '
kprobe:tcp_sendmsg { @start[tid] = nsecs; }
kretprobe:tcp_sendmsg /@start[tid]/ {
$d = nsecs - @start[tid];
@us = hist($d / 1000);
delete(@start[tid]);
}
'用 bpftrace 的三条纪律:
- 一定要加
interval或超时,Ctrl-C才是你唯一的安全阀 - 生产上避免
kprobe:sys_*这种超高热点函数长时间挂着,用count()聚合不要printf - 手工脚本是排障手段,不是监控手段。跑完就停,别拿它当长期采集器
五、五步排障法:把 eBPF 数据串成决策链
工具会用了,关键是怎么在压力下正确使用。下面是我在值班时执行的固定顺序。顺序不能乱,因为每一步都在排除一类可能性。
第 1 步:先分叉——是网络问题还是应用问题(2 分钟)
看三个数字:重传率、RST 率、建连 P99。
- 三个都正常 → 大概率应用问题,去第 5 步
- 有一个异常 → 网络问题,继续第 2 步
这一步的价值:避免"应用同学和网络同学互相甩锅两小时"。有数据,谁都不冤。
第 2 步:锁定范围——是全局、单节点、还是单对服务(3 分钟)
用 Hubble 按节点、按 workload 维度聚合同一组指标。三种结果对应完全不同的动作:
| 范围 | 典型根因 | 下一步 |
|---|---|---|
| 全局 | CNI 组件异常、DNS 故障 | 查 CoreDNS、Cilium agent 状态 |
| 单节点 | 网卡/固件/内核参数、conntrack 满 | 查该节点 dmesg、nf_conntrack_count |
| 单对服务 | 策略、连接池、MTU | 去第 3 步 |
第 3 步:看策略与丢弃——是不是"根本没连上"(3 分钟)
# 按丢弃原因聚合,一眼看出是策略拒绝还是协议栈丢
hubble observe --namespace production --verdict DROPPED --last 1000 \
-o jsonpb | jq -r '.flow.drop_reason_desc' | sort | uniq -c | sort -rn常见的丢弃原因和它们的真实含义:
Policy denied→ NetworkPolicy 拒绝,去看策略变更历史No route to host→ 路由/节点网络问题Connection reset by peer→ 对端拒绝,看对端有没有活stale or unreplied connection→ conntrack 表条目过期/满,这是 K8s 里最容易被误判成"应用 bug"的故障Packet is BIGGER than MTU→ MTU 不匹配(跨云、跨隧道场景常见)
第 4 步:下钻到连接——看具体某条流的时间线(5 分钟)
到这一步,范围已经收窄到一对服务或一个节点。现在看单条流的完整生命周期:
# 实时看一对 Pod 之间的所有流事件(含 TCP flags)
hubble observe --from-pod production/payment-api \
--to-pod production/order-svc --protocol tcp -f重点看:建连是否耗时、有没有中途 RST、FIN 是谁先发的、有没有重传。这一步能把"偶发"变成"可解释"。
第 5 步:如果网络全绿,回到应用(剩下的时间)
网络侧数据全绿且干净——这是个好消息,你已经用数据把一半的可能性排除了。接下来用 L7 数据:
- 请求延迟分布是按接口分化的,还是全接口一起抬升?
- 只有某个下游调用慢,还是服务自身处理就慢?
- 状态码分布有没有变化(4xx 突增往往是对端行为变了)
这时候再看 Trace(第 12 篇的 OTel)和日志,效率比一开始就翻日志高一个数量级。
这个五步法的本质是"排除法的顺序优化":把"最容易验证、信息量最大、最不依赖其他团队"的判断放前面。eBPF 数据恰好全部满足这三个条件。
六、一个真实案例:从 P99 抖动到 conntrack 满
脱敏后复盘一次线上事故,因为它同时踩中了三个"传统手段查不出来"的点。
6.1 现象
- 服务 A 调用服务 B,偶发超时(约 0.3% 请求),集中在每天 19:00-21:00
- 应用侧:A 的重试日志有,B 的入口指标正常(说明请求到了 B 的处理逻辑之前就出问题了)
- 节点侧:CPU/内存/带宽全部正常
- 网络侧老监控:只有带宽和丢包(都是 0)
持续三周,每次都要人肉排查一晚上。
6.2 eBPF 数据给出的证据
上了 Hubble + TCP 指标后,第一周就定位了:
# 按丢弃原因聚合(19:30 抓的)
$ hubble observe --verdict DROPPED --last 5000 -o jsonpb \
| jq -r '.flow.drop_reason_desc' | sort | uniq -c | sort -rn
3821 stale or unreplied connection
214 Policy denied
# 同时看节点的 conntrack 使用率
$ kubectl debug node/ip-10-0-3-17 -it --image=nicolaka/netshoot -- \
cat /proc/sys/net/netfilter/nf_conntrack_count
65536
$ kubectl debug ... -- cat /proc/sys/net/netfilter/nf_conntrack_max
65536nf_conntrack_count == nf_conntrack_max:conntrack 表满了。
6.3 根因链
- 每天 19:00 起,一批定时批量任务启动,向外网大量短连接
- 每个短连接在 conntrack 表里占一条,且默认
nf_conntrack_tcp_timeout_time_wait=120s - 表满后,新连接直接被丢,丢弃原因就是
stale or unreplied connection - 服务 A 的重试让情况更糟——重试的连接也进不去表
为什么传统手段查不出来? 因为这个故障没有任何"指标超标":带宽正常(短连接流量很小)、CPU 正常(丢包发生在协议栈更早的位置)、应用监控正常(请求根本没到应用)。只有内核里的 drop reason 这一个地方有真相。
6.4 修复与验证
# 短期:扩大表 + 缩短 TIME_WAIT(滚动执行,每节点)
sysctl -w net.netfilter.nf_conntrack_max=262144
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
# 长期:批量任务改成复用连接(HTTP keep-alive),并加并发上限验证方式不是"看告警消失",而是加一条前瞻性告警,让这个故障永远不会再是"新问题":
- alert: ConntrackTableNearFull
expr: |
node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8
for: 10m
labels:
severity: P2
annotations:
summary: "节点 {{ $labels.instance }} conntrack 使用率 > 80%,新连接将被丢弃"6.5 这个案例的三条可复用结论
- "全部指标正常"不等于"没有问题"——先确认你的指标采集点是否覆盖内核态
- drop reason 是 eBPF 给网络排查带来的最大增量,比流量图更值钱
- 修故障的收尾动作是加一条前瞻指标,否则同类故障会在别的节点重演
七、告警与 SLO:把网络数据接进现有体系
采到了数据,怎么进告警和 SLO?三层结构,不要混:
| 层级 | 用在哪 | 指标示例 | 处理方式 |
|---|---|---|---|
| SLO 层 | 用户视角 | 接口成功率、P99 < 300ms | 燃烧率告警(第 3 篇方法) |
| 诊断层 | 定位 | 重传率、RST 率、drop reason 分布 | 面板 + 阈值告警(P2/P3) |
| 容量层 | 预防 | conntrack 使用率、eBPF map 使用率、ring buffer 丢事件数 | 前瞻性告警(P2) |
关键原则:网络指标永远不进 SLO,但永远是 SLO 告警的一级排查入口。
原因很简单:SLO 的定义是"用户体验",而 TCP 重传率不是用户体验。但 SLO 燃烧率告警一响,第一件事就该去看网络指标——这就是本文第五节五步法存在的意义。
给告警分级时(第 13 篇的 P0-P4),网络类告警基本都落在 P2/P3,唯一例外是 "全集群 DNS 失败" 和 "核心服务间流量归零"——这两个是 P1,因为它们几乎必然是用户可感知的。
避坑:不要给 eBPF 采集器自己做 P0 告警。 采集器挂了不等于业务挂了。它的告警应该是 "observability collector down",级别 P3,让它自己安静地坏、安静地被修。
八、六条生产红线
这一节是我最想让你带走的。eBPF 很安全,但不安全的使用方式很危险。
红线一:eBPF 程序在内核态运行,panic 就是整节点挂掉
verifier 保证不会死循环、不会越界,但不代表逻辑正确。一个 map 里写脏数据、一个错误的偏移,都可能让整个节点内核异常。
纪律:
- 优先用成熟项目(Cilium/Pixie/Tetragon),别自研核心采集
- 自研必过三关:预发跑 72 小时 → 灰度 1% 节点 → 生产逐步放量
- 上线前必须确认回滚路径:eBPF 程序卸载是否干净(
bpftool prog show、bpftool map show看残留)
红线二:内核版本与 BTF 是硬门槛
CO-RE 依赖内核 BTF 信息。老内核(< 4.18)或裁掉了 BTF 的发行版,工具会直接不工作或降级。
上线前必查一条命令:
ls /sys/kernel/btf/vmlinux && uname -r没有这个文件,先解决内核问题,别怪工具。
红线三:观测本身会成为新的资源消耗者
三块开销必须量化后再上线:
| 开销项 | 量级 | 怎么控 |
|---|---|---|
| CPU | 全量 L7 解析在万级 QPS 下每节点 1-2 核 | 只对关键命名空间开 L7,或采样 |
| 内存 | map 大小 × 节点数,容易忽略 | 显式设定 map max_entries,不设默认值 |
| 网络/存储 | 事件上报占带宽,长期存储占容量 | 内核聚合 + 降采样后再导出 |
上线第一个月必看三个指标:采集器自身 CPU%、ring buffer 丢事件率、导出到后端的字节数。
红线四:ring buffer 丢了事件,你的数据就是错的
丢事件不会报错,只会让指标偏低。这类"静默失真"最危险。
纪律:把"丢事件数 / 总事件数"做成一个指标,> 0.1% 就告警(给自己的采集器告警,P3 级别)。
红线五:权限与合规——它能看到一切
eBPF agent 需要 CAP_BPF(或 CAP_SYS_ADMIN)、CAP_NET_ADMIN,通常还要 hostPID、hostNetwork。这意味着它能看到节点上所有进程的 syscall、所有明文流量。
- 安全团队一定会问:数据存哪、谁能查、保留多久
- uprobe 抓 TLS 明文这件事,在部分合规场景(金融、医疗)需要走审批
- 建议:默认只采元数据(五元组、状态码、延迟),报文内容默认不落盘
红线六:别把它当抓包工具的替代品
eBPF 擅长聚合与统计,抓包(tcpdump)擅长精确定位单条流的每个字节。两者是互补关系。
分工:先用 eBPF 找到"是哪一对服务、哪一类问题",再用 tcpdump/wireshark 看具体某条流。反过来用会非常低效——eBPF 抓全量报文,等于用错工具。
九、起步路径:三周能落地什么
不要一上来就规划"全链路 eBPF 平台"。按投入产出排序,三周能做出一套真正有人用的东西:
第 1 周:TCP 健康度(投入最小,收益最大)
- 确认 Cilium 版本 / 内核版本(
hubble status、uname -r) - 开 Hubble,只开
{tcp,drop,dns}三个指标,接 Prometheus - 做一到两个面板:TCP 重传率(按节点/按服务对)、drop reason 分布
- 值班手册加一页:TCP 指标判读口诀(本文 3.1)
第 2 周:流量拓扑 + 丢弃告警
- 跑一次全命名空间流量快照,产出依赖图,和架构文档 diff
- 配 3 条告警:Policy denied 突增、DNS 失败率、服务间流量归零
- 给 conntrack 使用率加前瞻告警(本文案例的直接产出)
第 3 周:L7 验证 + 成本核算
- 只对 1-2 个核心命名空间开 L7 解析
px get http_events/hubble observe --protocol http验证采集生效- 记录节点 CPU 增量、丢事件率,写成"要不要全量开"的决策文档
- 决定继续扩,还是维持"核心服务 + 按需排障"
明确不做的事(前三个月):
- 不自研 eBPF 采集器
- 不开全量 L7 + 全量 SSL 解析
- 不把网络指标写进 SLO
- 不上 XDP 做数据面改造
十、结语:eBPF 补的是"证据",不是"指标"
回到开头那个凌晨两点的场景。用了这套东西之后,变化不在于你的仪表盘变多了,而在于:
你能在 5 分钟内说出"这次变慢是因为节点 X 的 conntrack 满了,导致 A→B 的新连接被丢",而不是"我们怀疑是网络问题,正在排查"。
这是从描述现象到提供证据的跃迁。指标告诉你"有问题",eBPF 告诉你"问题在哪、为什么"。
三句话总结全文:
- 四个信号(拓扑、L7、TCP、丢弃)覆盖了网络可观测性 95% 的排障需求,先做 TCP 和丢弃,投入产出最高
- 五步排障法(分叉 → 定范围 → 看策略 → 下钻连接 → 回应用)的价值在顺序——把最便宜、信息量最大的判断放前面
- 六条红线的核心是"别让观测成为新的故障源"——量化开销、控制爆炸半径、给采集器自己的健康兜底
最后一条能立刻做的起步动作:登录任意一个节点,跑一遍 bpftrace -l 'tracepoint:tcp:*'。 如果命令存在,你的集群已经具备第一天就能出价值的能力——今晚的值班,就多了一个不用抓包的选项。
附:可直接取用的清单
上线清单(第 1-3 周)
- 确认内核 BTF 可用:
ls /sys/kernel/btf/vmlinux - Cilium 开 Hubble,指标只开
{tcp,drop,dns} - 面板:TCP 重传率、RST 率、建连 P99、drop reason 分布
- 告警:Policy denied 突增、DNS 失败率 > 2%、服务间流量归零、conntrack > 80%
- L7 只对核心命名空间开,并验证采集生效
- 采集器自身指标:CPU%、ring buffer 丢事件率、导出字节数
值班清单(排障时按顺序)
- 重传率 / RST 率 / 建连 P99 → 分叉(网络 or 应用)
- 按节点 / workload 聚合 → 定范围
hubble observe --verdict DROPPED→ 看 drop reason- 单条流时间线 → 定连接层细节
- 网络全绿 → 转 L7 + Trace + 日志
避坑清单
- 不自研核心采集器
- 不把网络指标写进 SLO
- 不给采集器自己配 P0
- 不默认落盘报文内容(合规)
- map 显式设上限,别用默认值
- 内核版本 / TLS 库类型先验证再规划
本文是 SRE 实战系列第 14 篇。第 7 篇给了 K8s 可观测性全景图,指出 eBPF 是"新范式"但没展开;第 12 篇统一了采集层(OTel),解决的是"应用怎么上报";本文补齐了剩下那块——应用上报不了的地方(内核、协议栈、加密前)怎么拿到数据。三篇连起来,可观测性从应用层贯通到网络层。
系列回顾:SLO 体系(3-5、10 篇)解决了"该盯什么",告警治理与分级(6、8、13 篇)解决了"怎么叫对人",OTel(12 篇)解决了"怎么采",本文解决了"采不到的地方怎么办"。
下一篇预告:Prometheus 告警规则的工程化——规则仓库结构、测试与灰度验证。