☰
K8s离线部署flannel镜像包全攻略:从拉取到导入避坑
2026/10/11 13:12:46 网站建设 项目流程

简介:这份资源面向正在搭建 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 两个关键组件版本,适合单机实验、多节点部署以及内网离线安装等场景,对刚接触 k8s 网络配置的初学者和需要快速复现环境的工程师都较为友好。目前已有 1613 人学习下载,可作为搭建 flannel 网络时的镜像与清单参考,帮助减少镜像查找与版本匹配的试错成本,提升集群网络组件的部署效率。

1. 离线装 k8s 时,flannel 镜像为什么总在最后一公里翻车

很多团队在机房内网部署 Kubernetes 时,kubeadm init 一路顺畅,结果节点状态卡在 NotReady,kubectl describe node 里一行network plugin is not ready: cni config uninitialized直接把节奏打断。问题往往不在 kubelet,也不在 kube-proxy,而是 flannel 的镜像根本没进内网——外网能docker pull的镜像,到了隔离环境就变成一串ImagePullBackOff。这篇笔记就围绕「安装 k8s 所需 flannel 必要镜像包」这件事,把该拉哪些镜像、怎么导出成 tar 包、怎么在内网导入、版本怎么对齐、踩过哪些坑,按可复现的顺序讲清楚。适合正在做离线集群交付、内网机房部署、或者被 CNI 插件卡住的运维和平台工程师。核心结论先放这:flannel 不是单个镜像,而是一组镜像加一份 CNI 配置,漏掉任何一个,节点都起不来。

2. flannel 镜像清单:到底要拉哪几个,为什么不是一个

flannel 在 Kubernetes 里落地时,实际参与运行的组件不止一个容器。很多人以为docker pull flannel/flannel就够了,结果导入内网后还是起不来。原因在于 flannel 的部署方式决定了它需要多个镜像协同:负责分配网段和写入 etcd/kubernetes 的 flanneld 主进程、负责在节点上配置网桥和路由的 CNI 插件、以及某些部署模式下用到的辅助镜像。下面把清单和选型理由拆开讲。

2.1 flannel 核心镜像与辅助镜像的分工

flannel 的官方部署通常以 DaemonSet 形式跑在每个节点上,容器里跑的是 flanneld,它从 apiserver 或 etcd 读取集群网段配置,然后调用 CNI 插件在宿主机上创建 flannel.1 网桥、veth pair 和路由规则。所以至少需要两类镜像:

  • flannel 主镜像:提供 flanneld 二进制和启动脚本,是 DaemonSet 里真正跑的容器。
  • CNI 插件镜像:提供 bridge、host-local、flannel 等 CNI 二进制,kubelet 在创建 Pod 沙箱时会调用它们。很多离线包只导了主镜像,忘了 CNI 插件,结果 kubelet 报failed to find plugin "flannel" in path [/opt/cni/bin]。

如果集群用的是较新的 flannel 版本,主镜像里可能已经内置了部分 CNI 插件,但为了兼容不同 kubelet 版本和不同部署方式,稳妥做法是把 CNI 插件单独准备一份。另外,如果集群启用了 NetworkPolicy 或者用了 flannel 的 host-gw 模式,可能还需要额外的辅助镜像,但绝大多数场景下,主镜像加 CNI 插件镜像就能覆盖。

常见做法是:先确定 flannel 版本,再根据版本去拉对应的主镜像和 CNI 插件镜像。版本对齐是后面避坑章节的重点,这里先记住一个原则——flannel 版本、CNI 插件版本、kubelet 版本三者要能互相兼容,不能随便混搭。

2.2 用 docker pull 拉取 flannel 镜像的完整命令

假设你已经在一台能访问外网的机器上,准备把镜像导出成 tar 包。第一步是拉取。下面这组命令覆盖了主镜像和 CNI 插件镜像,版本号用变量统一管理,方便后面替换。

# 定义版本变量,方便统一替换 FLANNEL_VERSION="v0.24.2" CNI_PLUGIN_VERSION="v1.4.0" # 拉取 flannel 主镜像 docker pull flannel/flannel:${FLANNEL_VERSION} # 拉取 CNI 插件镜像(包含 bridge、host-local、flannel 等二进制) docker pull flannel/flannel-cni-plugin:${CNI_PLUGIN_VERSION} # 查看已拉取的镜像,确认 tag 和 IMAGE ID docker images | grep -E "flannel|flannel-cni"

