
1. 问题现象与根因分析1.1 “节点NotReady”是最典型的表面症状这段时间后台好几个同学问我同一个问题新节点加入Kubernetes集群后kubectl get nodes一直显示NotReady节点上Pod调度不过去kube-system命名空间里的flannelPod反复CrashLoopBackOff日志刷满了标准输出。我这边复现出来的现象比较统一节点明明已经执行过kubeadm join证书流程也走完了但节点状态就是起不来。直接看kubectl describe node会看到类似network plugin is not ready: cni config uninitialized的提示。这个提示基本就是在点名说——容器的网络插件没就绪CNI配置压根没生成出来。顺着这个方向查下去最后定位到的根因不是kubelet的问题不是容器运行时的问题而是flannel启动时网卡选择错了。要理解这个问题得先明白flannel的工作原理。flannel属于CNI方案里的Overlay网络插件它在每个节点上启动一个flanneld进程负责从etcd或Kubernetes API Server拉取子网配置然后把本节点的Pod网段路由信息下发到宿主机路由表里再通过VXLAN或host-gw模式将跨节点的Pod流量封装转发出去。核心前提是flannel必须选对本节点上承载真实业务流量的那张网卡并且集群所有节点上这张网卡的身份标识要能对上号。问题就出在“选对网卡”这个环节。flannel默认的行为逻辑是在启动时获取第一个满足条件的网卡判断依据是网卡是否有内网IP、是否是UP状态、是否属于默认路由所在网卡。实际生产环境里服务器往往有多个网卡物理网卡、管理网卡、bond接口、虚拟网桥再加上云厂商环境里常见的eth0、eth1这种命名很容易导致flannel在自动探测时抓到一张完全不该用的网卡。最经典的情况就是某控制节点上存在一张ens192的网卡负责实际业务通信另一张docker0网桥或某虚拟网卡排在前面flannel默认探测捕到了后者于是它把Pod网段和路由信息全部绑定到了错误的网卡上VXLAN隧道始终建不起来节点网络一直处于异常状态。比“选错网卡”更隐蔽的是标题里这个“网卡名不兼容”。什么是网卡名不兼容就是flannel在节点上通过-iface参数写死了特定网卡名或者不同节点上同名网卡的IP网段互相冲突。比如你在一台机器上给flannel指定了--ifaceeth0但新加的节点上物理网卡叫enp3s0或ens160根本不存在eth0这个逻辑名。这种情况下flannel要么启动时报找不到接口要么启动成功但抓到了另一张毫无关联的网卡节点进入半死不活的状态。这种问题在由不同型号服务器、不同虚拟化平台混合组成的集群里尤其常见硬件命名规则不统一网卡名编号对不上号排错路径非常容易走偏。1.2 多网卡环境下的身份混淆与隧道失效从底层原理上看flannel在VXLAN模式下做的事情可以简化成三步第一确定本节点的Pod网段比如10.244.1.0/24第二用一张物理网卡的IP和MAC地址作为VTEP的对外身份与对端节点建立VXLAN隧道第三把节点上每个Pod的流量从flannel.1虚拟网卡送进隧道再交给物理网卡发出去。任何一个环节里“网卡身份错乱”整条链路就断了。在实际多网卡场景中最坑的还不是选错网卡本身而是你很难一眼看出它选错了。因为flannel日志里通常不会直接写“我选错了网卡”而是暴露出一堆看似不相关的错误。比如Failed to find any valid interface to use这是比较干脆的报错说明所有网卡都过不了它的过滤条件。还有另外一种Error adding route to 10.244.1.0/24 via 10.244.0.1: network is unreachable这种就很迷惑看着像路由问题其实是flannel把路由加到了错误的网卡上导致包根本发不出去。还有一种常见表现是flannelPod能运行kubectl get pods显示Running但节点状态就是永远卡在NotReady因为CNI配置文件和节点状态的上报链路始终没走通。我遇到过一个案例某公司内部Kubernetes集群用的是自建机房控制节点上同时存在一张em1千兆业务网和一张em2千兆存储网还有一块br0管理网桥。flannel默认抓取了br0这张桥接网卡原因是它被系统列为“第一个非loopback接口”。结果VXLAN隧道建起来了但是所有跨节点Pod流量都走到了管理网桥上不仅不通还把管理网络打出了高延迟。排查了很久最后看/run/flannel/subnet.env和ip route才确认是网卡选择错位。这里要强调的是flannel选错网卡本质上是自动探测逻辑无法覆盖你环境的“语义信息”。它不知道哪张网卡是你业务用的它只知道规则“取第一个有内网地址的UP网卡”。所以解决思路从来不是跟探测逻辑较劲而是显式告诉它“用哪张”或者通过配置让正确的网卡成为它的首选。1.3 为什么说“网卡名不兼容”比“网卡选错”更棘手网卡选错你改个参数指定--ifaceeth0就能绕过但如果两个节点的网卡名一样、IP段是不同网段或者同一个节点上有一张网卡的名字在不同重启后发生漂移问题就变得更复杂。我在一次混合云场景下遇到过更奇葩的情况某台节点上有一张eth0但这张eth0是虚拟机管理程序模拟出来的虚拟网卡真正承载业务的是后加的ens160。flannel默认识别到eth0指定了--ifaceeth0之后表面上看flannel启动成功了隧道也能建立但Pod流量只要一跨节点就断。因为eth0的IP段是管理网段它根本无法路由到对端Pod网段。这类问题之所以棘手是因为表面症状完全正常——Pod是Running状态日志也没有明显异常路由表看起来也有flannel的子网条目但业务就是不通。只有把ip route、ip neigh、bridge fdb show全拉出来对照才会发现flannel的VTEP地址用的是管理网IP对端节点还在拿业务网IP找它。这种隐性故障比CrashLoopBackOff难查得多也更值得在做网络规划时提前规避。2. 排查思路与日志定位2.1 先看节点状态再分链路定位拿到NotReady的节点我不会第一时间翻flannel的日志而是先按下面的顺序做一次快速体检查看节点状态和异常原因kubectl describe node node-name关注Conditions里的Ready和NetworkUnavailable状态。登录节点查看kubelet日志journalctl -u kubelet -f重点找network plugin is not ready或者cni config uninitialized。查看容器运行时中的Pod状态crictl ps -a | grep flannel确认flannel容器确实在重启。进入flannel容器或者用kubectl logs -n kube-system -l appflannel --tail200抓现场日志。直接暴力查看flannel生成的子网环境文件cat /run/flannel/subnet.env。这几个动作做完基本就能判断问题大致出在哪个环节。如果subnet.env文件不存在说明flannel还没能从API Server获得子网配置多半是网络连通性问题如果subnet.env存在但里面的FLANNEL_IFACE字段指向了错误的网卡名或IP那基本可以实锤是网卡识别异常了。来看一个典型的异常样本。假设你的节点上有以下网卡2: ens160: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc mq state UP inet 192.168.30.11/24 brd 192.168.30.255 scope global ens160 3: docker0: NO-CARRIER,BROADCAST,MULTICAST,UP mtu 1500 qdisc noqueue state DOWN inet 172.17.0.1/16 brd 172.17.0.255 scope global docker0docker0虽然没有实际流量但它也在UP状态且IP段是内网地址flannel的默认探测逻辑完全有可能会抓到它。于是subnet.env里写的是FLANNEL_NETWORK10.244.0.0/16 FLANNEL_SUBNET10.244.1.0/24 FLANNEL_MTU1450 FLANNEL_IFACE172.17.0.1看到FLANNEL_IFACE是172.17.0.1基本可以确认flannel把虚拟网桥当成了业务网卡。这个时候再去看路由表10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1 10.244.0.0/24 via 10.244.0.1 dev flannel.1 onlink缺失了对应的物理网卡路由条目跨节点通信必定中断。2.2 flannel日志里那句“I found interface”最关键很多人在排障时喜欢翻到最后看Error但flannel日志里真正有价值的信息其实藏在启动阶段。你会在日志开头看到类似这样的输出I1019 10:23:45.678901 1 main.go:203] Determining IP address of default interface I1019 10:23:45.678912 1 main.go:216] Using interface with name eth0 and address 192.168.30.11 I1019 10:23:45.678920 1 kube.go:132] Waiting for the cluster to become available重点看Using interface with name ... and address ...这一行。如果它显示的接口名或IP不是你所期望的业务网卡那问题基本就在这了。如果在日志里看到W1019 10:23:45.678915 1 main.go:210] No default interface found, trying to find the first interface with a valid IP则说明默认路由探测失败flannel进入了备选逻辑这时候它选中的网卡往往更不靠谱。排障时尤其要注意flannel容器内看到的网卡名和宿主机上看到的不一定一致但大多数情况是一致的因为是hostNetwork模式容器直接使用宿主网络命名空间。如果你发现日志里写的网卡名跟你机器上的对不上也可能是CNI网卡或网络策略工具的虚拟网卡插进来影响了判断。2.3 用三条命令确认“网卡错位”实锤确认网卡是否错位我一般用三条命令搞定第一条查看运行时flannel实际使用的网卡信息kubectl exec -n kube-system flannel-pod-name -- ip addr show这条命令可以看容器内实际看到的网卡和IP比对subnet.env中的FLANNEL_IFACE值是否一致。第二条查看节点的路由表重点找跟Pod网段相关的条目ip route | grep 10.244正常状态下你会看到类似10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1的条目。如果只有一条说明flannel还没有把跨节点路由下发到位。第三条查看VXLAN隧道对端信息bridge fdb show | grep flannel.1这条命令可以看到flannel.1上学习到的对端MAC地址如果MAC地址和对应的IP不属于预期网卡说明隧道对端的IP选错了。这三条命令对照下来基本能把“网卡错位”落实到具体层面是选了一张虚拟网卡、选了一张管理网卡还是选了一张IP完全跨网段的网卡一目了然。3. 解决方案与实操步骤3.1 方案一修改flannel DaemonSet参数显式指定业务网卡问题定位清楚之后最直接有效的操作就是在flannel的DaemonSet配置里通过--iface参数告诉flannel该用哪张网卡。这个方案也是Kubernetes社区里最常见的规避手段。步骤如下第一步修改flannel的DaemonSet让它通过环境变量或args参数指定网卡名。在kube-flannel.yml里找到容器启动参数的部分containers: - name: kube-flannel image: 你的flannel镜像地址 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr - --ifaceens160这里把--ifaceens160加进去将网卡名换为你实际业务网卡的名字。如果业务网卡在部分节点上名字不统一可以改用--iface-regex按正则表达式匹配比如--iface-regex^(eth0|ens160|enp3s0)$这种方式适用于一个集群里混合了多种服务器型号、网卡命名规则不一致的场景比写死单张网卡名要稳健得多。我在多节点集群上实测过用--iface-regex做正则匹配能覆盖绝大多数混合硬件环境。第二步让修改生效。如果你之前是用kubectl apply -f kube-flannel.yml部署的直接重新applykubectl apply -f kube-flannel.yml如果是用Helm或者其他方式部署的则通过对应的方式升级或重新渲染模板。第三步重启已有flannel Pod。DaemonSet的配置更新后并非所有节点上的Pod都会自动滚动重启取决于更新策略最省事的做法是直接删除全部flannel Pod让DaemonSet重新拉起来kubectl -n kube-system delete pod -l appflannel第四步清理可能残留的旧网络配置。这一步容易被忽略但很重要。在部分节点上kubelet的CNI配置目录/etc/cni/net.d/里可能残留之前flannel生成的配置或者由于旧Pod未清理干净导致新Pod启动时跳过了网络配置的重新生成。为了保险先移除旧配置再重启kubeletrm -f /etc/cni/net.d/* systemctl restart kubelet当然如果节点上有其他CNI插件比如Calico删配置前要确认/etc/cni/net.d/下确实只有flannel相关文件。3.2 方案二通过环境变量指定网卡和IP除了直接在DaemonSet的启动参数里写--iface还有一种更灵活的方式通过环境变量注入网卡信息。在kube-flannel.yml的容器定义里增加如下环境变量段env: - name: FLANNEL_IFACE value: ens160或者通过valueFrom字段从节点标签或字段引用动态取值实现更细粒度的网络配置管理。比如给每个节点打上kubernetes.io/hostname对应的网卡标签再通过环境变量引用它env: - name: FLANNEL_IFACE valueFrom: fieldRef: fieldPath: spec.nodeName不过这种方式需要配合正则匹配使用否则直接把节点名当网卡名肯定不对。实际环境中我更推荐把环境变量和--iface-regex结合起来用既能在不同节点间保持差异又不需要对每台机器单独改配置。对于那种“节点上有多个业务网卡不同网卡服务于不同网段Pod”的高级场景flannel还有--iface-regex配合--public-ip的方式。前者确定接口标识后者指定对外通告的IP地址。这部分比较进阶普通场景用不到但一旦涉及多网卡多网段隔离时它能救命。3.3 方案三彻底重建flannel网络防止配置残留如果你的集群已经因为反复改配置产生了各种残留问题而节点状态长期处于NotReady那么“局部修”不如“全局重建”。重建前先把集群里已有的flannel网络清理干净。第一步清理集群中的flannel资源kubectl delete -f kube-flannel.yml这会删除DaemonSet、ConfigMap和RBAC相关资源但要注意它不会自动清除各节点上已经写入的子网环境文件和CNI配置。第二步登录所有节点逐一清理残留rm -f /run/flannel/subnet.env rm -f /var/lib/cni/flannel/* rm -f /etc/cni/net.d/10-flannel.conflist ip link delete flannel.1 2/dev/null删除flannel.1虚拟网卡时如果提示“Resource temporarily unavailable”先重启kubelet再删一次。第三步重新应用修正后的kube-flannel.yml。注意此时--iface参数已经是修正后的字段kubectl apply -f kube-flannel.yml第四步观察恢复状态kubectl get nodes kubectl get pods -n kube-system -o wide | grep flannel正常状态下所有节点在1分钟内会变为Readyflannel Pod也不会再重启。这套方案适合集群规模较小、业务影响可控的维护窗口期操作。如果集群已经在承载生产流量直接重建flannel会引发所有节点的网络中断务必先切换业务流量或选择业务低谷时段操作。3.4 针对“多网卡名不一致”的预防性配置模板既然标题点明了“网卡名不兼容”这一节给出一个专门应对多服务器型号混合集群的flannel配置模板。这个模板我整理了很久在多个生产集群里验证过能解决大多数网卡名不一致的坑。核心思路是不直接写死单网卡名而是通过--iface-regex结合变量让每台节点自动匹配符合规则的网卡。下面是推荐的核心配置片段args: - --ip-masq - --kube-subnet-mgr - --iface-regex^(ens160|enp3s0|eno1|eth0)$ - --ip-masq - --kube-subnet-mgr注意--iface-regex不要写得太宽泛^(eth.*)$这种表达式会把虚拟网卡也匹配进来反而更容易踩坑。匹配规则最好精确到本集群所有节点实际使用的物理网卡名宁可多列几种不要用模糊匹配。另一种可选的解耦方式是用NodeSelector加自定义标签比如给节点打network-ifaceens160然后修改flannel DaemonSet的nodeSelector让不同标签的节点跑不同配置的flannel副本。这种方式管理成本略高但能实现真正的“按节点规划网络”适合严谨的生产环境。3.5 修改后验证网络连通性的完整步骤配置改完所有节点状态也Ready了网络是不是真的通了还要验证。我强烈建议不要只看节点状态直接跑一轮Pod间的跨节点通信测试。准备两个Pod分别调度到两台不同节点上Pod内自带ping和wget工具这里以nginx镜像为例或者你用busybox也行kubectl run test-pod-1 --imagebusybox --restartNever --command -- sleep 3600 kubectl run test-pod-2 --imagebusybox --restartNever --command -- sleep 3600然后分别查看这两个Pod被调度到哪台节点、Pod IP是什么kubectl get pods -o wide | grep test-pod接着进入第一个Podping第二个Pod的IPkubectl exec -it test-pod-1 -- ping test-pod-2-ip如果能够Ping通说明跨节点Pod通信链路正常。如果Ping不通再查一下两个节点的路由表和ARP表看看VXLAN隧道是否建立成功。这一步往往能发现“节点Ready了但网络通信还是受限”的隐蔽问题。对于更严谨的验证还要测服务访问。可以在集群里创建一个LoadBalancer或NodePort类型的服务从外部访问一下确认从Service到Pod的流量也正常。这样才能确定flannel修复后对整个集群网络栈的恢复是完整的。4. 常见问题与排查技巧实录4.1 “明明没改配置为什么重启后镜像变了”一种非常奇怪的故障是某节点网络配置明明一直正常重启后却突然变NotReady。重查发现/run/flannel/subnet.env里面写的网卡名变了。这种现象通常发生在系统版本升级或者重启后网卡名发生漂移的场景下。比如某服务器原来物理网卡是eth0重启后由于udev规则调整变成了enp3s0而flannel配置里写的是eth0于是就容易出现“找不到接口”或“接口IP不对”的情况。解决方案有两种一种是把配置从--iface改为--iface-regex正则里包含多个可能的网卡名这样无论重启后变成哪种命名只要物理链路还在flannel就有机会匹配成功另一种是给网卡的MAC地址绑定固定命名通过/etc/udev/rules.d/70-persistent-net.rules写入固定的NAME。不过这种方式在部分虚拟化环境下配置起来比较繁琐也更难维护。经验表明在Kubernetes这类基础设施平台中过度依赖固定的物理网卡名本身就是一种脆弱设计。更好的方式是把网络规划重心放在IP和路由上用IP绑定业务而不是用网卡名绑定业务。这一点在后续规划多集群和混合云时会尤其明显。4.2 flannel 与 kube-proxy 的模式冲突排查另一个高频怪问题是flannel修好了节点Ready了但Service的ClusterIP访问始终不通。这种问题十有八九出在kube-proxy的转发模式与flannel的转发模式不匹配上。kube-proxy模式有三种基础选择userspace基本废弃、iptables、ipvs。flannel默认的VXLAN模式在数据路径上依赖Linux内核的ip_forward和netfilter能力而kube-proxy使用ipvs模式时对Linux内核版本和内核模块有额外要求。如果节点上的ip_vs内核模块没加载kube-proxy的ipvs模式运行会失败表现为Service的ClusterIP访问超时或拒绝连接。排查时敲一下lsmod | grep ip_vs如果找不到输出说明ip_vs模块没加载。解决方法是编辑/etc/modules-load.d/ipvs.conf加入需要加载的模块名然后重启节点或手动modprobe加载。flannel网络与kube-proxy模式冲突的排查优先级我排在CNI问题之后因为一旦CNI本身有问题kube-proxy也不会正常工作。解决顺序建议是先把Pod间直连验证通过再测Service访问。4.3 为什么有时“删了Pod就恢复”反而误导排障方向有些同学在遇到网络问题时喜欢删Pod重建发现恢复了一段时间就认定问题解决了。但如果是flannel网卡绑定错误导致的网络问题删除Pod重建通常只是临时掩盖了症状根因依然存在。因为Pod重建后容器网络基于CNI配置重新创建此时如果CNI配置是新生成的且新生成的配置恰好和当前节点的真实网卡匹配上了比如flannel在重建时显式带了正确的网卡参数那么Pod通信能恢复。但一旦节点重启或者flannel Pod被重新调度到别的节点之前临时修正过的配置又可能被覆盖或者丢掉了问题会再次暴露。排障时如果看到“删Pod就好、过一阵又复发”的循环模式建议把思路从临时修复转向配置根因检查DaemonSet配置是否包含了错误网卡参数节点上的subnet.env是否被写入了异常值。4.4 常见问题速查表现象直接原因快速定位方法解决方向节点NotReadyflannel CrashLoopBackOffflannel容器找不到可用网卡或网卡IP不匹配kubectl logs -n kube-system -l appflannel --tail50增加--iface或--iface-regex指定正确网卡节点Ready但Pod跨节点不通flannel访问到了虚拟网桥或管理网卡查看/run/flannel/subnet.env中的FLANNEL_IFACE修改DaemonSet指定业务网卡重建flannelflannel日志显示“Using interface with name docker0”flannel自动探测优先级异常启动日志前几行观察接口选择显式指定--iface物理网卡名节点重启后flannel又挂了网卡命名漂移对比重启前后ip link show输出改用--iface-regex正则匹配或绑定固定网卡名跨节点心跳正常但Service IP不通kube-proxy模块缺失执行lsmod | grep ip_vs加载ip_vs相关内核模块节点状态Ready但Pod创建极慢CNI配置残留或子网环境文件过期查看/var/lib/cni/flannel与/run/flannel/subnet.env状态清理残留配置并重启kubelet这张表覆盖了大多数与“网卡不兼容”直接或间接相关的问题实际排障时拿着它对症状找方向效率会高很多。5. 集群网络规划层面的避坑与扩展建议5.1 在集群初始化前就确定网卡规划回顾整个排障过程最本质的教训不是“怎么修flannel”而是“在集群初始化阶段就要明确网络规范”。集群里的所有节点尤其是网络节点在加入集群前就应该确认好节点的业务网卡是哪个IP是什么是否有多网卡管理网卡和业务网卡是否分离flannel的默认探测规则是否会选错网卡是否有虚拟机或容器环境下的虚拟网卡干扰。如果初始化时就用--iface或--iface-regex把网卡钉死后面的绝大多数“网卡不兼容”问题根本不会发生。我在给新集群做规划时会在准备阶段就用ip link和ip addr把每台节点的网卡情况摸底一遍然后把统一的网卡匹配规则写进部署模板。这套事前准备流程大约多花20分钟但能省掉后期几天的排障时间。5.2 关于VXLAN与host-gw的选择顺带说一句这个标题主要讲的是网卡名兼容问题但修好网络之后很多人也会纠结既然flannel网卡问题这么多要不要换个CNI方案我的建议是flannel在中小型集群里依然是非常简单可靠的网络方案只要网卡选择问题被正确解决它的性能、稳定性和运维复杂度都是可接受的。相比Calico的BGP模式、Cilium的eBPF模式flannel的综合门槛最低排障路径也最清晰。真到了需要更高性能、更精细网络策略或更强多租户隔离的场景才需要考虑迁移到Calico或Cilium。而无论选哪种方案“显式指定网卡”这项基础配置都是通用的这点必须在初期规划时就落实。5.3 后续扩展从“修好”到“监控保障”修复只是第一步后续的保障同样重要。建议在集群里加一项基础的网络监控定期检查各节点/run/flannel/subnet.env中FLANNEL_IFACE的IP是否与节点的业务IP一致。可以把这条检查写成一个简单的脚本通过定时任务或Prometheus的文本采集器上报。一旦发现网卡IP异常变化立即告警。另外一个值得做的增强是在集群初始化时就把Pod网段和Service网段规划成预留网段不与任何已有网段冲突。flannel的--net参数要显式指定避免它从etcd中获取的网段与真实环境冲突。我个人在实际操作中的体会是Kubernetes网络问题的排障大多不是“不懂命令”而是“没有把网络原理和实际环境对上号”。flannel网卡不兼容这个问题表面是一个配置问题实质是对节点网络拓扑认知的缺失。先把每台节点上有哪些网卡、哪张网卡该承担什么角色搞清楚再去看flannel选没选对排障速度和准确度都会完全不一样。这套方法论不仅适用于flannel拿到Calico、Cilium场景下同样有效——网络插件永远只是执行者决策者始终是你自己。