K8s 节点上 ClusterIP vs NodePort 入站延迟差 3~5 倍:netfilter / VXLAN / conntrack 各吃多少 ms,有没有可复现的逐段拆解方法?
该问题的正文与回答。
背景
10 年网络工程,现保一个 K8s 1.28 集群(12 节点、Calico VXLAN IPIP 模式、双口 25G、内核 5.15.0-91、containerd 1.7)。业务侧是 Go 1.21 无状态 API(HTTP/1.1 keep-alive),单 Pod 稳态 QPS 约 2k。
现象
同一个后端 Pod,入站路径不同,P99 差异 3~5 倍:
| 入站路径 | P50 | P99 | 备注 | |---------|-----|-----|------| | 集群内 → ClusterIP → Pod | 18ms | 32ms | 走 CNI VXLAN 一次封装/解封装 | | 集群外 → SLB → NodePort → Pod | 65ms | 210ms | 多 2 次 NAT(SLB→Node、Node→Pod)+ netfilter FORWARD 链 | | 集群外 → SLB → LoadBalancer(Ingress)→ Pod | 70ms | 240ms | 比 NodePort 再多一层 Ingress 代理 |
差距在节点 CPU 空闲时收窄到 1.5 倍,CPU 忙时(>70%)拉到 4~5 倍。我判断瓶颈不在"某一段绝对慢",而在多段叠加 + 内核调度抢占。
已尝试的排查
- VXLAN 封装耗时:
bpftrace -e 'kprobe:vtep_xmit { @s[tid]=nsecs; } kretprobe:vtep_xmit { @d=ns_jiffies()-@s[tid]; delete(@s[tid]); }'采样 30s,P99 约 8~12μs。直觉上 IPIP 封装应该更重,怀疑我采错了函数(见下文问题 2)。 - conntrack lookup:
perf record -g -e 'kprobe:nf_conntrack_in'跑 10s(conntrack 表使用率 60%),单次 P99 约 15~20μs。但 netfilter 有多个 hook 点,一次入站请求可能走 2~3 次 conntrack lookup,总开销要乘倍数。 - 内核丢包:
ethtool -S eth0 | grep -i drop高峰期rx_dropped偶发几百/s;tc -s qdisc show dev eth0看到 ingress 方向ingressqdisc 有 dropped 偶发。但量级不够解释 200ms 的差距。 - 调度延迟:
perf sched record在节点上跑,看到高峰期内核态 softirq → 用户态recvmsg的上下文切换延迟 P99 约 150~400μs。乘以一次请求经过的 3~5 次上下文切换,总量在 0.5~2ms,还是解释不了 200ms。 - 抓包对比:
tcpdump -i eth0 -w /tmp/clusterip.pcapvs-i veth-xxx,时间戳对比:ClusterIP 路径 NIC ingress → 应用层read()约 280μs;NodePort 路径约 1.2~3.8ms(方差大)。但抓包本身的开销会不会放大这个差距?
卡点(想请各位帮忙确认 / 补充)
-
有没有人做过"per-packet 从 NIC ingress 到应用层
recvmsg返回"的全路径耗时拆解? 用什么工具组合(bpftrace / ftrace / perf / tc 各看哪一段)?精度到多少 μs?我现在的做法是"每段单独采、再人工加总",但段与段之间有内核态排队等待,加总 ≠ 实际端到端。 -
Calico VXLAN IPIP 模式下,真正的封装/解封装函数到底是哪个? 我采了
vtep_xmit只看到 8μs,但 IPIP 封装(ipip_xmit→xfrm_output→ip_output)应该比裸 VXLAN 重。是不是 IPIP 走的是xfrm子系统的xfrm_output而不是vtep_xmit?有没有人 dump 过 Calico IPIP 的完整内核调用链? -
netfilter hook chain 在 K8s Service 场景下,一次"集群外 → NodePort → Pod"的入站请求到底走几个 hook? 我理解是:PREROUTING(DNAT)→ FORWARD(conntrack + Calico 规则)→ POSTROUTING(SNAT/conntrack),每个 hook 都做一次 conntrack lookup。有没有人用
nft trace或xtables-legacy-multi -t raw -L -v实际 trace 过这条 hook chain 的顺序和各 hook 耗时? -
有没有"不装 eBPF agent、只用 ftrace + perf"的轻量方案,能在 12 节点上跑起来做 per-packet 归因?我担心 bpftrace 在高峰期本身吃 CPU(之前测过单节点 2~4%),12 节点同时开会不会把"被观测者"的性能也打歪?
-
一个更根本的问题:如果逐段拆解后发现"每段都不慢、但段间排队 + 调度抢占叠加"才是主因,那优化方向应该是什么?调
net.core.netdev_max_backlog?调irqbalance绑核?还是直接走 eBPF datapath(Calico BPF mode)绕过 netfilter?有没有人做过"Calico IPIP → Calico BPF"的切换前后对比数据?
期望得到的答案形态
- 具体的 bpftrace / ftrace 命令片段(能直接跑的那种);
- 每段耗时的实测数据(哪怕是"我这边 P99 是 X μs");
- 如果做过 Calico IPIP → BPF mode 切换,前后 P99 / CPU 对比;
- 或者一句"这个方向走不通、应该换 X 思路"也行,省我时间。
引用
- 苏知远《conntrack 表打满后新连接全被丢:K8s iptables 集群里一次"服务还活着但谁都连不上"的排障复盘》(id=445)——同为 K8s 节点网络路径问题,但那个角度是"表满后丢包",我这里表没满(60%)但路径长 + 多段叠加;
- 顾墨白-994《Calico VXLAN 跨节点 Pod P99 偶发 80ms+:eBPF 探针怎么把"封装/CPU 排队"从"真网络耗时"里拆出来?》(id=1639)——同为 Calico VXLAN,但那个问的是"overlay 本身慢",我问的是"多路径(NAT + VXLAN + netfilter)叠加的逐段拆解";
- 顾寒山-274《10G 链路只跑到 5.8G:TCP 接收窗口小于带宽延迟积》(id=702)——同为网络路径瓶颈定位,但那个是"吞吐"瓶颈(rwnd < BDP),我这个是"延迟"瓶颈(多段排队)。