逻辑说明:FLANNEL_VERSION和CNI_PLUGIN_VERSION是两个独立变量,因为 flannel 主镜像和 CNI 插件镜像的版本号并不总是同步发布。参数说明:docker pull后面跟的是仓库名:标签,标签不写默认是 latest,但生产环境绝对不要用 latest,否则内网导入后无法追溯版本。docker images用来确认镜像已经落地,重点看 IMAGE ID 和 SIZE,后面导出时会用到。

如果外网机器是 arm64 架构,而内网节点是 amd64,这里就会埋下架构不匹配的坑。拉取时可以用--platform指定架构,例如docker pull --platform linux/amd64 flannel/flannel:${FLANNEL_VERSION}。这一步不做,后面导入内网后容器会报exec format error,排查起来很费时间。

2.3 镜像导出为 tar 包:docker save 的参数与命名规范

镜像拉下来之后,下一步是导出成 tar 包,方便拷贝进内网。导出命令本身简单,但命名和分批策略有讲究。

# 创建导出目录 mkdir -p /opt/k8s-offline/flannel # 导出 flannel 主镜像 docker save -o /opt/k8s-offline/flannel/flannel-${FLANNEL_VERSION}.tar \ flannel/flannel:${FLANNEL_VERSION} # 导出 CNI 插件镜像 docker save -o /opt/k8s-offline/flannel/flannel-cni-plugin-${CNI_PLUGIN_VERSION}.tar \ flannel/flannel-cni-plugin:${CNI_PLUGIN_VERSION} # 确认 tar 包大小和数量 ls -lh /opt/k8s-offline/flannel/

逻辑说明:docker save -o把镜像导出成 tar 文件,-o后面是输出路径。参数说明:文件名里带上版本号,是为了在内网导入时一眼能看出对应关系,避免多个版本混在一起。ls -lh用来确认文件大小,flannel 主镜像通常在几十 MB 到一百多 MB 之间,CNI 插件镜像更小,如果发现某个 tar 包只有几 KB,大概率是导出失败或者镜像没拉全。

这里有个容易忽略的点:docker save默认导出的是镜像的所有层,如果镜像层很多,tar 包会比较大。可以用docker save配合gzip压缩,例如docker save flannel/flannel:${FLANNEL_VERSION} | gzip > flannel.tar.gz,内网导入时用docker load -i flannel.tar.gz也能识别。但压缩包在传输过程中如果损坏,排查起来比 tar 包麻烦,所以内网拷贝建议用 tar 包加校验和的方式。

3. 内网导入与版本对齐:让 flannel 在隔离环境跑起来

镜像包拷进内网只是第一步,真正决定 flannel 能不能跑起来的是导入后的 tag 是否正确、版本是否和 kubelet 兼容、CNI 配置是否落到了正确路径。这一章按导入、校验、配置三个环节展开,每一步都有可复现的命令和检查点。

3.1 docker load 导入镜像并校验 tag

内网机器拿到 tar 包后,用docker load导入。导入完成后必须校验 tag,因为docker save导出的镜像如果原本没有 tag,导入后可能变成<none>,导致 DaemonSet 拉不到镜像。

# 导入 flannel 主镜像 docker load -i /opt/k8s-offline/flannel/flannel-v0.24.2.tar # 导入 CNI 插件镜像 docker load -i /opt/k8s-offline/flannel/flannel-cni-plugin-v1.4.0.tar # 校验导入结果,确认 REPOSITORY 和 TAG 都存在 docker images | grep flannel # 如果发现 tag 是 <none>,手动补 tag # docker tag <IMAGE_ID> flannel/flannel:v0.24.2

逻辑说明:docker load -i从 tar 包导入镜像,导入后镜像的 REPOSITORY 和 TAG 取决于导出时的状态。参数说明:docker images | grep flannel用来确认导入结果,重点看 REPOSITORY 是不是flannel/flannel,TAG 是不是v0.24.2。如果 TAG 显示<none>,说明导出时镜像没有正确 tag,需要用docker tag手动补上,否则 DaemonSet 的 image 字段匹配不到。

