离线装k8s集群:flannel镜像包准备与导入避坑指南

发布时间:2026/9/29 19:11:51
离线装k8s集群:flannel镜像包准备与导入避坑指南
简介面向Kubernetes集群搭建场景的flannel网络插件离线镜像包专为需要在内网或离线环境安装k8s的运维与开发人员准备能够解决部署过程中镜像无法拉取、网络受限等常见问题。包内共3个文件以2个tar镜像压缩包和1个yaml资源清单文件为主tar包可直接导入节点使用yaml文件则用于声明flannel相关网络对象并完成一键部署整体压缩后约27.34MB体积小巧适合作为集群安装的前置配套。镜像版本已锁定为flannel-cni-plugin:v1.1.2与flannel:v0.21.5搭配对应的kube-flannel.yaml可有效规避版本不一致带来的配置风险让用户快速拥有可用的容器网络插件。现有1613人学习下载适用于k8s集群初始化、CNI网络配置等实操场景值得正在搭建或维护Kubernetes环境的读者收藏使用。1. 装 k8s 集群最容易被 flannel 镜像卡住在内网或离线环境搭 k8s 集群时kubeadm init 和 kubeadm join 都能过唯独 flannel 的 Pod 一直 ContainerCreatingkubectl describe 一看拉取镜像超时。这就是没提前准备 flannel 必要镜像包的下场。这个镜像包不是单个镜像而是 flannel 网络插件运行所需的完整镜像集合包括 flannel 本体和 pause 基础镜像。本篇文章就围绕“安装 k8s 所需 flannel 必要镜像包”这件事讲清楚到底要准备哪几个镜像、怎么在有网机器上打包、怎么在内网节点导入、以及那些让新手翻车的版本匹配和架构问题。适合正在搭 k8s 集群、卡在 CNI 网络插件这一步的运维和开发同学。2. flannel 镜像包到底包含什么拆开 kube-flannel.yml 看依赖2.1 flannel 不是单镜像核心是 flanneld 和 pause 两个角色在准备镜像包之前先搞清楚 flannel 在 k8s 集群里跑起来需要哪些镜像。很多人以为 flannel 只有一个镜像实际上从 kube-flannel.yml 这个官方 manifest 里能看到它创建了一个 DaemonSet每个节点上跑一个 flannel Pod这个 Pod 里有两个容器一个是 flannel 主容器跑 flanneld 进程负责为每个节点分配子网、维护 VXLAN 或 host-gateway 隧道另一个是 initContainer通常是 pause 镜像负责先拉起来占住 Pod 的网络命名空间。pause 镜像是 k8s 所有 Pod 都依赖的基础镜像kubelet 在创建任何 Pod 时都会先拉取 pause。如果节点上只有 flannel 镜像而没有 pause 镜像flannel Pod 依然起不来。这就是很多人把 flannel 镜像导进去了还是报 ImagePullBackOff 的原因。2.2 版本匹配k8s 版本决定 flannel 版本flannel 版本决定镜像 tagflannel 的镜像 tag 和 k8s 版本有对应关系。一般建议 flannel 版本不低于 k8s 小版本一个数量级比如 k8s 1.28 配 flannel v0.24.x 或更新k8s 1.30 配 flannel v0.25.x 以上。这个不能只看 flannel 仓库的 README还要看 kube-flannel.yml 里实际写的 image 字段。不同版本的 flannel 镜像 tag 命名不一样有的是 v0.24.0有的是 v0.25.4还有带 -amd64 或 -arm64 后缀的。把镜像包准备错了架构arm 节点上跑 amd64 镜像Pod 直接 CrashLoopBackOff。所以第一步先拿kubectl describe pod -n kube-flannel或者直接打开 kube-flannel.yml看清楚当前要用的镜像完整名字。2.3 常见镜像清单与拉取来源以官方 kube-flannel.yml 为例默认拉取的镜像包括ghcr.io/flannel-io/flannel:v0.25.4 registry.k8s.io/pause:3.9注意是 ghcr.io 不是 docker.io很多离线环境根本访问不了 ghcr.io所以要先在有网机器上 pull 下来再 save 成 tar 包。还有一点kubeadm 初始化的集群kubelet 默认的 sandbox 镜像pause版本可能不是 3.9而是 kubeadm 配置里指定的 pause 版本。如果 kubeadm init 用的是 1.28那 pause 可能是 3.9 也可能是 3.10看 kubeadm 版本。最稳妥的做法是kubeadm config images list这个命令会列出 kubeadm 自己需要的基础镜像其中就有 pause 的 tag以那个为准。把 flannel 仓库里的 pause 版本和 kubeadm 列出来的版本都准备好避免车辘轳问题。3. 有网机器上拉镜像并打成 tar 包最常用的 docker pull save 流程3.1 先确定目标 k8s 版本和 flannel 版本这一步是后续所有操作的基础。我一般会先查一下 kubeadm 版本和 flannel 官方 release 的兼容性。kubeadm 版本用kubeadm version看flannel 版本直接去 GitHub 的 flannel-io/flannel release 页面看。选定版本后在有网机器上执行 docker pull。这里有个坑ghcr.io 拉取慢是常态建议配置 docker registry mirror或者用代理拉取。拉取时注意架构如果目标节点是 arm64就在有网机器上执行docker pull --platform linux/arm64 ghcr.io/flannel-io/flannel:v0.25.4并且docker images确认 Architecture 列是 ARM64。# 在 x86 有网机器上拉取 amd64 和 arm64 两个架构的 flannel 镜像 docker pull --platform linux/amd64 ghcr.io/flannel-io/flannel:v0.25.4 docker pull --platform linux/amd64 registry.k8s.io/pause:3.9 # 如果需要 arm64 架构加 --platform linux/arm64 重新拉参数说明--platform指定拉取镜像的目标平台不指定的话 docker 会拉当前机器的架构如果后续要传到 arm 节点上就会报 exec format error。registry.k8s.io/pause:3.9的 tag 必须和 kubeadm 实际使用的 sandbox 镜像一致分不清的话直接看 kubeadm 节点上的/var/lib/kubelet/kubeadm-flags.env文件里的--pod-infra-container-image参数。3.2 把镜像 save 成 tar 文件注意单个文件还是分文件拉完镜像后把多个镜像保存到一个 tar 包里是最省事的方式。docker save 支持多个镜像一起导出的语法但要注意这个 tar 包不一定能被 containerd 的 ctr 命令优雅地导入。docker save 导出的是 docker 的 image format在 containerd 环境下如果用ctr -n k8s.io images import导入有多数情况是能成功的但偶尔会出现导入后镜像名和 tag 变成一个 digest 的情况。所以我更推荐用docker save对每个镜像分别导出再统一传到目标节点。这样即使某个镜像导入失败也不会影响其他镜像。# 分别导出 flannel 和 pause 镜像 docker save ghcr.io/flannel-io/flannel:v0.25.4 | gzip flannel-v0.25.4-amd64.tar.gz docker save registry.k8s.io/pause:3.9 | gzip pause-3.9-amd64.tar.gz # 或者一个 tar 包含两个镜像不压缩体积大但导入兼容性好 docker save ghcr.io/flannel-io/flannel:v0.25.4 registry.k8s.io/pause:3.9 -o flannel-images.tar逻辑说明| gzip 是边导出边压缩能省一半左右空间。不压缩的 tar 导入速度其实更快因为 containerd 导入时不需要先解压。内网传输如果带宽有限就压缩如果局域网千兆以上建议直接不压缩省去导入时的 CPU 开销。3.3 用 skopeo 替代 docker 拉镜像兼容 containerd 和镜像仓库有网机器上没有 docker 但有 podman 或者只想用更标准的方式拉镜像可以用 skopeo。skopeo 直接操作镜像仓库不走 docker daemon导出的是 OCI 格式和 containerd 的兼容性更好。尤其在目标节点只有 containerd 没有 docker 的情况下skopeo copy 出来的目录结构可以直接用 ctr 导入不会出现 docker save 格式导致的意外。# 用 skopeo 拉取并直接保存为 docker-archive 格式 skopeo copy docker://ghcr.io/flannel-io/flannel:v0.25.4 docker-archive:flannel-v0.25.4.tar:ghcr.io/flannel-io/flannel:v0.25.4参数说明docker-archive:后面跟目标文件名再跟一个冒号指定保存到 tar 里的镜像名和 tag。skopeo 的好处是它能识别 registry 的认证信息也可以指定--src-creds和--dest-creds在内网有私有 Harbor 的场景下直接用 skopeo 把镜像从 ghcr.io 同步到私有仓库比 docker pull save 上传 load 四步操作少一半。缺点是 skopeo 在部分 Linux 发行版上要单独装 EPEL 源或编译安装没有 docker 那么普及。整体来看有 docker 就用 docker想省事就配置好了 skopeo 再走 skopeo。4. 把镜像包导入内网节点并完成 flannel 部署ctr 导入和 kubectl apply 是一套组合拳4.1 containerd 节点的导入ctr -n k8s.io 才是正路目标节点如果是 containerd 运行时k8s 1.24 以后默认如此直接docker load是无效的因为 containerd 没有 docker daemon 那种 load 概念。正确姿势是使用ctr -n k8s.io images import-n k8s.io指定命名空间是 k8s 自己的。kubelet 只从这个命名空间读镜像导到 default 命名空间的话 kubelet 一样找不到这是最常见的导入失败原因之一。# 在目标节点上执行先导入 pause 再导入 flannel sudo ctr -n k8s.io images import pause-3.9-amd64.tar.gz sudo ctr -n k8s.io images import flannel-v0.25.4-amd64.tar.gz # 验证镜像是否在 k8s.io 命名空间里 sudo ctr -n k8s.io images list | grep -E flannel|pause逻辑说明ctr 是 containerd 自带的命令行工具-n k8s.io表示在 k8s 命名空间内操作。导入后镜像会出现在 containerd 的内容存储里但 kubelet 真正读取的是经过 CRI 插件暴露的镜像列表所以导入完成不等于节点就认识了最好再用crictl images确认一下crictl走的是 CRI 接口和 kubelet 看到的一致。参数说明如果导入时报unpacking failed: archive/tar: invalid tar header大概率是 tar 包损坏或者压缩格式不对。docker save 默认不压缩的 tar 是标准格式gzip 之后的 tar.gz 也没问题但 skopeo 导出的 docker-archive 格式如果传输出错解压就会失败。这类问题往下看第 5 章的排查清单。4.2 从 kube-flannel.yml 部署先改 image 名字再 apply镜像导入后接下来是部署 flannel。使用官方 kube-flannel.yml 时注意把里面的 image 地址改成和导入镜像一致的完整名称。如果你导入的镜像 tag 是 v0.25.4那 manifest 里的默认 tag 要是对不上kubelet 会重新尝试去远端拉取。内网环境下这个拉取会一直卡住。# 修改 kube-flannel.yml 里的镜像地址为内网实际存在的 tag sed -i s#ghcr.io/flannel-io/flannel:v0.25.4#ghcr.io/flannel-io/flannel:v0.25.4# kube-flannel.yml # 然后部署 kubectl apply -f kube-flannel.yml这段 sed 是幂等操作的占位写法实际要看你的 kube-flannel.yml 里写的 tag 和你导入的 tag 是否一致不一致就改成一致。如果之前是 grep 出来镜像名镜像也可以用grep -n image kube-flannel.yml找到所有 image 行逐一确认。部署后检查状态kubectl get pods -n kube-flannel -o wide kubectl logs -n kube-flannel -l appflannelflannel Pod 日志里看到SubnetSet创建成功和Starting IP allocation这类输出说明 flanneld 启动是正常的。此时再看节点状态如果之前 node 是 NotReady现在应该翻成 Ready。如果 node 还是 NotReady看 kubelet 日志和 flannel 日志配合分析这属于搭配问题而不是镜像问题。4.3 如果从私有仓库拉镜像导入 registries.yaml 或直接改 imagePullPolicy有些团队会把 flannel 镜像包推到内网 Harbor然后在节点上配置/etc/containerd/certs.d或者 containerd 的registries.yaml。这种方式的好处是以后有别的镜像也往仓库推节点不用逐一导入 tar 包。配置私有仓库后kube-flannel.yml 里的 image 字段要写成harbor.example.com/flannel/flannel:v0.25.4。# /etc/containerd/certs.d/harbor.example.com/hosts.toml server https://harbor.example.com [host.https://harbor.example.com] capabilities [pull, resolve] skip_verify false注意修改 containerd 配置后要systemctl restart containerd。这个方式和离线条子导入 tar 包是两条路B 场景适合集群还在持续扩容A 场景适合一次性把镜像吐给固定节点。哪条路更省事就看你有多少节点节点超过 5 个我统一建议推私有仓库否则后续每个节点导入 tar 包会很疲惫。5. flannel 镜像包安装避坑五种翻车现象、原因与处理5.1 现象Pod 一直 ImagePullBackOffdescribe 里显示拉取失败原因节点上没有被 kubelet 识别到的 flannel 镜像。镜像导入到了 containerd 但命名空间不是 k8s.io或者ctr导入后没跑crictl images确认kubelet 不认。解决先用sudo ctr -n k8s.io images list | grep flannel确认存在再sudo crictl images | grep flannel确认 CRI 能看到。如果 ctr 能列出但 crictl 看不到说明镜像 tag 是 docker 格式的旧格式重新用ctr images import导入时加上--all-platforms参数或者改一下 tag 再导入。5.2 现象flannel Pod 起来后 CrashLoopBackOff日志报exec format error原因镜像架构不对arm64 节点上导入了 amd64 的 flannel 镜像。解决在目标节点上uname -m确认架构重新拉对应平台的镜像。如果内网传输时把 amd64 和 arm64 混淆了重拉即可。注意 arm64 的 flannel 镜像 tag 有的版本带-arm64后缀有的不带直接看镜像的 Architecture 字段最准。5.3 现象flannel Pod 正常但 node 始终 NotReady报node controller sync原因kube-flannel.yml 里默认的 Pod 网段10.244.0.0/16和 kubeadm init 时指定的--pod-network-cidr不一致。flannel 拿到冲突或错误的子网kubelet 初始化 pod CIDR 失败。解决kubeadm init 时如果用了--pod-network-cidr10.244.0.0/16那 kube-flannel.yml 里 net-conf.json 那段的 Network 字段也要是 10.244.0.0/16。这个不是镜像包本身的问题但通常是在准备镜像包之前就要确认的配置项建议把 kube-flannel.yml 的 net-conf.json 和 kubeadm-config.yaml 并排检查一遍。改完后先kubectl delete -f kube-flannel.yml再重新 apply光改 configmap 不会热生效。5.4 现象内网节点上ctr images import时卡住不动或者报dial tcp: lookup xxx失败原因镜像 tar 包里包含了多层 manifest 列表manifest listctr 导入时尝试解析所有的 manifest 和索引如果 tar 包是从一个有认证的私有仓库导出的导入时可能触发一次校验网络请求。本质上还是内网环境 DNS 或出网策略的问题。解决导入前先tar -tf flannel.tar.gz看看里面是否有包含 JSON 索引和 layer 的完整结构确保 tar 包完整。导入时加--no-unpack参数先只导入镜像元数据再让运行的时候按需解包。如果导入时确实想去掉 manifest list 的干扰用docker load在有网机器上加载一次再用docker save重新导出单个镜像的 tar往往就正常了。5.5 现象kube-flannel.yml apply 成功但 flannel DaemonSet 的 Pod 是 0 个原因kube-flannel.yml 里的 namespace 是kube-flannel但kubectl apply时没有先创建这个 namespace或者 RBAC 的 ServiceAccount 没起来。flannel 需要 ClusterRole 绑定才能读 node 和创建 CRDAPIServer 拒绝后 Pod 调度不出来。解决先kubectl create ns kube-flannel然后kubectl apply -f kube-flannel.yml。同时确认kubectl get sa -n kube-flannel里能看到 flannel 的 ServiceAccount。这个坑不是镜像包的问题但是离线条环境手动 apply 时极其常见。如果之前 apply 过但没建 namespaceAPIServer 会有报错记录kubectl describe daemonset.apps -n kube-flannel就能看到。6. 镜像包准备的高级用法多节点批量分发 验证 flannel 网络是否真的通了镜像包离线导入这件事到“kubectl get nodes 全 Ready”不算完。flannel 镜像包本身很小但集群里的场景是多个节点都要有同一份镜像。节点多了以后挨个scptar 包再ctr images import是不现实的。我一般是把 tar 包放到内网一个 HTTP 服务或直接放到 Harbor然后写一个简单的分发脚本在各节点上执行。脚本的核心是先把 tar 包拉到本地再逐节点导入#!/bin/bash # 每个节点上执行从内网 HTTP 服务拉取 flannel 镜像包并导入 containerd cd /tmp wget -q http://192.168.1.10:8080/k8s/flannel-v0.25.4-amd64.tar.gz sudo ctr -n k8s.io images import /tmp/flannel-v0.25.4-amd64.tar.gz sudo crictl images | grep -E flannel|pause echo 导入成功参数说明wget 走内网 HTTP 不占用 NameServer 资源比 scp 逐节点省事。crictl images没输出时说明导入有问题脚本里就安排退出码非 0方便做批量失败检测。这个脚本放到 cron 或 Ansible 里每个节点跑一遍即可。镜像包就绪后验证 flannel 网络是否真的通推荐三步走。先在任意一个节点上创建两个测试 Podkubectl run test-a --imagebusybox --command -- sleep 3600 kubectl run test-b --imagebusybox --command -- sleep 3600 kubectl exec -it test-a -- ping test-b的Pod IP如果 ping 通说明 flannel 的 VXLAN 隧道在同一节点或跨节点都正常。然后再验证跨节点的 service 解析kubectl exec -it test-a -- nslookup kubernetes.default。最后一步是检查 flannel 接口和路由表ip addr show flannel.1和ip route里有没有 10.244.0.0 网段的路由。这三个检查都是实际生产里的日常巡检项不要省。至于 monitring如果集群里已经装了 Prometheus可以给 flannel 开 metrics 采集。flanneld 默认在 9091 端口暴露/metrics在 kube-flannel.yml 的 DaemonSet 里加上annotations: prometheus.io/scrape: true和prometheus.io/port: 9091Prometheus 就能采集到每个节点的 flannel 隧道状态。这一步做不做取决于你的监控体系是否完善但在接入之后看flannel_network_subnet和interface相关指标比每次kubectl logs翻历史都快。我做离线环境从来都是先把镜像包准备成“多点分发”的形式容器本地导入这套流程只是第一版能跑通的最小闭环。有一条血泪经验是每个节点导入完都要跑crictl images人工或者说脚本确认一次不要做完一轮就急着 join 节点等所有节点 Ready 了再往后走。镜像版本和 kubeadm 的 pause 版本一年能坑我好几次现在每次搭环境前都会先把kubeadm config images list输出和 flannel 镜像 tag 抄在一张纸上逐一核对过再动手。希望帮到你少踩一个算一个。本文还有配套的精品资源点击获取