一次 K8s 集群内网故障的排查记录

本文记录了一次 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.74
  • k8sn2: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,文件上传/下载严重超时:

Bash
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)里起服务:

Bash
# Pod A: 10.42.3.174
iperf3 -s

在算法 Pod(icdm-design-pcb-ring-tool)里做客户端:

Bash
# Pod B: 10.42.4.198
iperf3 -c 10.42.3.174

最开始的结果大概是:

Bash
[  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 上:

Bash
iperf3 -s

在 k8sn2 上:

Bash
iperf3 -c 10.88.88.74
iperf3 -c 10.88.88.74 -R

结果是:

Bash
# 正向
[  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 相关配置:

Bash
kubectl -n kube-system get cm rke2-canal-config -o yaml

关键部分:

YAML
net-conf.json: |
  {
    "Network": "10.42.0.0/16",
    "Backend": {
      "Type": "vxlan"
    }
  }
...
veth_mtu: "1450"

flannel 日志也能看到:

Bash
kubectl -n kube-system logs ds/rke2-canal -c kube-flannel | egrep -i 'Backend type'
# 输出类似:
Found network config - Backend type: vxlan

在 worker 节点上看接口:

Bash
ip link | egrep 'flannel|cali'
ip addr show flannel.1

以及路由表:

Bash
ip route

例如(k8sn1):

Bash
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 统计信息:

Bash
ethtool -S ens160 | egrep -i 'err|drop|crc|miss|buf|fault'

可以看到类似:

Bash
pkts rx err: 4xxxxx / 5xxxxx
drv dropped rx total: 0
rx buf alloc fail: 0

pkts 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

Bash
kubectl -n kube-system edit cm rke2-canal-config

把其中的 net-conf.json 改成:

YAML
{
  "Network": "10.42.0.0/16",
  "Backend": {
    "Type": "host-gw"
  }
}

保存后可以确认一下:

Bash
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,于是顺手也改一下:

Bash
kubectl -n kube-system set env ds/rke2-canal FLANNEL_BACKEND=host-gw --overwrite

再看下 DaemonSet 模板:

Bash
kubectl -n kube-system get ds rke2-canal -o yaml | egrep -A3 'FLANNEL_BACKEND|kube-flannel'

7.3 重启 canal DaemonSet

Bash
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 日志:

Bash
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 为例):

Bash
ip route | grep 10.42.

可以看到类似:

Bash
10.42.3.174 dev calif660738e20d scope link
10.42.4.0/24 via 10.88.88.75 dev ens160

而在 k8sn2:

Bash
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 ens160
  • 10.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.174
  • icdm-design-pcb-ring-tool-deployment:10.42.4.198

在后端 Pod 启动 iperf3 server:

Bash
iperf3 -s

在算法 Pod 做客户端:

Bash
iperf3 -c 10.42.3.174

这次的结果是:

Bash
[  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 routeethtool -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/interruptstop -H 在 iperf 压测时一起看。

    MTU / MSS / PMTUD

      • 理论值上:1500(ens160) – 50(封装头) ≈ 1450(flannel.1/Pod),现在配置也是 1450。
      • 但中间如果还有 VLAN/QinQ、某一段链路 MTU 被下调,又刚好 ICMP 被丢,容易出现「隐形 MTU 问题 → 丢包+重传+性能掉底」。
      • 后续可以用 tracepathiperf3 -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 现在还是一个问号,等以后有机会在测试环境一项项拆解完,再写一篇「真凶篇」接在这后面。

          Prometheus + Grafana 构建监控平台 REK2 搭建6节点K8S教程(三):RKE节点安装 REK2 搭建6节点K8S教程(二):HAProxy + Keepalived 高可用
          View Comments
          There are currently no comments.