本文记录了一次 Kubernetes 集群内 Pod 间网络极度缓慢 的排障过程,并借机系统性梳理了几种常见的 K8s 网络模式(VXLAN Overlay、host-gw、Calico IPIP/BGP、eBPF 等)。
核心经历:
- 发现:同一个集群内 Pod 访问后端服务极慢,
curl下载只有几百 KB/s; - 对比:宿主机之间 iperf3 有10 Gbit/s,但 Pod → Pod 只有几 Mbit/s;
- 定位:集群使用的是 flannel VXLAN(UDP 8472)覆盖网络,叠加物理网卡存在
pkts rx err,导致叠加网络性能雪崩; - 解决:将 flannel 的 Backend 从
vxlan切换为host-gw路由直连模式,Pod → Pod 带宽直接提升到 10 Gbit/s+。
在复盘过程中,本文也会顺带:
- 解释 VXLAN / host-gw / IPIP / eBPF 等几种常见模式的原理;
- 用多张 Mermaid
flowchart图画出跨节点 Pod → Pod 的流量路径; - 给出一些选型与排障时的 checklist。
最终把 Pod ↔ Pod 带宽从 1–2 Mbit/s 拉回到了 10 Gbit/s,业务侧的 curl 下载时间也恢复正常。这篇文章记录整个排查过程和关键结论,希望对同样在折腾 K8s 网络的同学有参考价值。
1. 问题起源:内网访问一个大文件,部分容器偶发卡死
集群环境:
- K8s 发行版:RKE2
- CNI:Canal(flannel + calico),flannel 后端默认为 VXLAN
- 节点示意:
k8sn1:10.88.88.74k8sn2:10.88.88.75- Pod 网段:
10.42.0.0/16 - Pod A(算法 Pod
icdm-design-pcb-ring-tool):10.42.4.198(在k8sn2) - Pod B(Java 后端
icdm-backend-deployment):10.42.3.174(在k8sn1)
问题现象:
算法端访问 Java 后端下载一个大文件,走的是集群内 HTTP,文件上传/下载严重超时:
curl -o /dev/null -w 'time_total=%{time_total} size=%{size_download}\n' \
"http://icdm-backend-deployment.default.svc.cluster.local:8002/files/02b6eacf-7213-4f11-9693-84e9837e87x2/download" \
-H "Authorization: Bearer <token>"表现是:
- time_total 很长,size_download 对应的平均速度只有 200KB/s 左右;
肉眼可见严重不符合内网访问的预期。
2. 第一波排查:Pod ↔ Pod 带宽明显异常
直觉上怀疑是 网络带宽 / 丢包 / 抖动,于是先上 iperf3。
在 Java 后端 Pod(icdm-backend-deployment)里起服务:
# Pod A: 10.42.3.174
iperf3 -s在算法 Pod(icdm-design-pcb-ring-tool)里做客户端:
# Pod B: 10.42.4.198
iperf3 -c 10.42.3.174最开始的结果大概是:
[ 5] 0.00-10.00 sec 2.00 MBytes 1.68 Mbits/sec 402 sender
[ 5] 0.00-10.00 sec 1.88 MBytes 1.57 Mbits/sec receiver特点:
- 只跑到 1–2 Mbit/s
- Retr(重传)次数巨大(几百次)
- 这还只是单向 TCP 测试,和真实业务的 curl 表现高度一致
结论:问题不是只在代码层、代理层、 HTTP 层,而是 Pod ↔ Pod 的底层网络链路就已经不对劲。
3. Node ↔ Node 带宽却很健康:矛头指向 CNI / overlay
为了排除物理网络问题,在两台 worker 节点(宿主机)之间做了对比。
- k8sn1:
10.88.88.74 - k8sn2:
10.88.88.75
在 k8sn1 上:
iperf3 -s在 k8sn2 上:
iperf3 -c 10.88.88.74
iperf3 -c 10.88.88.74 -R结果是:
# 正向
[ 5] 0.00-10.00 sec 19.9 GBytes 17.1 Gbits/sec sender
[ 5] 0.00-10.04 sec 19.9 GBytes 17.0 Gbits/sec receiver
# 反向
[ 5] 0.00-10.04 sec 17.3 GBytes 14.8 Gbits/sec sender
[ 5] 0.00-10.00 sec 17.3 GBytes 14.8 Gbits/sec receiver这就是一个相当健康的万兆内网水平。
结论:物理机之间的网络没问题,问题集中在「Pod ↔ Pod」这一层,大概率是 CNI(flannel/calico 之类)或者 overlay 隧道方面的问题。
4. 看看 CNI 与路由:flannel + calico(canal 模式)+ VXLAN
集群是 RKE2,CNI 用的是 canal(flannel + calico)。
先看下 CNI 相关配置:
kubectl -n kube-system get cm rke2-canal-config -o yaml关键部分:
net-conf.json: |
{
"Network": "10.42.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
...
veth_mtu: "1450"flannel 日志也能看到:
kubectl -n kube-system logs ds/rke2-canal -c kube-flannel | egrep -i 'Backend type'
# 输出类似:
Found network config - Backend type: vxlan在 worker 节点上看接口:
ip link | egrep 'flannel|cali'
ip addr show flannel.1以及路由表:
ip route例如(k8sn1):
10.42.4.0/24 via 10.42.4.0 dev flannel.1 onlink
...
10.88.88.0/24 dev ens160 proto kernel scope link src 10.88.88.74这个结构说明:
- Pod 网段:
10.42.0.0/16 - 跨节点流量:通过
flannel.1这个 VXLAN 接口封装,再从ens160发出去 - 每个 Pod 网口是
caliXXXX,对应 calico
此时的拓扑可以理解为:
flowchart LR
subgraph Node2[k8sn2 节点<br/>10.88.88.75]
subgraph PodA[Pod A<br/>10.42.4.198]
AApp[应用容器]
end
AApp --> ACali[caliXXXX<br/>veth]
ACali --> AFlannel[flannel.1<br/>VXLAN 8472 封装]
AFlannel --> AEns[ens160<br/>10.88.88.75]
end
AEns <--> BEns[ens160<br/>10.88.88.74]
subgraph Node1[k8sn1 节点<br/>10.88.88.74]
BEns --> BFlannel[flannel.1<br/>VXLAN 解封装]
BFlannel --> BCali[caliYYYY<br/>veth]
subgraph PodB[Pod B<br/>10.42.3.174]
BApp[应用容器]
end
BCali --> BApp
end
而 Node ↔ Node 的 iperf3 是直接走 ens160,不经过 flannel overlay,自然表现很好。
结论:只要一走 VXLAN(flannel.1),性能就崩;不走 VXLAN 的纯宿主机链路很快。
5. 更细一点:丢包、错误统计与 VXLAN 封装
继续观察 NIC 统计信息:
ethtool -S ens160 | egrep -i 'err|drop|crc|miss|buf|fault'可以看到类似:
pkts rx err: 4xxxxx / 5xxxxx
drv dropped rx total: 0
rx buf alloc fail: 0pkts rx err 数量非常可观,说明在 NIC 或虚拟化层面,确实存在不少接收错误。
考虑到:
- 覆盖网络使用 VXLAN(UDP 8472),
- 每个包要做额外封装,MTU 变小,如果底层有 MTU 问题,也可能造成大量碎片/丢包,
- 再叠加虚拟化环境(VMware 的 vNIC + vSwitch),某些 offload/特性可能和 VXLAN 不是特别兼容,
综合起来就能解释:
Node ↔ Node(裸 TCP)很快,但 Pod ↔ Pod(经过 VXLAN overlay)性能惨不忍睹。
因此我高度怀疑是ESXi底层+虚拟网卡驱动+VXLAN混合导致的深层问题。
6. 方向确定:尝试把 flannel Backend 从 VXLAN 改为 host-gw
根据 flannel 的工作模式:
- VXLAN:通过 UDP 封装实现跨三层网络的「二层覆盖」,好处是拓扑灵活、节点不必在同一二层网络中;
- host-gw:本质上是节点之间互相加静态路由,要求节点之间三层可达(我们这里是同一个 10.88.88.0/24 网段),性能更接近原生路由转发。
我们的节点 IP 是类似:
10.88.88.71(k8sm1)10.88.88.72(k8sm2)10.88.88.73(k8sm3)10.88.88.74(k8sn1)10.88.88.75(k8sn2)
节点之间本来就在一个平坦网段内,非常适合换成 host-gw 网络模式,于是准备直接切。
切换后,网络链路将变成下图所示:
flowchart LR
subgraph NodeA[Node A:k8sn2<br/>10.88.88.75]
PodA[Pod A<br/>10.42.4.198]
vethA[caliXXX<br/>veth]
routeA[Node A 路由表<br/>10.42.3.0/24 via 10.88.88.74]
PodA --> vethA --> routeA
end
subgraph Underlay[底层网络(物理/虚拟网络)]
nicA[ens160<br/>10.88.88.75]
nicB[ens160<br/>10.88.88.74]
routeA --> nicA --> nicB
end
subgraph NodeB[Node B:k8sn1<br/>10.88.88.74]
routeB[Node B 路由表<br/>10.42.3.0/24 dev caliYYY]
vethB[caliYYY<br/>veth]
PodB[Pod B<br/>10.42.3.174]
nicB --> routeB --> vethB --> PodB
end
7. 实操:修改 flannel Backend 为 host-gw
7.1 修改 ConfigMap
kubectl -n kube-system edit cm rke2-canal-config把其中的 net-conf.json 改成:
{
"Network": "10.42.0.0/16",
"Backend": {
"Type": "host-gw"
}
}保存后可以确认一下:
kubectl -n kube-system get cm rke2-canal-config -o jsonpath='{.data.net-conf\.json}' | jq .
# 看到 "Type": "host-gw" 即可7.2 设置环境变量 FLANNEL_BACKEND(保险起见)
有些部署方式同时读取环境变量 FLANNEL_BACKEND,于是顺手也改一下:
kubectl -n kube-system set env ds/rke2-canal FLANNEL_BACKEND=host-gw --overwrite再看下 DaemonSet 模板:
kubectl -n kube-system get ds rke2-canal -o yaml | egrep -A3 'FLANNEL_BACKEND|kube-flannel'7.3 重启 canal DaemonSet
kubectl -n kube-system rollout restart daemonset rke2-canal
kubectl -n kube-system rollout status daemonset rke2-canal在 rollout 过程中,可以看到 canal Pod 在各节点上依次重建。
8. 验证:flannel 确实切到了 host-gw
先看每个 canal Pod 的 flannel 日志:
kubectl -n kube-system logs rke2-canal-cvh6j -c kube-flannel | \
egrep -i 'Found network config|Backend type'
# 输出:
I1206 16:31:19.009020 1 main.go:468] Found network config - Backend type: host-gw再看节点上的路由表(以 k8sn1 为例):
ip route | grep 10.42.可以看到类似:
10.42.3.174 dev calif660738e20d scope link
10.42.4.0/24 via 10.88.88.75 dev ens160而在 k8sn2:
10.42.3.0/24 via 10.88.88.74 dev ens160
10.42.4.198 dev cali394abb81e79 scope link这跟 VXLAN 时的路由有明显差异:
- 之前(VXLAN):
10.42.X.0/24 via 10.42.X.0 dev flannel.1 onlink- 现在(host-gw):
10.42.4.0/24 via 10.88.88.75 dev ens16010.42.3.0/24 via 10.88.88.74 dev ens160
也就是:
Pod 网段之间的路由,已经走 宿主机物理网卡 ens160 直连,不再通过 VXLAN 封装的
flannel.1。
9. 再次测速:Pod ↔ Pod 带宽从 Mbit 一跃到 Gbit
回到最初的两台 Pod:
icdm-backend-deployment:10.42.3.174icdm-design-pcb-ring-tool-deployment:10.42.4.198
在后端 Pod 启动 iperf3 server:
iperf3 -s在算法 Pod 做客户端:
iperf3 -c 10.42.3.174这次的结果是:
[ 5] 0.00-10.00 sec 2.65 GBytes 2.28 Gbits/sec 110 sender
[ 5] 0.00-10.00 sec 2.65 GBytes 2.28 Gbits/sec receiver对比一下一开始的 1–2 Mbit/s,恢复到了正常水平。
Retr 也从几百次降到了百级,而且在 Gbit 级别带宽下这属于非常正常的范围。
此时再去跑 curl 下载大文件,耗时也已经回归一个合理的范围(几秒内搞定)。
最终结论:
根因是 flannel VXLAN backend 在虚拟化网络环境中的性能问题。 通过切换到 host-gw backend,使 Pod ↔ Pod 流量走宿主机直连路由,彻底解决了带宽瓶颈。
10. 经验总结
这次排查有几个值得记住的点:
1. 不要只盯在应用层(curl / HTTP)
一旦发现“内网访问一个文件奇慢”,非常建议第一时间上 iperf3,分别在:
- Pod ↔ Pod
- Node ↔ Node
做基准对比,可以迅速判断问题是在物理网络还是 CNI/overlay。
2.VXLAN 本身不是“错”,但环境可能不适合
在一些物理网络 / 云环境中,VXLAN 表现很好;
但在某些虚拟化 + 特定驱动 / offload 组合下面,VXLAN overlay 可能引起奇怪的性能问题(尤其是高带宽、大包、小 MTU 混合时)。
3.host-gw 是一个简单粗暴但非常高效的方案(前提是三层可达)
- 如果所有节点都在同一三层网段(比如 10.88.88.0/24),host-gw 的路由其实非常“傻瓜”:
- 每个 Pod 网段 10.42.X.0/24 通过节点 IP 10.88.88.X 互相可达;
- 少了一层封装,自然比 VXLAN 更接近“原生”表现。
4.善用 ip route 与 ethtool -S
ip route可以直观看出流量实际从哪条链路走,是走flannel.1还是ens160;ethtool -S的错误计数(pkts rx err)能帮助判断是否有底层链路/驱动的问题。
5.变更 CNI Backend 一定要有计划、有步骤
- 改 ConfigMap;
- 改 DaemonSet 环境变量(如果需要);
- 滚动重启 CNI DaemonSet;
- 按节点逐个确认路由 & 性能,避免生产服务中断。
11.几种常见 K8s 网络模式对比与示意
借这次实战,顺带整理几种常见网络模式的原理和适用场景。
11.1 Overlay:VXLAN 模式(flannel vxlan / calico vxlan)
对底层网络质量更敏感,一旦丢包/错误,就会放大(本文踩的坑)。
特点:
- 不要求底层网络认识 Pod 网段;
- 只要 Node IP 能互通,就可以通过 VXLAN 构造一个“虚拟二层”;
- 缺点是:
- 有封装开销(头部 + MTU 问题);
flowchart LR
subgraph NodeA[Node A]
PodA[Pod A]
vethA[veth/caliXXX]
vxlanA[flannel.1 / vxlan0]
nicA[eth0/ens160]
PodA --> vethA --> vxlanA --> nicA
end
subgraph Underlay[底层网络]
nicA --> nicB
end
subgraph NodeB[Node B]
nicB[eth0/ens160]
vxlanB[flannel.1 / vxlan0]
vethB[veth/caliYYY]
PodB[Pod B]
nicB --> vxlanB --> vethB --> PodB
end
适用场景:
- 底层网络拓扑复杂,或者没法轻易让物理网络认识 PodCIDR;
- 多云 / 混合网络环境;
- 对性能要求一般,但对「能跑起来」要求更高。
11.2 host-gw:路由直连(你目前的模式)
多机房、跨网络复杂时,可能需要更多规划。
原理:
- 每个 Node 负责一个或多个 Pod 子网(如 10.42.3.0/24);
- flannel 在每个 Node 上写静态路由:
10.42.3.0/24 via NodeBIP dev ens160
10.42.4.0/24 via NodeAIP dev ens160 - 底层网络只需要正常转发 Node IP 即可。
优点:
- 没有封装,延迟低、带宽高;
- 排障简单,完全是裸 IP 路由。
不足:
- 要求 Node 间三层可达(通常同一机房同一二层/路由器,问题不大);
flowchart LR
subgraph NodeA[Node A(Pod 子网 10.42.4.0/24)]
PodA[Pod A<br/>10.42.4.198]
vethA[caliXXX / eth0]
routeA[路由表<br/>10.42.3.0/24 via NodeB]
nicA[eth0/ens160<br/>NodeA IP]
PodA --> vethA --> routeA --> nicA
end
subgraph Network[物理/虚拟网络]
nicA --> nicB
end
subgraph NodeB[Node B(Pod 子网 10.42.3.0/24)]
nicB[eth0/ens160<br/>NodeB IP]
routeB[路由表<br/>10.42.3.0/24 dev caliYYY]
vethB[caliYYY / eth0]
PodB[Pod B<br/>10.42.3.174]
nicB --> routeB --> vethB --> PodB
end
非常适合:
- 中小规模集群;
- 底层网络在你控制之内;
- 对延迟和带宽比较敏感(例如训练、批量数据搬运等)。
11.3 Calico IPIP / BGP 模式(路由 + 隧道)
Calico 常见组合:
IPIP Overlay 模式:
- 类似 VXLAN,也是隧道,只不过封装在 IP 协议(dev
tunl0); - 坏处:同样有封装开销、MTU 问题。
- 好处:底层只需要支持 Node IP;
flowchart LR
subgraph NodeA[Node A]
PodA[Pod A<br/>10.244.1.5]
vethA[caliXXX]
tunlA[tunl0<br/>IPIP 封装]
nicA[eth0<br/>Node A IP]
PodA --> vethA --> tunlA --> nicA
end
subgraph Underlay[底层网络]
nicA --> nicB
end
subgraph NodeB[Node B]
nicB[eth0<br/>Node B IP]
tunlB[tunl0<br/>IPIP 解封装]
vethB[caliYYY]
PodB[Pod B<br/>10.244.2.8]
nicB --> tunlB --> vethB --> PodB
end
BGP + no encapsulation 模式:
- 每个 Node 作为 BGP Speaker,把自己的 PodCIDR 通告出去;
- 可以只在 Node 间跑 BGP,也可以把路由分发到上游交换机;
- 路径上更像 host-gw,只不过路由不是静态写死,而是 BGP 自动收敛。
适用:
- 网络团队比较专业,能够操作 BGP / EVPN 等;
- 集群规模和网络拓扑都比较大,需要动态路由来减轻人工维护成本。
11.5 eBPF Dataplane(Cilium / Calico eBPF)
更激进也更现代的一种玩法:用 eBPF 接管 Node 上的流量路径和 Service 负载均衡。
特点:
- 在内核 hook (TC/XDP 等) 里直接做:
- Pod ↔ Pod 路由;
- Service 负载均衡(替代 iptables/ipvs);
- 加上 BPF Map,可以极大降低规则查找开销,性能通常更好;
- 同时也能做细粒度网络策略、透明加密、流观测等。
flowchart LR
subgraph NodeA[Node A with eBPF Dataplane]
PodA[Pod A]
vethA[vethA]
ebpfA[eBPF 程序<br/>路由 + Service LB]
nicA[eth0]
PodA --> vethA --> ebpfA --> nicA
end
subgraph Network[底层网络]
nicA --> nicB
end
subgraph NodeB[Node B with eBPF Dataplane]
nicB[eth0]
ebpfB[eBPF 程序]
vethB[vethB]
PodB[Pod B]
nicB --> ebpfB --> vethB --> PodB
end
适用:
- 对网络性能、可观测性、策略要求都比较高的团队;
- 能够接受更复杂的部署与排障方式。
12. VXLAN 问题的可能根源:留给我的几个问号
这次的问题几乎肯定出在VXLAN + 虚拟化环境这一段,而不是业务代码、物理网络或 Pod 自身。
目前我觉得至少有这几类可疑点,留给后续继续深挖:
CPU / 内核软中断
- VXLAN 封装/解封装都在内核里做,如果中断/softirq 集中在某个 core,被打满,就会表现为「业务看起来不占 CPU,但网络速度奇慢」。
- 后续可以配合
mpstat -P ALL 1、/proc/interrupts、top -H在 iperf 压测时一起看。
MTU / MSS / PMTUD
- 理论值上:1500(ens160) – 50(封装头) ≈ 1450(flannel.1/Pod),现在配置也是 1450。
- 但中间如果还有 VLAN/QinQ、某一段链路 MTU 被下调,又刚好 ICMP 被丢,容易出现「隐形 MTU 问题 → 丢包+重传+性能掉底」。
- 后续可以用
tracepath、iperf3 -M/-l等方式验证不同 MSS/路径 MTU。
网卡/驱动/Offload 组合
- ESXi 上的 vmxnet3 + Linux VXLAN + 各种 TSO/GSO/GRO/UDP TNL offload,本身就是一个复杂组合。
- 某个 offload 特性如果在 VXLAN 场景下有 bug,可能只表现为「性能极差」而不中断。
- 之前粗暴关过一遍 offload,但没做逐项 AB Test,这一块后面可以更精细地一项一项试。
ESXi / vSwitch / flannel 版本匹配
- vSwitch / PortGroup 上是否有 shaping、特殊安全策略、内核/驱动/ESXi 某版本的已知 VXLAN 问题,这些目前都还没系统验证。
- flannel 版本 + 内核版本 + vmxnet3 版本的组合,也可能是踩中了某个已知但不起眼的坑。
这篇文章先记录「现象 + 排查路径 + host-gw 绕行方案」。
真正的 root cause 现在还是一个问号,等以后有机会在测试环境一项项拆解完,再写一篇「真凶篇」接在这后面。