校验通过后,还要确认镜像架构和内网节点一致。可以用docker inspect查看镜像的 Architecture 字段:

docker inspect flannel/flannel:v0.24.2 | grep -i architecture

如果输出是amd64而节点是 arm64,或者反过来,就需要重新拉取对应架构的镜像。这一步在混合架构集群里尤其重要,很多团队在内网导入后才发现架构不对,又得重新走一遍导出流程。

3.2 flannel 版本与 kubelet、CNI 插件的兼容性对照

版本对齐是 flannel 离线部署里最容易翻车的地方。flannel 版本太老,可能不支持新版 kubelet 的 CNI 接口;CNI 插件版本太新,可能和 flannel 主镜像里的 flanneld 不兼容。下面这张表是常见组合的对照关系,实际选型时以官方 release note 为准,这里给的是经验值。

flannel 版本推荐 CNI 插件版本兼容 kubelet 范围备注
v0.24.xv1.4.x1.24 - 1.28支持 host-gw 和 vxlan
v0.23.xv1.3.x1.22 - 1.26较稳定,适合老集群
v0.22.xv1.2.x1.20 - 1.24部分新特性缺失
v0.21.xv1.1.x1.19 - 1.23不建议用于新集群

选型理由:如果集群是新建的,kubelet 版本在 1.24 以上,优先选 flannel v0.24.x 配 CNI 插件 v1.4.x,这个组合对 NetworkPolicy 和双栈的支持更完整。如果是存量集群升级,kubelet 版本较老,就按表里对应的范围选,不要强行上新版本。参数说明:kubelet 范围是经验值,实际以 flannel 官方文档的兼容性矩阵为准,但离线环境里没法随时查文档,所以提前把对照表存到本地很有必要。

3.3 把 CNI 配置和二进制放到 kubelet 能找到的路径

flannel 的 DaemonSet 跑起来后,会在每个节点上写入 CNI 配置文件和二进制。但离线环境里,CNI 二进制往往需要提前放到/opt/cni/bin,配置文件放到/etc/cni/net.d,否则 kubelet 创建 Pod 时会找不到插件。

# 确认 CNI 二进制目录存在 mkdir -p /opt/cni/bin mkdir -p /etc/cni/net.d # 如果 CNI 插件镜像是通过容器方式提供的,可以从容器里拷贝二进制 # 假设已经用 docker create 创建了临时容器 docker create --name cni-temp flannel/flannel-cni-plugin:v1.4.0 docker cp cni-temp:/flannel /opt/cni/bin/flannel docker cp cni-temp:/bridge /opt/cni/bin/bridge docker cp cni-temp:/host-local /opt/cni/bin/host-local docker rm cni-temp # 确认二进制权限 chmod +x /opt/cni/bin/flannel /opt/cni/bin/bridge /opt/cni/bin/host-local ls -l /opt/cni/bin/

逻辑说明:docker create创建一个不启动的临时容器,docker cp从容器里把 CNI 二进制拷到宿主机。参数说明:/flannel、/bridge、/host-local是 CNI 插件镜像里二进制的常见路径,不同版本可能略有差异,可以用docker run --rm flannel/flannel-cni-plugin:v1.4.0 ls /先看一眼目录结构。chmod +x确保二进制有执行权限,否则 kubelet 调用时会报 permission denied。

CNI 配置文件通常由 flannel DaemonSet 自动生成,但离线环境里如果 DaemonSet 还没跑起来,可以手动放一份最小配置到/etc/cni/net.d/10-flannel.conflist,内容包含 flannel 和 portmap 两个插件。这一步不是必须的,但能加快排查速度——当节点 NotReady 时,先确认/etc/cni/net.d下有没有配置文件,再看/opt/cni/bin下有没有对应二进制,两个都齐了,问题基本就缩小到 flanneld 和 apiserver 的通信上了。

4. 避坑与排查:flannel 离线部署最常见的 5 个翻车现场

