目录

【dive-into-k8s】用 Kind 手撸 CNI,从 CrashLoop 到 VXLAN 抓包解剖

周末,我在 Manjaro 上利用 kind 从零构建了一个“无网络”集群,手动排查了内核模块缺失、CNI 插件丢失等硬核故障,并最终通过 tcpdump 亲眼见证了 VXLAN 的封包过程。这篇文章记录了全过程。

在云原生领域,CNI (Container Network Interface) 往往是很多开发者的知识盲区。我们习惯了 kubectl apply -f flannel.yaml 一键梭哈,却很少探究底层发生了什么。

特别是对于致力转向 AI Infra 的工程师来说,高性能网络是分布式训练的命脉。如果连基础的 Overlay 网络开销在哪里都不知道,就更谈不上后续的 RDMA 或 eBPF 优化了。

一、 环境准备:制造“犯罪现场”

为了深入理解,我没有使用默认配置,而是强行禁用了 Kind 的默认 CNI,模拟一个只有骨架没有神经的“裸”集群。

环境: Manjaro Linux + Docker + Kind

kind-config.yaml:

1
2
3
4
5
6
7
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true # 关键:禁用默认网络,模拟裸机环境
nodes:
- role: control-plane
- role: worker # 多节点才能观察跨节点通信

启动集群后,正如预期,节点状态为 NotReady。 进入节点内部查看 ip addr,只有 loeth0,完全没有 cni0flannel.1 这样的网桥接口。此时的 Kubernetes 就像一个植物人,有心跳(Kubelet 在跑)但无法动弹(Pod 无法通信)。

二、 步步惊心的 Debug 之路

我选择安装经典的 Flannel 插件来“激活”网络,但过程并非一帆风顺。

1. 幽灵 Pod 与命名空间陷阱

执行安装命令后,习惯性查看 kube-system 命名空间,结果空空如也。

1
2
kubectl get pods -n kube-system
# 输出:No resources found.

排查: 检查 DaemonSet 发现 DESIRED 副本数正常。原来新版 Flannel 为了隔离性,已经将自己迁移到了独立的 kube-flannel 命名空间。 教训: 排查资源丢失时,请务必使用 kubectl get pods -A

2. 内核的拒绝:Missing br_netfilter

找到 Pod 后,发现它们全部处于 CrashLoopBackOff 状态。查看日志,捕获到了核心报错:

1
Failed to check br_netfilter: stat /proc/sys/net/bridge/bridge-nf-call-iptables: no such file or directory

深度解析: 这是 Linux 内核与 K8s 网络的经典冲突。Linux Bridge 默认工作在二层(数据链路层),不经过 iptables。而 K8s 的 Service (ClusterIP) 强依赖 iptables 做 NAT 转发。br_netfilter 模块的作用就是架起一座桥,强制经过网桥的流量进入 iptables 处理。

解决方案(在宿主机 Manjaro 上执行):

1
2
3
4
5
6
# 加载内核模块
sudo modprobe br_netfilter
# 开启转发参数
echo 1 | sudo tee /proc/sys/net/bridge/bridge-nf-call-iptables
# 重建 Pod
kubectl delete pod -n kube-flannel --all

因为 Kind 容器共享宿主机内核,宿主机加载模块后,容器内立即生效。

3. 消失的施工队:CNI Chaining 故障

Flannel 终于 Running 了,但我部署的测试 Pod (Nginx) 却一直卡在 ContainerCreatingkubectl describe pod 揭示了新的问题:

1
failed to find plugin "bridge" in path [/opt/cni/bin]

深度解析: 这里涉及到了 CNI 链式调用 (Chaining)。Flannel 只是“项目经理”,负责分配网段(IPAM)和同步路由。真正干脏活累活(创建 cni0 网桥、连接 veth pair)的是 CNI 标准库中的 Bridge 插件。Kind 的极简节点镜像中并没有预置这些基础二进制文件。

解决方案: 我们需要手动“空投”这些插件进去。

  1. 下载官方 cni-plugins-linux-amd64 包。
  2. 将解压后的 bridgeloopback 等二进制文件复制到 Kind 节点的 /opt/cni/bin/ 目录。
1
docker cp cni-plugins/. kind-worker:/opt/cni/bin/

这波操作后,所有 Pod 瞬间变绿(Running)。查看节点内部,subnet.env 成功生成,网络打通。

三、 终极解剖:Tcpdump 实战 Overlay 网络

网络通了只是开始,作为一个 AI Infra 预备役,必须看清数据包到底长什么样。我在 Control-plane 节点抓包,观察跨节点访问流量。

实验拓扑:

  • Client: Netshoot Pod (在 Control-plane 节点)
  • Server: Nginx Pod (在 Worker 节点)

抓包命令: 监听宿主机网卡的 UDP 8472 端口(VXLAN 标准端口)。

1
2
3
# 宿主机上使用 nsenter 借用容器网络命名空间抓包 (Pro Tip!)
PID=$(docker inspect --format '{{.State.Pid}}' kind-control-plane)
sudo nsenter -t $PID -n tcpdump -i eth0 port 8472 -n -v

抓包结果分析:

1
2
3
4
5
# 外层 (The Envelope)
IP 172.29.0.2.59431 > 172.29.0.3.8472: OTV, flags [I] (0x08), overlay 0, instance 1 ... length 134

# 内层 (The Letter)
IP 10.244.0.3 > 10.244.1.5: ICMP echo request ... length 64

硬核结论: 通过 -v 参数,清晰地看到了“包中包”结构。

  1. 外层 (Underlay):节点 IP 之间的 UDP 通信,目标端口 8472
  2. 内层 (Overlay):Pod IP 之间的 ICMP 通信。
  3. 性能税 (Tax):外层包长 134 字节,内层包长 84 字节。这意味着每个数据包都有 50 字节 的封装开销。

四、 总结

这次实战不仅修复了 CrashLoop,更重要的是量化了 Overlay 网络的成本。

在 Web 场景下,50 字节微不足道。但在 AI 大模型训练(如 AllReduce)场景下,海量梯度同步对延迟极度敏感。这 50 字节的封包/解包开销以及 CPU 上下文切换,可能就是制约 GPU 集群性能的瓶颈。

这也是为什么在高端 AI Infra 中,我们往往弃用 VXLAN,转而探索 HostNetworkMacVLAN 甚至基于 eBPF 的 Cilium 方案,以追求极致的零损耗网络。

Next Step: 下一步,我将把 Flannel 替换为 Cilium,并尝试使用 Hubble 可视化观察网络拓扑。