在RHEL系统中运行Kubernetes、OpenShift或Podman时,CNI网络插件承担着为容器创建网络接口、分配IP地址、配置路由和DNS的关键任务。一旦CNI插件出错,最直接的表现就是Pod一直停留在ContainerCreating状态,或者容器启动后无法访问网络。处理这类故障不能只盯着kubelet日志,还要结合CNI配置文件、插件二进制、IPAM数据以及主机网络配置来综合判断。下面结合RHEL环境特点,从报错信息到修复步骤逐一展开。

CNI插件并不是由RHEL内核直接提供的能力,而是由容器运行时按照CNI规范调用的一组外部程序。在RHEL中,这些插件通常安装自containernetworking-plugins软件包,二进制文件默认位于/opt/cni/bin目录,配置文件则放置在/etc/cni/net.d目录。kubelet通过CRI接口向容器运行时发起创建Pod的请求,容器运行时再根据CNI配置文件加载对应插件。因此任何一环出现偏差,都会让容器网络创建失败。理解这个调用链是排查问题的基础。
CNI插件报错的常见表现与日志定位
CNI插件错误通常不会直接以弹窗或独立服务日志的形式出现,而是体现在kubelet、容器运行时以及容器事件的输出中。最典型的症状是Pod长时间处于ContainerCreating状态,用kubectl describe pod能看到类似Error syncing pod的详细信息,或者用crictl logs查看容器运行时的报错。在RHEL上,kubelet日志可以通过journalctl -u kubelet -f实时查看,其中经常出现failed to find plugin、CNI plugin not initialized、no IP addresses available等关键字段。
为了准确判断错误来源,建议先按时间顺序获取完整日志,而不是只截取最后几行。可以使用以下命令把kubelet日志导出到临时文件:
journalctl -u kubelet -n 200 --no-pager > /tmp/kubelet-cni.log grep -i cni /tmp/kubelet-cni.log
输出中如果出现failed to find plugin "bridge" in path [/opt/cni/bin],说明CNI配置文件里声明了bridge插件,但对应二进制缺失或没有执行权限。如果出现network plugin is not ready: cni config uninitialized,则往往表示/etc/cni/net.d目录下没有有效的配置文件,或者配置文件语法错误导致CNI未初始化。还有一种报错是failed to allocate for range 0: no IP addresses available in range set,这表示IPAM插件无法从配置的地址段中分配IP,可能由地址池耗尽或IPAM状态文件残留引起。通过日志中的这些关键字,可以快速缩小问题范围。
除了kubelet日志,容器运行时自身的日志也值得关注。如果使用CRI-O,可以执行journalctl -u crio -f;如果使用containerd,则检查/var/log/containerd/containerd.log。CNI插件执行时产生的标准输出和标准错误通常会被容器运行时捕获并记录,因此从CRI日志里也能看到插件执行的具体错误码。有些插件在失败时只返回简单的exit status 1,此时可以临时修改配置文件里的插件参数,增加debug字段,让插件输出更详细的调试信息。
从配置文件和插件二进制入手定位根因
CNI插件能否正常工作,最基础的两个条件就是配置文件有效、二进制可执行。在RHEL上,/etc/cni/net.d目录下可以存在多个.conf或.conflist文件,kubelet会按照字母顺序选择第一个读取。如果同时存在多个配置,并且它们定义了不同的网络类型或插件,就可能导致容器运行时加载了预期之外的插件,甚至直接报错。因此排查时应先列出该目录内容,检查有没有多余的旧配置文件。
ls -la /etc/cni/net.d/ for f in /etc/cni/net.d/*.conf /etc/cni/net.d/*.conflist; do echo "=== $f ==="; cat "$f"; done
常见的配置文件错误包括:JSON语法错误、cniVersion与插件版本不兼容、插件类型type写成了不存在的名字、ipam字段缺失或配置错误。下面是一个标准的bridge网络配置示例,可以作为校验参考:
{
"cniVersion": "1.0.0",
"name": "default-network",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"ranges": [
[
{
"subnet": "10.244.0.0/16"
}
]
],
"dataDir": "/run/cni-ipam-state"
}
}配置文件中即使多了一个逗号或少了一个括号,CNI都会拒绝加载。可以用python3 -m json.tool /etc/cni/net.d/*.conf快速验证JSON格式是否正确。如果格式无误但kubelet仍然不识别,需要核对type字段是否与/opt/cni/bin目录下的可执行文件名称完全一致。例如配置文件里写type: "bridge",那么对应二进制文件就必须是/opt/cni/bin/bridge,并且具有可执行权限。权限问题容易被忽视,RHEL对文件权限管理较严格,如果插件二进制是手动复制的,很可能缺少执行位。
另一个容易造成CNI故障的因素是SELinux。RHEL默认启用SELinux enforcing模式,如果插件二进制或配置文件的SELinux上下文不正确,CNI可能在读取或执行时被拒绝,表现为avc denied日志。可以用ls -lZ /opt/cni/bin /etc/cni/net.d查看上下文,正常应该显示container_file_t或类似类型。如果上下文异常,执行restorecon -Rv /opt/cni/bin /etc/cni/net.d即可恢复。
典型故障场景的修复方法与验证
场景一:CNI插件二进制缺失。在精简安装的RHEL容器宿主机上,可能只安装了容器运行时,没有安装containernetworking-plugins软件包。此时/opt/cni/bin目录为空或缺少常用插件,kubelet日志会反复出现failed to find plugin。修复方法是直接安装软件包:
dnf install -y containernetworking-plugins systemctl restart kubelet
如果因为网络限制无法使用dnf,也可以从其他同版本RHEL节点复制/opt/cni/bin目录下的所有插件二进制,但复制后必须执行chmod +x /opt/cni/bin/*和restorecon -Rv /opt/cni/bin,确保权限和SELinux上下文正确。
场景二:IPAM地址池耗尽或状态残留。当大量容器频繁创建删除时,host-local插件的IP分配记录会保留在/var/lib/cni/networks目录下,每个已分配的IP对应一个文件。如果容器删除时CNI的DEL操作没有正确执行,这些记录不会自动清理,最终导致新Pod无法获得IP。此时可以查看具体网络名称对应的目录:
ls -la /var/lib/cni/networks/ # 假设网络名为 default-network ls -la /var/lib/cni/networks/default-network
如果确认某些IP已经不再使用,可以删除对应文件来释放地址。不过删除时要格外小心,必须先确认没有容器正在使用该IP,否则可能造成IP冲突。更好的做法是检查CNI配置中的dataDir是否指向了持久化目录,如果使用host-local插件的旧版本,状态默认存放在/var/lib/cni/networks,升级CNI版本后路径可能变化,导致原有记录与新配置不一致。此时统一dataDir路径并清理旧目录,即可恢复IP分配。
场景三:NetworkManager对容器网桥的干扰。RHEL上的NetworkManager服务会尝试管理系统网络设备,如果CNI创建了cni0网桥或者veth接口,NetworkManager可能自动接管这些设备,导致IP地址被重置或路由被覆盖。典型表现是Pod创建成功但网络时好时坏,主机上cni0的状态不稳定。解决方法是在NetworkManager配置中忽略容器相关的网络接口:
cat > /etc/NetworkManager/conf.d/90-cni.conf <<'EOF' [keyfile] unmanaged-devices=interface-name:cni0;interface-name:veth* EOF systemctl reload NetworkManager
上述配置告诉NetworkManager不要管理名为cni0或veth开头的接口。如果使用Flannel等插件,可能还需要忽略flannel.1之类的VXLAN接口,具体可以根据ip link show看到的接口名补充。
SELinux导致的CNI故障修复也很有代表性。在enforcing模式下,如果自定义CNI插件需要访问非默认路径的文件,或者需要执行某些系统调用,SELinux可能拦截。可以通过ausearch -m avc -ts recent查看最近被拒绝的操作,关键字通常包含cni或container。临时测试时可以执行setenforce 0关闭SELinux,如果此时CNI恢复正常,就说明是SELinux策略问题。生产环境不建议长期关闭,应该使用audit2allow生成自定义策略模块并加载,或者调整文件上下文。对于大多数标准CNI插件,只要使用RHEL仓库安装并保持默认路径,一般不会触发SELinux问题。
修复完成后,需要从多个层面验证网络是否真正恢复。首先查看Pod状态是否从ContainerCreating变为Running:
kubectl get pods -o wide kubectl describe pod <pod-name> | tail -20
然后登录到Pod内部测试跨节点通信和DNS解析:
kubectl exec -it <pod-name> -- ip addr show kubectl exec -it <pod-name> -- ping -c 4 10.244.0.1 kubectl exec -it <pod-name> -- nslookup kubernetes.default.svc.cluster.local
最后在宿主机上确认CNI网桥和路由是否正确生成:
ip addr show cni0 ip route show | grep cni0
只有这些检查全部通过,才能说明CNI插件已经恢复到正常状态。
CNI故障预防与日常运维检查
CNI插件的稳定性直接影响整个容器集群的网络可用性,与其等到故障发生时再排查,不如在日常运维中建立定期检查机制。建议至少每周检查一次所有节点的CNI配置文件、插件二进制以及IPAM状态目录,确保没有异常变化。以下是一个简单的检查脚本,可以放在cron任务中执行:
#!/bin/bash set -e CNI_CONF_DIR="/etc/cni/net.d" CNI_BIN_DIR="/opt/cni/bin" IPAM_DIR="/var/lib/cni/networks" if [ ! -d "$CNI_CONF_DIR" ]; then echo "错误:CNI配置目录不存在" exit 1 fi if [ ! -x "$CNI_BIN_DIR/bridge" ] && [ ! -x "$CNI_BIN_DIR/portmap" ]; then echo "错误:常用CNI插件缺失或不可执行" exit 1 fi if [ ! -d "$IPAM_DIR" ]; then echo "警告:IPAM状态目录不存在,首次运行时会自动创建" fi echo "CNI基础检查通过" exit 0
脚本执行后会检查配置目录、插件二进制和IPAM状态目录,可以在集群部署初期或变更后快速验证基础环境。对于更复杂的CNI插件,比如Calico、Cilium等,它们通常有自己的健康检查命令,例如Calico可以使用calicoctl node status查看节点状态。RHEL环境建议将这些健康检查纳入监控系统,通过Prometheus等工具采集指标并配置告警。
除了脚本检查,还应该注意CNI版本与容器运行时的兼容性。CNI规范本身在演进,不同版本对插件参数和返回结果有差异。升级Kubernetes或OpenShift时,要同步确认CNI插件版本是否满足要求。RHEL软件仓库中的containernetworking-plugins通常会保持较好的兼容性,但如果是手动部署的第三方插件,就需要仔细核对版本匹配关系。此外,备份/etc/cni/net.d目录下的配置文件和自定义脚本,可以在出现配置被误改时快速恢复。
总结来说,RHEL上的CNI网络插件错误虽然表现多样,但排查路径基本固定:先看日志,确定是配置缺失、二进制缺失、IPAM问题还是系统组件干扰,再针对性地检查对应目录和权限,最后通过测试Pod验证修复效果。只要把配置文件、插件二进制、IPAM数据和主机网络管理这四类因素都纳入检查范围,绝大多数CNI故障都能在较短时间内定位并解决。