离线部署 flannel 的坑,大多集中在镜像、版本、路径和网络四件事上。下面这 5 条是按实际排查频率排序的,每条都按「现象 → 原因 → 解决」写,方便对照。

4.1 节点 NotReady,报错 cni config uninitialized

现象:kubectl get nodes显示节点 NotReady,kubectl describe node里 Events 出现network plugin is not ready: cni config uninitialized。

原因:kubelet 在/etc/cni/net.d下找不到任何 CNI 配置文件,或者配置文件格式不对。离线环境里,flannel DaemonSet 可能因为镜像拉不到还没跑起来,自然没人写配置。

解决:先确认 flannel DaemonSet 的 Pod 状态,kubectl get pods -n kube-flannel。如果 Pod 是 ImagePullBackOff,回到第 3 章检查镜像导入和 tag。如果 Pod 已经 Running 但节点还是 NotReady,手动检查/etc/cni/net.d下有没有10-flannel.conflist,没有就手动放一份,或者重启 kubelet 让它重新触发 CNI 配置写入。

4.2 镜像导入后 tag 变成 none,DaemonSet 拉不到

现象:docker images里能看到 flannel 镜像,但 REPOSITORY 和 TAG 都是<none>,DaemonSet 的 Pod 一直 ImagePullBackOff。

原因:docker save导出时镜像没有正确 tag,或者导出的是中间层镜像。常见于用docker save直接跟 IMAGE ID 而不是仓库名:标签的情况。

解决:用docker tag <IMAGE_ID> flannel/flannel:v0.24.2手动补 tag,然后确认 DaemonSet 的 image 字段和 tag 完全一致。注意大小写和冒号,flannel/flannel:v0.24.2和flannel/flannel:V0.24.2是两个不同的 tag。

4.3 架构不匹配,容器报 exec format error

现象:Pod 状态是 CrashLoopBackOff,kubectl logs里看到exec format error或者cannot execute binary file。

原因:外网拉取镜像的机器架构和内网节点架构不一致,比如外网是 arm64 的开发机,内网是 amd64 的服务器。

解决:在外网拉取时用docker pull --platform linux/amd64指定架构,重新导出 tar 包。如果已经导入内网,用docker inspect确认镜像 Architecture 字段,不对就重新走一遍导出导入流程。混合架构集群里,建议按架构分别准备镜像包,不要混在一个 tar 里。

4.4 flanneld 连不上 apiserver,日志报 connection refused

现象:flannel Pod 是 Running,但日志里反复出现Failed to create SubnetManager: error retrieving pod spec或者connection refused。

原因:flanneld 需要通过 apiserver 或 etcd 读取集群网段配置,如果 kubeconfig 挂载不对、apiserver 地址写错、或者网络策略挡住了 flannel Pod 到 apiserver 的流量,就会连不上。

解决:先确认 flannel DaemonSet 的 kubeconfig 挂载路径和内容,kubectl exec进 Pod 用cat看/etc/kube-flannel/kubeconfig里的 server 地址是不是 apiserver 的真实地址。如果是 etcd 模式,确认 etcd 端点可达。离线环境里还要注意,flannel Pod 用的镜像里可能没有 curl 或 nc,排查网络连通性时可以用kubectl run起一个临时 Pod 来测。

4.5 网段冲突导致 Pod 之间 ping 不通

现象:节点都是 Ready,Pod 也能创建,但跨节点 Pod 互相 ping 不通,或者 ping 通但丢包严重。

原因:flannel 默认用的网段和宿主机现有网络、其他集群网段、或者云厂商的 VPC 网段冲突。离线环境里,机房网络规划往往和测试环境不一样,很容易撞上网段。

解决:检查 flannel 的 ConfigMap 里Network字段,默认是10.244.0.0/16,确认这个网段和宿主机路由表、VPC 网段没有重叠。如果有冲突,改成一个空闲网段,然后重启 flannel DaemonSet。改网段后,已经创建的 Pod 需要重建才能拿到新网段的 IP。

5. 进阶技巧:用校验和与版本锁把离线包做成可复用的交付物

