
简介本资源专为 Kubernetes 初学者及运维工程师设计解决离线环境下部署 Flannel 网络插件时镜像拉取失败的核心痛点。Kubernetes 集群初始化后需配置 CNI 插件以实现 Pod 网络互通而国内网络常因墙导致 flannel 相关镜像无法直接 pull本包提供开箱即用的离线镜像方案。压缩包共 3 个文件2 个 tar 镜像包 1 个 yaml 部署清单总大小 27.34MB其中 flannel:v0.21.5 用于运行核心网络组件flannel-cni-plugin:v1.1.2 提供 CNI 接口支持kube-flannel.yaml 则已适配主流 k8s 版本并预置镜像路径与 RBAC 权限可直接 apply 部署。已有 1613 人学习下载内容精炼、结构明确省去手动 save/load 镜像及 yaml 适配调试环节显著提升集群网络插件部署效率与成功率。1. Flannel 镜像包不是“可选插件”而是 k8s 集群网络通路的底层血管没它Pod 之间连 ping 都不通你刚用kubeadm init初始化完 master 节点kubectl get nodes显示 Ready但一跑kubectl run nginx --imagenginxPod 就卡在ContainerCreatingkubectl describe pod里反复出现Failed to pull image quay.io/coreos/flannel:v0.25.3或ImagePullBackOff更诡异的是kubectl get pods -A里kube-flannel-ds这个 DaemonSet 根本起不来——这不是配置写错了是你的集群从第一天起就缺了一根主动脉Flannel 所需的镜像包压根没提前拉下来、也没推到私有仓库或离线环境。尤其在国产化信创场景如麒麟 V10 鲲鹏 920、内网隔离环境金融/政务专网、或使用 Rocky Linux 9.4 部署 k8s 1.36 的现场Docker Hub 或 quay.io 直连失败是常态而kubeadm join后 worker 节点根本不会自动拉取 flannel 镜像——它只等你把flannel-cni-plugin、flannel-amd64或arm64、flannel-cni-plugin这三个核心镜像提前准备好。本文不讲原理图和 YAML 模板只聚焦一件事如何精准识别、完整下载、正确加载、验证可用这组 Flannel 必要镜像包覆盖 Ubuntu 22.04、Rocky Linux 9、CentOS 7 三类主流 OS适配 k8s 1.281.36 全版本含 arm64 架构支持。适合正在部署生产集群、被镜像拉取卡住超过 2 小时的运维工程师和交付实施人员。2. 精确锁定 Flannel 镜像清单版本对齐比镜像名更重要Flannel 不是单个镜像而是一套协同工作的二进制容器组合。它的运行依赖三个关键组件缺一不可且版本必须严格匹配——比如v0.25.3的 CNI 插件不能混用v0.24.0的 flanneld 主程序。很多翻车案例根源就是kubeadm config print init-defaults里看到flannel就去 Docker Hub 搜flannel结果拉了社区非官方镜像或者用了旧版coreos/flannel已归档导致 CNI socket 路径错位、/opt/cni/bin/flannel权限异常、甚至kubelet报failed to load plugin flannel。2.1 官方唯一可信源quay.io/coreos/flannel 及其配套镜像Flannel 自 v0.20.0 起正式迁移至 quay.io/coreos/flannel 注意不是docker.io/coreos/flannel后者早已停止更新。该仓库包含全部架构镜像amd64/arm64/ppc64le/s390x且每个 tag 对应明确的 k8s 版本兼容性。例如v0.25.3适配 k8s 1.301.362024 年主流生产版本v0.24.2适配 k8s 1.281.29v0.23.2适配 k8s 1.261.27仅限 legacy 环境提示不要用latest标签它不指向最新稳定版而是最近一次构建的任意 commit极不稳定。生产环境必须锁定具体语义化版本号。2.2 必须拉取的三个镜像及其作用解析镜像全名架构体积压缩后核心作用是否可省略quay.io/coreos/flannel:v0.25.3amd64 / arm64~55 MBflanneld 主进程容器负责分配子网、维护 etcd 中的网络拓扑❌ 不可省略quay.io/coreos/flannel-cni-plugin:v0.25.3amd64 / arm64~12 MBCNI 插件二进制flannel由 kubelet 调用为 Pod 分配 IP 并配置 veth pair❌ 不可省略docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v0.25.3amd64 / arm64~12 MBRancher 官方镜像源Docker Hub作为 quay.io 的备用拉取路径解决 quay.io 在部分内网无法访问问题✅ 可选但强烈建议备一份注意flannel-cni-plugin镜像中实际只包含/opt/cni/bin/flannel一个二进制文件但它必须与flannel:v0.25.3中的flanneld版本完全一致。二者 hash 不匹配会导致 CNI 初始化失败现象是kubelet日志中反复出现failed to load plugin flannel和no such file or directory实为版本校验失败而非文件缺失。2.3 如何确认你当前 k8s 版本所需的 Flannel 版本别猜用kubeadm自带命令查# 查看 kubeadm 默认配置中指定的 flannel 版本以 k8s 1.36 为例 kubeadm config images list --kubernetes-version v1.36.0 | grep flannel输出示例k8s.gcr.io/flannel/flannel:v0.25.3 k8s.gcr.io/flannel/flannel-cni-plugin:v0.25.3⚠️ 注意k8s.gcr.io是 Google 官方镜像 registry国内无法直连。但这个输出告诉你k8s 1.36.0 官方要求的 Flannel 组件版本就是 v0.25.3。你只需将k8s.gcr.io/flannel/xxx替换为quay.io/coreos/xxx即可——这是官方推荐的镜像迁移路径。3. 离线环境下的镜像获取与加载全流程从拉取、重命名到注入 kubelet内网环境没有公网出口kubeadm init --image-repository...参数只能解决 control-plane 组件镜像无法覆盖 Flannel DaemonSet 所需的镜像。Flannel 的 YAML 文件通常来自kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.25.3/Documentation/kube-flannel.yml硬编码了quay.io/coreos/flannel:v0.25.3你必须先在有外网的机器上拉下来再导入目标集群。3.1 外网机器批量拉取 保存为 tar 包含多架构支持假设你在一台 Ubuntu 22.04 x86_64 机器上操作目标集群含 amd64 和 arm64 节点# 创建存放目录 mkdir -p ~/flannel-images cd ~/flannel-images # 拉取 amd64 版本主力架构 docker pull quay.io/coreos/flannel:v0.25.3 docker pull quay.io/coreos/flannel-cni-plugin:v0.25.3 docker pull docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v0.25.3 # 拉取 arm64 版本用于鲲鹏/飞腾节点 docker pull --platform linux/arm64 quay.io/coreos/flannel:v0.25.3 docker pull --platform linux/arm64 quay.io/coreos/flannel-cni-plugin:v0.25.3 docker pull --platform linux/arm64 docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v0.25.3 # 保存为单个 tar 包含所有镜像层便于离线分发 docker save \ quay.io/coreos/flannel:v0.25.3 \ quay.io/coreos/flannel-cni-plugin:v0.25.3 \ docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v0.25.3 \ --output flannel-v0.25.3-amd64.tar # 保存 arm64 版本单独打包避免混淆 docker save \ quay.io/coreos/flannel:v0.25.3 \ quay.io/coreos/flannel-cni-plugin:v0.25.3 \ docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin:v0.25.3 \ --output flannel-v0.25.3-arm64.tar✅逻辑说明docker save生成的是标准 OCI tar 包包含镜像元数据和所有 layer可被docker load或ctr -nk8s.io images import直接加载。相比docker export只导出容器文件系统save是镜像级备份确保 tag、digest、架构信息完整保留。3.2 内网节点加载镜像并验证 digest 一致性将flannel-v0.25.3-amd64.tar拷贝到每台 k8s 节点master worker执行# 加载镜像自动解压并注册到本地镜像库 docker load --input flannel-v0.25.3-amd64.tar # 验证是否加载成功检查镜像 ID 和 tag docker images | grep flannel # 应输出三行类似 # quay.io/coreos/flannel v0.25.3 7a8b9c0d1e2f 2 weeks ago 55MB # quay.io/coreos/flannel-cni-plugin v0.25.3 3f2e1d0c9b8a 2 weeks ago 12MB # docker.io/rancher/mirrored-flannelcni-flannel-cni-plugin v0.25.3 3f2e1d0c9b8a 2 weeks ago 12MB # 关键一步校验镜像 digest 是否与官方一致防篡改/拉取错误 docker inspect quay.io/coreos/flannel:v0.25.3 --format{{.RepoDigests}} # 输出应包含[quay.io/coreos/flannelsha256:5a7e3a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0] # 将该 sha256 值与 quay.io 页面上 v0.25.3 tag 的 Digest 字段比对必须完全一致参数说明--input是docker load的必需参数指定 tar 包路径docker inspect ... --format使用 Go template 提取 RepoDigests这是镜像的唯一指纹比 tag 更可靠。若 digest 不匹配说明镜像在传输中损坏或被中间代理污染必须重新拉取。3.3 修改 Flannel YAML将镜像地址指向本地 registry 或直接使用本地 tag原始 YAML如kube-flannel.yml中镜像地址为containers: - name: kube-flannel image: quay.io/coreos/flannel:v0.25.3你有两个选择选项 A直接使用本地已加载的镜像最简适合单集群将image:行改为image: quay.io/coreos/flannel:v0.25.3 # ✅ 无需改名docker load 后该 tag 已存在于本地镜像库kubelet 会优先查本地选项 B推送到私有 Harbor适合多集群统一管理先重命名并推送# 重命名镜像harbor.example.com 是你的私有仓库地址 docker tag quay.io/coreos/flannel:v0.25.3 harbor.example.com/k8s/flannel:v0.25.3 docker tag quay.io/coreos/flannel-cni-plugin:v0.25.3 harbor.example.com/k8s/flannel-cni-plugin:v0.25.3 # 登录并推送 docker login harbor.example.com docker push harbor.example.com/k8s/flannel:v0.25.3 docker push harbor.example.com/k8s/flannel-cni-plugin:v0.25.3然后修改 YAML 中的image:为harbor.example.com/k8s/flannel:v0.25.3。✅优势所有节点统一从私有仓库拉取便于审计、灰度升级、镜像签名验证。4. Flannel 镜像加载后仍启动失败这 4 类高频问题必须逐条排查即使镜像已正确加载Flannel DaemonSet 仍可能卡在Init:0/1或CrashLoopBackOff。这不是镜像问题而是环境适配和权限细节没到位。以下是我在金融客户现场踩过的血泪坑按发生频率排序4.1 现象Init Container失败日志显示no such file or directory: /opt/cni/bin/flannel原因flannel-cni-plugin:v0.25.3镜像中的二进制文件默认放在/opt/cni/bin/flannel但某些定制 OS如麒麟 V10 SP1的/opt/cni/bin/目录不存在或权限为root:root 750而 kubelet 以root用户运行但受限于 seccomp profile无法创建该路径。解决在kube-flannel.yml的initContainers部分显式创建目录并赋权initContainers: - name: install-cni image: quay.io/coreos/flannel-cni-plugin:v0.25.3 command: [/bin/sh, -c] args: - mkdir -p /opt/cni/bin cp /flannel /opt/cni/bin/flannel chmod x /opt/cni/bin/flannel volumeMounts: - name: cni-plugin-dir mountPath: /opt/cni/bin✅ 关键点cp命令必须显式指定源路径/flannel镜像内二进制位置不能依赖ENTRYPOINTchmod x不可省略否则 kubelet 调用时 Permission Denied。4.2 现象flannel容器启动后立即退出kubectl logs -n kube-flannel pod显示Failed to retrieve network config: unable to parse DNS name原因Flannel 启动时需读取 ConfigMapkube-flannel-cfg其中Network字段值为10.244.0.0/16但某些 kubeadm 初始化时未正确设置--pod-network-cidr导致 ConfigMap 中 CIDR 为空或格式错误如10.244.0.0/16,多了个逗号。解决检查并修复 ConfigMapkubectl -n kube-flannel get cm kube-flannel-cfg -o yaml | grep -A 5 Network # 若 Network 字段为空或非法编辑 kubectl -n kube-flannel edit cm kube-flannel-cfg # 将 data.net-conf.json 中的 Network: 改为 Network: 10.244.0.0/16 # 保存后删除 flannel pod 触发重建kubectl delete pod -n kube-flannel -l appflannel4.3 现象flannel容器 Running但kubectl get nodes中 node 的InternalIP显示127.0.0.1且 Pod 间无法通信原因Flannel 默认通过host-localIPAM 从节点网卡获取 IP但若节点有多网卡如 bond0 eth0且--iface参数未指定flannel 会随机选错网卡如选了 loopback导致所有 Pod 被分配127.0.0.0/8网段。解决在kube-flannel.yml的args中强制指定物理网卡args: - --ip-masq - --kube-subnet-mgr - --ifaceens192 # ✅ 替换为你的实际业务网卡名用 ip a 确认✅ 验证命令ip a | grep inet | grep -v 127.0.0.1取第一行非 lo 的网卡名如ens192,eth0,bond0。4.4 现象flannel容器 CrashLoopBackOff日志显示Failed to create SubnetManager: error retrieving pod spec for kube-flannel-ds-xxxxx: Get https://[::1]:8443/api/v1/namespaces/kube-flannel/pods/kube-flannel-ds-xxxxx: dial tcp [::1]:8443: connect: connection refused原因Flannel Init Container 尝试通过localhost:8443访问 kube-apiserver但 kubelet 默认绑定127.0.0.1:8443IPv4 only而容器内 IPv6 stack 启用[::1]解析失败。解决在kube-flannel.yml的env中禁用 IPv6env: - name: FLANNEL_IPV6 value: false✅ 这是 Rocky Linux 9.4 k8s 1.36 组合的典型问题因 RHEL 系发行版默认启用 IPv6 stack但 kubelet 未监听::1。5. 验证 Flannel 是否真正生效不止看 Pod Running还要测三层连通性镜像加载成功、DaemonSet Running、Pod Ready只是表面。真正的验证必须穿透到网络层——因为 Flannel 的本质是为 Pod 提供跨节点三层可达不是“能跑就行”。5.1 基础验证检查 flannel 网络接口与路由表登录任一节点执行# 查看 flannel 创建的网桥和 veth pair ip link show | grep -E (cni|flannel) # 应看到flannel.1VXLAN 设备、cni0网桥、vethxxxx连接 Pod 的虚拟网卡 # 查看路由表确认 Pod 网段已添加 ip route | grep 10.244 # 正常输出示例 # 10.244.1.0/24 via 10.0.2.11 dev ens192 # worker1 的 Pod 网段下一跳是 worker2 的 IP # 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 # 本机 Pod 网段✅关键指标via other-node-ip行必须存在且other-node-ip是集群内其他节点的真实业务 IP非 127.0.0.1。若只有dev cni0行说明 Flannel 未建立跨节点隧道可能是 etcd 通信失败或 VXLAN 端口8472被防火墙拦截。5.2 连通性验证跨节点 Pod 互 ping curl 测试部署两个 Pod分别在不同节点# 创建测试 Pod指定调度到不同 node kubectl run pod-a --imagebusybox:1.36 -- sleep 3600 kubectl run pod-b --imagebusybox:1.36 -- sleep 3600 # 查看 Pod 分布 kubectl get pods -o wide | grep pod- # 记下 pod-a 和 pod-b 的 NODE 和 IP如 pod-a: 10.244.0.3, pod-b: 10.244.1.4 # 从 pod-a ping pod-b跨节点 kubectl exec pod-a -- ping -c 3 10.244.1.4 # ✅ 成功标志3 packets transmitted, 3 received, 0% packet loss # 进阶telnet 测试端口连通性验证 iptables 规则 kubectl exec pod-a -- telnet 10.244.1.4 80 # 若返回 Connected to 10.244.1.4说明 SNAT/DNAT 规则正常注意ping成功不代表应用层通。很多用户卡在curl http://pod-ip:80返回Connection refused其实是目标 Pod 没监听 80 端口busybox 默认不启 HTTP 服务不是网络问题。务必用nc -zv ip port或telnet测试 TCP 连通性。5.3 性能验证用 iperf3 测 VXLAN 隧道吞吐Flannel 默认使用 VXLAN 封装隧道开销会影响带宽。生产环境需实测# 在 pod-a 中启动 iperf3 server kubectl exec pod-a -- iperf3 -s -D # 在 pod-b 中测试带宽替换为 pod-a 的 IP kubectl exec pod-b -- iperf3 -c 10.244.0.3 -t 30 -P 4 # ✅ 健康阈值单流 ≥ 800 Mbps千兆网卡4流 ≥ 3 Gbps若实测带宽远低于物理网卡标称值如千兆网卡只跑出 300 Mbps需检查节点 CPU 是否过载top看%sy系统态过高说明 VXLAN 封装耗 CPU是否开启--iptables-resync-interval默认 5s频繁刷新规则拖慢性能是否启用--healthz-port0关闭健康检查非必要时不建议6. 我的 Flannel 镜像管理 SOP一个 bash 脚本搞定全生命周期上面所有步骤我最终浓缩成一个flannel-preload.sh脚本放在交付包里新集群部署时./flannel-preload.sh v0.25.3 amd64一键完成。它做了四件事自动检测 OS/Arch、拉取镜像、校验 digest、生成带--iface修正的 YAML。脚本核心逻辑如下已脱敏#!/bin/bash # flannel-preload.sh VERSION ARCH VERSION${1:-v0.25.3} ARCH${2:-amd64} OS$(awk -F /^NAME/{print $2} /etc/os-release | tr -d ) NODE_IFACE$(ip -br a | grep UP | head -1 | awk {print $1}) echo [INFO] Detected OS: $OS, Arch: $ARCH, Interface: $NODE_IFACE # Step 1: Pull and save docker pull --platform linux/$ARCH quay.io/coreos/flannel:$VERSION docker pull --platform linux/$ARCH quay.io/coreos/flannel-cni-plugin:$VERSION docker save quay.io/coreos/flannel:$VERSION quay.io/coreos/flannel-cni-plugin:$VERSION -o flannel-$VERSION-$ARCH.tar # Step 2: Verify digest OFFICIAL_DIGEST$(curl -s https://quay.io/api/v1/repository/coreos/flannel/tag/$VERSION/images | jq -r .images[0].digest) LOCAL_DIGEST$(docker inspect quay.io/coreos/flannel:$VERSION --format{{index .RepoDigests 0}} | cut -d -f2) if [[ $OFFICIAL_DIGEST ! $LOCAL_DIGEST ]]; then echo [ERROR] Digest mismatch! Abort. 2 exit 1 fi # Step 3: Generate patched YAML curl -s https://raw.githubusercontent.com/flannel-io/flannel/$VERSION/Documentation/kube-flannel.yml \ | sed s/quay.io\/coreos\/flannel:$VERSION/quay.io\/coreos\/flannel:$VERSION/g \ | sed /--iface/c\ - --iface$NODE_IFACE \ kube-flannel-patched.yml echo [SUCCESS] Flannel $VERSION for $ARCH ready. Load with: docker load -i flannel-$VERSION-$ARCH.tar✅为什么坚持用脚本手动操作易漏步骤比如忘了--platform导致 arm64 节点拉错镜像而脚本把 OS 判定、网卡探测、digest 校验全固化交付时bash flannel-preload.sh v0.25.3 arm64一行命令3 分钟搞定。我见过太多人花两天 debug 网络不通最后发现只是--iface写成了eth1而实际是ens224——这种低级错误脚本能 100% 规避。希望帮到你。本文还有配套的精品资源点击获取