简介:这份资源面向正在搭建 Kubernetes 集群、需要为节点配置网络插件的运维与开发人员,集中提供 flannel 方案所需的镜像与部署清单,解决离线或内网环境下拉取镜像困难、插件版本不匹配的问题。压缩包共 3 个文件,包含两个 tar 镜像归档与一个 yaml 部署文件,整体约 27.34MB,体积轻便,便于通过 U 盘或内网仓库分发导入。其中镜像归档分别对应 flannel-cni-plugin:v1.1.2 与 flannel:v0.21.5,覆盖 CNI 插件与主进程两个核心组件;yaml 文件则用于快速下发 DaemonSet 等资源,省去逐条编写配置的麻烦。已有 1613 人学习下载,说明该组合在社区中具备一定通用性。读者可直接将镜像导入各节点并应用清单,快速打通 Pod 跨主机通信,适合作为集群初始化阶段的网络组件备料,也便于后续排查网络不通、插件未就绪等常见问题。
1. 从一次卡在 ImagePullBackOff 的部署说起:flannel 镜像到底该备哪些
上周帮朋友排查一套 Rocky Linux 上的 k8s 集群,控制节点初始化完,kubectl get nodes一直是 NotReady,describe 一看,flannel Pod 卡在ImagePullBackOff,事件里反复刷dial tcp ... i/o timeout。他问我:flannel 不是装 k8s 时自动就有的吗,怎么还要单独准备镜像包?这个问题其实特别典型。k8s 本身不内置任何 CNI 插件,flannel 是独立部署的,它的镜像得从 registry 拉。而生产环境或者内网机房,节点往往出不了外网,这时候你就得提前把 flannel 相关镜像离线备好,用docker save或ctr导出成 tar 包,再分发到每个节点导入。这份「安装 k8s 所需 flannel 必要镜像包」解决的正是这个场景:让你在无外网、弱网或者 registry 被限速的环境里,把 flannel 跑起来。适合正在做 k8s 安装部署、被镜像拉取卡住的运维和平台工程师,也适合刚学 k8s、想搞懂 CNI 镜像从哪来的人。
2. flannel 镜像包的构成与选型:为什么不是一个 tar 就够
2.1 flannel 到底依赖哪几个镜像
很多人以为 flannel 就一个镜像,实际上一套能跑起来的 flannel 部署,涉及的镜像不止一个。核心是flannel/flannel,这是跑在节点上、负责配置网络的主容器。但如果你用的是较新的 flannel 版本,它还会带一个flannel/flannel-cni-plugin,这个镜像提供 CNI 二进制,负责把 Pod 接进 flannel 网络。老版本里 CNI 插件是打包在主镜像里的,新版本拆开了,所以你会看到 DaemonSet 里有两个 initContainer 或者两个容器。
除此之外,还有一个容易被忽略的:如果你集群里用了kube-flannel.yml这种官方 manifest,它里面引用的镜像 tag 是写死的。你下载的镜像包必须和 manifest 里的 tag 完全一致,差一个字符都会继续 ImagePullBackOff。常见做法是先把 manifest 拉下来,grep 出所有image:行,再去逐个准备。
| 镜像 | 作用 | 是否必需 |
|---|---|---|
| flannel/flannel | 主容器,配置 vxlan/host-gw 后端 | 必需 |
| flannel/flannel-cni-plugin | 提供 CNI 二进制,新版本拆分 | 新版本必需 |
| quay.io/coreos/flannel | 老版本常见来源 | 视 manifest 而定 |
选型上,我一般建议直接对齐你 manifest 里的镜像地址,不要自己换 registry。有人图省事把quay.io换成某个国内镜像源,结果 tag 对不上,反而多绕一圈。镜像包的价值就在于「原样搬运」,不改地址、不改 tag。
2.2 镜像包和 manifest 的版本对齐
flannel 的镜像 tag 和 k8s 版本没有强绑定,但和 flannel 自身的 manifest 强绑定。比如你用的是 flannel v0.22 的 manifest,里面引用的就是flannel/flannel:v0.22.0。如果你手里拿到的是 v0.20 的镜像包,导入后 manifest 还是拉 v0.22,照样失败。
所以准备镜像包的第一步不是下载,而是确认版本。操作上:
# 拉取官方 manifest,确认里面引用的镜像和 tag curl -LO https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # 提取所有镜像行,这就是你要准备的清单 grep -E 'image:' kube-flannel.yml这段命令的逻辑是先拿到 manifest,再用 grep 把镜像地址全部列出来。参数上,-E是启用扩展正则,image:后面可能跟引号也可能不跟,实际输出里你会看到类似image: flannel/flannel:v0.22.0这样的行。拿到清单后,逐个docker pull再docker save,或者用skopeo copy直接转 tar,后者不需要本地 docker daemon,更干净。
提示:manifest 里的镜像地址如果带
quay.io前缀,导出时保持原样,导入时也保持原样,不要手动改 tag。
2.3 导出与导入:docker save 和 ctr 的差别
镜像包导出有两个主流路径。用 docker 的,docker save -o flannel.tar flannel/flannel:v0.22.0,导入用docker load -i flannel.tar。但 k8s 1.24 之后默认用 containerd 作为运行时,docker 导出的镜像 containerd 不一定认,因为镜像格式的 mediaType 有差异。这时候更稳的做法是用ctr:
# 用 ctr 导出,注意 -n 指定 namespace,k8s 用的是 k8s.io ctr -n k8s.io images pull flannel/flannel:v0.22.0 ctr -n k8s.io images export flannel-v0.22.0.tar flannel/flannel:v0.22.0 # 在目标节点导入 ctr -n k8s.io images import flannel-v0.22.0.tar这里的关键参数是-n k8s.io。containerd 有 namespace 概念,k8s 用的 namespace 就是k8s.io,如果你不加这个参数,导入的镜像在defaultnamespace 里,kubelet 看不到,还是会去拉。这是血泪经验,很多人导入成功但 Pod 还是 ImagePullBackOff,就是 namespace 没对上。
3. 离线环境下的完整落地流程:从导出到 Pod Running
3.1 在有网机器上准备镜像包
先找一台能出外网的机器,架构要和目标节点一致。现在很多开发机是 arm64,而服务器是 amd64,导出的镜像在目标节点上跑不起来,报exec format error。确认架构用uname -m,amd64 就是 x86_64。
# 确认架构 uname -m # 拉取并导出 flannel 主镜像 ctr -n k8s.io images pull flannel/flannel:v0.22.0 ctr -n k8s.io images export flannel-main.tar flannel/flannel:v0.22.0 # 拉取并导出 CNI 插件镜像 ctr -n k8s.io images pull flannel/flannel-cni-plugin:v1.4.0-flannel1 ctr -n k8s.io images export flannel-cni.tar flannel/flannel-cni-plugin:v1.4.0-flannel1CNI 插件的 tag 比较特殊,带-flannel1后缀,这个必须和 manifest 里完全一致。导出后可以用tar -tf flannel-main.tar | head看一眼内容,确认里面有 manifest.json 和 layer 文件。两个 tar 包加起来通常几十 MB,不算大,但少了任何一个,flannel 都起不来。
3.2 分发到各节点并导入
把 tar 包 scp 到每个节点,包括 master 和所有 worker。flannel 是 DaemonSet,每个节点都要跑,所以每个节点都得有镜像。
# 分发 scp flannel-main.tar flannel-cni.tar root@node1:/root/ # 在目标节点导入 ctr -n k8s.io images import /root/flannel-main.tar ctr -n k8s.io images import /root/flannel-cni.tar # 验证镜像已存在 ctr -n k8s.io images ls | grep flannel导入后一定要用ctr -n k8s.io images ls确认,看到flannel/flannel:v0.22.0才算成功。如果只看到docker.io/flannel/flannel这种带 registry 前缀的,而 manifest 里写的是不带前缀的,kubelet 可能还是会去拉。这时候要么改 manifest 的 image 字段,要么给镜像打 tag。我一般用ctr -n k8s.io images tag补一个和 manifest 一致的 tag,省得改 yaml。
3.3 应用 manifest 并验证网络
镜像就位后,应用 flannel 的 manifest:
kubectl apply -f kube-flannel.yml # 观察 Pod 状态 kubectl get pods -n kube-flannel -o wide -w正常情况下,几秒内 flannel Pod 会从 Init 走到 Running。如果还是 ImagePullBackOff,describe 看事件,重点看它尝试拉的镜像地址和你导入的是否一致。Running 之后,验证网络是否真的通了:
# 在节点上看 flannel 网卡 ip addr show flannel.1 # 起一个测试 Pod,看能否跨节点通信 kubectl run test --image=busybox --rm -it -- ping <另一个节点Pod的IP>flannel.1是 vxlan 设备,存在说明 flannel 后端初始化成功。跨节点 ping 通,说明 CNI 配置生效。这一步过了,节点状态才会从 NotReady 变 Ready。
注意:如果
flannel.1不存在,先看 flannel Pod 日志,常见是failed to find plugin "flannel" in path,说明 CNI 插件镜像没导入或没放到/opt/cni/bin。
4. 避坑与排查:flannel 镜像相关的五个高频翻车点
4.1 导入成功但 Pod 仍 ImagePullBackOff
现象是ctr images ls能看到镜像,但 Pod 事件里还在拉。原因通常是 namespace 不对,或者镜像 tag 和 manifest 不一致。解决:用ctr -n k8s.io images ls确认在 k8s namespace 下,再kubectl describe pod看它实际请求的镜像字符串,逐字符比对。差一个v或者后缀都会导致重新拉取。
4.2 架构不匹配导致 exec format error
现象是 Pod 起来了但立刻 CrashLoopBackOff,日志报exec format error。原因是导出镜像的机器是 arm64,目标节点是 amd64。解决:在uname -m确认架构的机器上重新导出,或者用docker manifest inspect看镜像支持的架构。跨架构搬运镜像包是新手最容易忽略的坑。
4.3 CNI 插件镜像漏导
现象是 flannel 主容器 Running,但节点还是 NotReady,flannel 日志报找不到 CNI 插件。原因是新版本 flannel 把 CNI 插件拆成独立镜像,只导了主镜像。解决:把flannel-cni-plugin也导出导入,并确认它被正确安装到/opt/cni/bin。这个目录是 kubelet 调 CNI 的默认路径。
4.4 manifest 里镜像地址带 registry 前缀
现象是导入的镜像叫flannel/flannel,但 manifest 写的是docker.io/flannel/flannel,kubelet 认为不是同一个。原因是 containerd 对镜像名的解析规则。解决:用ctr -n k8s.io images tag补一个完全一致的 tag,或者直接改 manifest 里的 image 字段为你导入的地址。
4.5 节点重启后镜像丢失
现象是重启后 flannel 又拉不到镜像。原因是 containerd 的镜像存储在临时目录,或者被 GC 清理了。解决:确认 containerd 的root配置在持久化磁盘上,别放在/tmp或内存盘。另外 kubelet 的 image GC 阈值如果设得太激进,也可能把不常用的镜像清掉,可以适当调高--image-gc-high-threshold。
5. 进阶:把 flannel 镜像包做成可复用的离线仓库
单次搬运镜像包只能救急,团队里反复用,最好把它做成一个可复用的离线仓库。我现在的习惯是,每做一个 k8s 版本,就把 flannel 相关镜像连同 manifest 一起打成一个 bundle,目录结构固定:images/放 tar,manifests/放 yaml,import.sh放导入脚本。导入脚本里把ctr -n k8s.io images import和 tag 补齐都写进去,新节点一条命令搞定。
#!/bin/bash # import-flannel.sh,在目标节点执行 set -e for img in images/*.tar; do ctr -n k8s.io images import "$img" done # 补齐 manifest 需要的 tag ctr -n k8s.io images tag flannel/flannel:v0.22.0 flannel/flannel:v0.22.0 2>/dev/null || true ctr -n k8s.io images ls | grep flannel脚本里set -e保证任何一步失败就停,避免半吊子状态。tag 那行是兜底,防止导入的镜像名和 manifest 有细微差异。验证环节我一般会加一个kubectl apply --dry-run=server先跑一遍 manifest,确认镜像字段能被 API server 接受,再真正 apply。
再进一步,如果团队有内部 registry,可以把这些镜像 push 进去,manifest 里的地址统一改成内部 registry,这样连导入都省了。但内部 registry 本身的高可用又是另一个话题,小团队用 tar 包分发反而更简单可靠。从那以后我每次准备 k8s 离线环境,都会先把 flannel 的镜像清单和 manifest 版本锁死,再动手导出,避免版本漂移。希望帮到你。
本文还有配套的精品资源,点击获取