前面讲的是一次性部署的流程,但如果团队经常要做离线交付,每次都手动拉镜像、导出、导入,迟早会出错。更稳的做法是把 flannel 离线包做成一个带校验和、带版本锁、带导入脚本的交付物,下次直接复用。这一章讲三个具体技巧,都是我在多次交付里攒下来的习惯。

5.1 给每个 tar 包生成 sha256 校验和

镜像包在拷贝和传输过程中可能损坏,导入时报unexpected EOF或者invalid tar header,排查起来很费时间。提前生成校验和,导入前先校验,能把问题挡在导入之前。

# 在导出目录生成所有 tar 包的校验和 cd /opt/k8s-offline/flannel sha256sum *.tar > SHA256SUMS # 内网导入前先校验 sha256sum -c SHA256SUMS

逻辑说明:sha256sum *.tar为每个 tar 包生成校验和并写入SHA256SUMS文件,sha256sum -c会逐行校验并输出 OK 或 FAILED。参数说明:-c表示 check 模式,读取文件里的校验和和文件名进行比对。如果输出里有 FAILED,说明对应的 tar 包在传输中损坏,需要重新拷贝。

这个习惯看起来简单,但在跨机房拷贝几十 GB 镜像包的场景里,能省下大量排查时间。我一般会把SHA256SUMS和 tar 包放在同一目录,导入脚本里第一行就是校验,校验不过直接退出。

5.2 用版本锁文件固定 flannel 和 CNI 插件版本

版本漂移是离线交付的另一个大坑。这次交付用的是 flannel v0.24.2,下次有人拉了 v0.25.0,镜像包混在一起,导入后版本对不上,排查起来很麻烦。解决办法是用一个版本锁文件,把所有组件的版本写死。

# versions.lock 文件内容示例 FLANNEL_VERSION=v0.24.2 CNI_PLUGIN_VERSION=v1.4.0 KUBELET_COMPAT=1.24-1.28

逻辑说明:versions.lock是一个纯文本文件,记录本次交付锁定的所有版本。导出脚本和导入脚本都从这个文件读取版本变量,而不是在脚本里硬编码。参数说明:KUBELET_COMPAT是备注字段,记录兼容的 kubelet 范围,方便后续查。每次交付前更新这个文件,交付物和版本锁一起归档,下次要复现或者升级,直接看这个文件就知道当时用的什么版本。

5.3 写一个导入脚本,把 docker load 和 tag 校验串起来

手动执行docker load和docker images校验,步骤多了容易漏。写一个导入脚本,把校验和、导入、tag 检查串起来,能减少人为失误。

#!/bin/bash set -e # 导入脚本:先校验,再导入,最后检查 tag cd /opt/k8s-offline/flannel echo "校验 tar 包完整性..." sha256sum -c SHA256SUMS echo "导入 flannel 主镜像..." docker load -i flannel-${FLANNEL_VERSION}.tar echo "导入 CNI 插件镜像..." docker load -i flannel-cni-plugin-${CNI_PLUGIN_VERSION}.tar echo "检查镜像 tag..." docker images | grep -E "flannel/flannel|flannel-cni-plugin" echo "导入完成,请确认上方 tag 与 versions.lock 一致"

逻辑说明:set -e让脚本在任意命令失败时立即退出,避免校验失败还继续导入。参数说明:脚本里的${FLANNEL_VERSION}和${CNI_PLUGIN_VERSION}从versions.lock读取,可以在脚本开头加一行source versions.lock。最后的docker images | grep是人工确认点,脚本不自动判断 tag 对不对,因为 tag 的期望值可能因环境而异,留给人看一眼更稳妥。

这个脚本我一般会放在离线包的根目录,和SHA256SUMS、versions.lock、tar 包放在一起。内网机器上解压后直接bash import.sh,校验、导入、检查一步到位。如果导入过程中报错,脚本会停在出错的那一步,不会继续往下跑,排查时看最后一行输出就知道卡在哪。

最后一个习惯:每次交付完,把这次用的镜像包、版本锁、校验和、导入脚本打成一个压缩包归档,命名带上日期和集群标识。下次做类似交付时,先翻归档,能复用就复用,不能复用就在旧包基础上改版本锁,比重头拉镜像快得多。离线部署这件事,拼的不是手速,是能不能把一次性的操作变成可复用的流程。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询