Kubernetes离线部署flannel镜像全攻略:清单、导入与避坑 简介这份资源面向正在搭建 Kubernetes 集群、需要为节点配置网络插件的运维与开发人员聚焦解决 k8s 安装过程中 flannel 网络组件镜像拉取困难、离线环境部署不便的问题。压缩包共 3 个文件以 2 个 tar 镜像包和 1 个 yaml 清单为主整体约 27.34MB其中 tar 文件用于导入 flannel 与 flannel-cni-plugin 两个核心镜像yaml 文件则提供可直接应用的部署配置方便在无外网或网络受限的机器上完成插件安装。资源覆盖 flannel-cni-plugin:v1.1.2 与 flannel:v0.21.5 两个版本并附带配套的 kube-flannel.yaml读者可据此快速完成镜像加载与插件部署减少因镜像源不稳定导致的反复重试。目前已有 1613 人学习下载适合需要离线部署或希望简化 flannel 安装流程的中高级 k8s 使用者参考使用。1. 离线装 k8s 时flannel 镜像为什么总在最后一公里翻车很多团队在机房内网部署 Kubernetes 时kubeadm init 一路顺畅节点也 Ready 了结果一查 CoreDNS 全是 Pendingkubectl get pods -n kube-system 里 flannel 那两个 Pod 卡在 ImagePullBackOff。问题不在 YAML也不在 CNI 配置而是 flannel 依赖的镜像根本没进内网仓库。安装 k8s 所需 flannel 必要镜像包这件事本质是把 flannel 运行时要用的容器镜像提前拉全、打对 tag、导成 tar、再推进离线 registry 或直接 ctr 导入。它解决的是「集群能起来但网络不通」这类最容易被忽略的落地问题适合做私有化交付、内网机房、边缘节点批量部署的工程师。下面按我实际交付过的路径把镜像清单、拉取、导出、导入和排错一次讲透。2. flannel 镜像到底要哪几个清单、来源与版本对齐2.1 别只记一个 flannel/flannel真正要拉的是三类镜像flannel 在 k8s 里跑起来最少涉及三类镜像。第一类是 flannel 本体常见仓库名是 flannel/flannel也有历史版本叫 coreos/flanneltag 通常跟 k8s 大版本没有强绑定但跟 flannel 自身版本绑定比如 v0.24、v0.25、v0.26。第二类是 CNI 插件镜像flannel 的 initContainer 会用它把 CNI 二进制和配置写到宿主机 /opt/cni/bin 和 /etc/cni/net.d常见名是 flannel/flannel-cni-plugin。第三类是 kube-flannel.yml 里可能引用的辅助镜像比如某些发行版会带 kube-proxy 或 pause但 pause 通常由 kubeadm 自己管理不算 flannel 专属。我一般先确认集群的 k8s 版本和 flannel 清单版本再决定 tag。做法是直接看官方 kube-flannel.yml 里的 image 字段不要凭记忆。下面这段命令用来把清单里的镜像地址全部抽出来避免漏拉。# 下载对应版本的 kube-flannel.yml这里以常见 v0.25 为例 curl -fsSL -o kube-flannel.yml \ https://raw.githubusercontent.com/flannel-io/flannel/v0.25.1/Documentation/kube-flannel.yml # 抽取所有 image: 行去重后输出 grep -E ^\s*image: kube-flannel.yml | awk {print $2} | sort -u逻辑说明grep 抓 image 行awk 取第二列sort -u 去重。参数上-E 启用扩展正则\s* 兼容 YAML 缩进。执行后你会看到类似 flannel/flannel:v0.25.1 和 flannel/flannel-cni-plugin:v1.4.1-flannel1 两行。如果输出为空说明清单里 image 字段格式不同可能是 image: flannel/flannel:v0.25.1 带引号需要把 awk 改成 sed 去引号。提示不要用 latest。离线环境里 latest 无法复现今天拉到的和下周拉到的可能不是同一个 digest回滚时你会很被动。2.2 版本对齐k8s 1.28 配 flannel 哪个 tag 不翻车版本对齐没有官方强制矩阵但社区实践里有一条经验线k8s 1.24 之后 flannel 建议 v0.19 以上k8s 1.26 之后建议 v0.21 以上k8s 1.28/1.29 用 v0.24 或 v0.25 比较稳。原因是 k8s 1.24 移除了 dockershim容器运行时切到 containerdflannel 的 CNI 插件版本也要跟着更新否则会出现 Pod 网络通了但 Service 不通的玄学现象。我一般按下面这张表做初筛然后再用实际清单确认。k8s 版本flannel 建议版本CNI 插件 tag 示例备注1.24 至 1.25v0.19 至 v0.21v1.1.1-flannel1过渡期注意 containerd 配置1.26 至 1.27v0.21 至 v0.23v1.2.0-flannel1多数内网交付稳定1.28 至 1.29v0.24 至 v0.25v1.4.0-flannel1推荐CNI 插件较新1.30 及以上v0.25 及以上v1.4.1-flannel1先小规模验证这张表不是绝对真理但能帮你避开大部分「版本太老导致 CNI 不兼容」的坑。真正落地时以你下载的 kube-flannel.yml 里的 tag 为准表只用来判断这个 tag 是否合理。2.3 拉取镜像docker pull 和 ctr 两条路怎么选如果你的跳板机或构建机装了 docker用 docker pull 最顺手因为导出 tar 的命令成熟。如果只有 containerd就用 ctr。两条路我都用过区别在于 docker 导出的 tar 用 docker load 导入ctr 导出的 tar 用 ctr images import 导入混用会报格式错误。# 方式一docker 拉取并导出 docker pull flannel/flannel:v0.25.1 docker pull flannel/flannel-cni-plugin:v1.4.1-flannel1 docker save -o flannel-images.tar \ flannel/flannel:v0.25.1 \ flannel/flannel-cni-plugin:v1.4.1-flannel1 # 方式二ctr 拉取并导出 ctr -n k8s.io images pull docker.io/flannel/flannel:v0.25.1 ctr -n k8s.io images pull docker.io/flannel/flannel-cni-plugin:v1.4.1-flannel1 ctr -n k8s.io images export flannel-images.tar \ docker.io/flannel/flannel:v0.25.1 \ docker.io/flannel/flannel-cni-plugin:v1.4.1-flannel1逻辑说明docker save 的 -o 指定输出文件后面跟多个镜像名。ctr 必须带 -n k8s.io因为 k8s 用的是 k8s.io 这个 namespace不带的话导入后 kubelet 看不到。export 子命令在较新 containerd 里可用老版本可能只有 images export 或需要 nerdctl。参数上镜像名要写全docker.io 前缀在 ctr 里不能省否则会去默认 registry 找。注意ctr 导出时如果镜像名带 docker.io 前缀导入端也要用同样前缀否则 kubelet 按清单里的短名去找会找不到。最稳的办法是导入后 docker tag 或 ctr images tag 成清单里写的名字。3. 离线导入把 tar 推进内网 registry 或直接喂给 containerd3.1 推私有 registrydocker tag push 的完整链路内网有 Harbor 或 registry:2 的情况下推荐把镜像推仓库这样所有节点都能拉不用每台机器 scp tar。步骤是在能上网的机器拉取docker tag 成内网仓库地址push然后在离线节点正常 kubeadm 部署kubelet 会自动从内网仓库拉。# 假设内网仓库地址是 registry.internal:5000 docker tag flannel/flannel:v0.25.1 \ registry.internal:5000/library/flannel:v0.25.1 docker tag flannel/flannel-cni-plugin:v1.4.1-flannel1 \ registry.internal:5000/library/flannel-cni-plugin:v1.4.1-flannel1 docker push registry.internal:5000/library/flannel:v0.25.1 docker push registry.internal:5000/library/flannel-cni-plugin:v1.4.1-flannel1逻辑说明tag 的作用是改镜像的仓库地址push 会把层推到内网仓库。参数上library 是命名空间可以换成你的项目名。推完后要改 kube-flannel.yml 里的 image 字段把 flannel/flannel 换成 registry.internal:5000/library/flannel否则 kubelet 还是去 docker.io 找。改清单可以用 sed 批量替换但要注意别把注释里的地址也改了。我一般先备份再替换。cp kube-flannel.yml kube-flannel.yml.bak sed -i s|flannel/flannel:|registry.internal:5000/library/flannel:|g; s|flannel/flannel-cni-plugin:|registry.internal:5000/library/flannel-cni-plugin:|g kube-flannel.yml逻辑说明sed 的 s 命令做替换| 作分隔符避免和路径里的 / 冲突g 表示全局替换。替换后 grep image 确认一遍确保没有漏网或误伤。3.2 直接导入 containerdctr images import 的细节没有内网仓库时就把 tar 拷到每个节点用 ctr 导入。导入后不需要改清单里的镜像名但前提是导入时的镜像名和清单里一致。如果清单写 flannel/flannel:v0.25.1导入的也必须是这个名字。# 在每个节点执行 ctr -n k8s.io images import flannel-images.tar # 验证导入结果 ctr -n k8s.io images ls | grep flannel逻辑说明-n k8s.io 指定 namespaceimport 读取 tar 并写入 containerd 内容存储。验证时 grep flannel 应该能看到两个镜像tag 和清单一致。如果看不到检查 tar 是不是用 docker save 导出的docker save 的 tar 在 ctr import 时可能报错需要先用 docker load 再 ctr 重新导出或者直接用 nerdctl。提示导入后如果 kubelet 仍然 ImagePullBackOff先看 kubelet 日志里的 image 名再和 ctr images ls 对比。十有八九是名字或 tag 差一个字符这种问题没有后悔药只能逐字核对。3.3 批量节点用脚本把导入和校验串起来节点多的时候手动 scp 和 import 容易漏。我一般写一个简单脚本用 ssh 批量执行导入后自动校验镜像是否存在。#!/bin/bash # nodes.txt 每行一个节点 IP TAR_FILEflannel-images.tar REMOTE_DIR/tmp while read -r node; do echo 处理节点 $node scp $TAR_FILE root$node:$REMOTE_DIR/ ssh root$node ctr -n k8s.io images import $REMOTE_DIR/$TAR_FILE \ ctr -n k8s.io images ls | grep flannel done nodes.txt逻辑说明while read 逐行读节点scp 传 tarssh 执行 import 并立即校验。参数上nodes.txt 要提前准备好root 按实际用户改。这个脚本没有做并发节点多时可以加 和 wait但要注意输出交错。校验那步很关键导入失败时 grep 为空你能立刻发现而不是等部署时才排查。4. 避坑与排查flannel 镜像相关的 5 个血泪现场4.1 现象Pod 一直 ImagePullBackOff但 ctr 里明明有镜像原因kubelet 用的镜像名和 ctr 里的名字不一致。常见情况是清单里写 flannel/flannel而 ctr 导入的是 docker.io/flannel/flannelcontainerd 不会自动补全短名。另一个可能是 tag 不一致比如清单 v0.25.1导入的是 v0.25.0。解决先 kubectl describe pod 看 Events 里的 image 名再 ctr -n k8s.io images ls 对比。不一致就用 ctr images tag 改成清单里的名字或者改清单。改完等 kubelet 重试或者删 Pod 让它重建。4.2 现象flannel Pod 起来了但节点之间 Pod 不通原因CNI 插件镜像没导入initContainer 失败但主容器可能还在跑或者 CNI 配置没写成功。flannel 的 initContainer 负责把 CNI 二进制拷到宿主机如果这个镜像缺失Pod 可能显示 Running 但网络实际没配好。解决kubectl logs 看 initContainer 日志确认 CNI 插件镜像是否拉取成功。同时检查宿主机 /opt/cni/bin 下有没有 flannel 和 bridge 等二进制/etc/cni/net.d 下有没有 10-flannel.conflist。缺就补导入 CNI 插件镜像然后重启 flannel Pod。4.3 现象docker save 的 tar 用 ctr import 报错原因docker save 和 ctr import 的 tar 格式不完全兼容尤其是较老 containerd 版本。docker save 的 manifest 结构和 ctr 期望的有差异。解决要么在导入端用 docker load 再 ctr 重新导出要么直接用 nerdctl save 或 ctr images export 生成 tar。如果节点只有 containerd建议在构建机上用 ctr 导出别用 docker save。4.4 现象内网 registry 推送成功但节点拉取报证书错误原因registry 用了自签证书节点 containerd 没配 insecure 或 CA。kubelet 拉取时走 HTTPS证书不信任就失败。解决在节点 /etc/containerd/config.toml 里给该 registry 配 insecure_skip_verify 或 CA 路径然后 systemctl restart containerd。生产环境建议配 CA不要长期 skip verify。4.5 现象flannel 版本和 k8s 版本不匹配Service 不通原因CNI 插件版本太老不支持新版 k8s 的某些网络特性或者 flannel 后端类型和 k8s 配置冲突。典型是 k8s 1.28 配 flannel v0.19Pod 能通但 ClusterIP 访问超时。解决升级 flannel 到建议版本重新导入镜像更新 kube-flannel.yml 并 apply。升级前先在小规模测试集群验证别直接在生产上滚。5. 进阶技巧用 digest 锁定镜像让离线包可复现镜像 tag 是可变的同一个 tag 在不同时间可能指向不同 digest。离线交付最怕「上次能装这次装不上」所以进阶做法是用 digest 锁定。docker pull 后 docker inspect 能看到 RepoDigestsctr images ls 也能看到 digest。把清单里的 image 改成 imagesha256:... 形式就能保证每次拉到的都是同一个镜像。# 查看镜像 digest docker inspect --format{{index .RepoDigests 0}} flannel/flannel:v0.25.1 # 输出类似 flannel/flannelsha256:abc123... # 在 kube-flannel.yml 里把 image 改成这个带 digest 的形式逻辑说明RepoDigests 是镜像内容哈希tag 变了 digest 不变。用 digest 引用后即使仓库里同名 tag 被覆盖kubelet 拉到的还是你验证过的那个。参数上index .RepoDigests 0 取第一个 digest多架构镜像可能有多个选对应架构的。另一个技巧是做一个离线包校验清单把每个镜像的 digest、tar 大小、导入后 ctr ls 的输出都记下来。交付时先跑校验脚本确认所有镜像 digest 一致再继续部署。这个习惯帮我省过很多次现场排查时间尤其是跨团队交付时对方环境和你不一样有校验清单就能快速定位是镜像问题还是配置问题。我自己的习惯是每次做离线 k8s 交付先在一台干净的测试节点上完整走一遍「拉取、导出、导入、部署、验证 Pod 网络」确认无误后再批量复制到生产节点。flannel 镜像看着简单但名字、tag、namespace、digest 这四个维度任何一个对不上都会让你在最后一公里翻车。希望帮到你。本文还有配套的精品资源点击获取