news 2026/10/15 9:26:25

K8s离线交付:flannel镜像包从拉取到导入的全流程避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K8s离线交付:flannel镜像包从拉取到导入的全流程避坑指南

简介:在离线或网络受限环境下安装Kubernetes集群时,flannel网络插件镜像的获取常成为阻塞项,特别是国内用户常因拉取超时或版本缺失而中断部署。压缩包内含flannel v0.21.5与flannel-cni-plugin v1.1.2两个tar归档,以及配套的kube-flannel.yaml编排文件,面向K8s部署与运维人员。文件总数为3,体积仅27.34MB,tar镜像可通过docker load直接导入本地镜像库,yaml文件则定义了flannel在集群中的部署参数与DaemonSet配置,结构清晰,便于检查与调整。已有1613人学习下载,说明其确实覆盖了常见安装场景。拿到压缩包后,用户可自行加载镜像并应用清单文件,快速完成网络插件上线,省去在公网反复拉取或版本匹配的困扰,适合内网环境、离线交付以及需要快速复现K8s网络组件的学习与生产场景。

1. 安装 k8s 所需 flannel 必要镜像包:这活儿看着简单,翻车点全在镜像清单上

安装 k8s 所需 flannel 必要镜像包,这个活儿看着简单,但我见过太多集群卡在这一步:kubeadm init 顺利通过,控制平面起来了,kubectl apply -f kube-flannel.yml敲下去,kube-flannel-ds-xxxx 却一直 ImagePullBackOff。原因几乎都一样——动手之前没盘清楚"必要"到底指哪几个镜像。

flannel 从来不是一个镜像,而是一个镜像组合:主守护进程一个,CNI 插件安装用的 init 容器一个,不同版本之间还有对齐关系。只拉一个 flannel 主镜像就以为完事,是这条路上最大的坑。这篇笔记按离线交付的实际顺序,把这组镜像的清单怎么提取、版本怎么对齐、tar 包怎么打、节点上怎么导入、翻车了怎么看,一次讲透。适合用 kubeadm 搭集群、需要在离线或网络受限环境里交付 flannel 的运维和交付工程师。

2. 先盘清楚 flannel 到底需要哪几个镜像:版本对齐与缺失排查

2.1 kube-flannel.yml 里的 image 字段:主镜像和 init 镜像缺一不可

kube-flannel.yml 是一个 DaemonSet,你把它 apply 下去之后,每个符合条件的节点上都会拉起一个 kube-flannel Pod。很多人只记得"flannel 有个镜像",于是拉了一个flannel/flannel就完事,结果 Pod 起不来,因为这份清单里的镜像远不止一个。

从 flannel 官方维护的 kube-flannel.yml 来看,Pod 模板里通常会出现两类镜像:一类是主容器kube-flannel用的 flannel 镜像,它负责跑 flanneld 进程,维护 VXLAN 或 IPIP 后端、上报节点子网;另一类是 init 容器用的 CNI 插件安装镜像,负责把 flannel 的 CNI 二进制拷贝到节点的/opt/cni/bin目录,再把 CNI 配置文件写到/etc/cni/net.d/。这两个 init 容器分别在使用不同镜像,所以"必要镜像包"至少是两个镜像。具体是几个、叫什么名字,只取决于你手上这份 kube-flannel.yml,而不是网上的某篇教程。

最可靠的办法是直接把你将要 apply 的那份 YAML 拿来做文本提取,看它到底引用了哪些镜像:

grep -E '^\s+image:' kube-flannel.yml | awk '{print $2}' | sort -u

这条命令的含义:grep匹配以行首空白加image:开头的行,把 manifest 里所有镜像字段捞出来;awk '{print $2}'取第二列,也就是镜像名;sort -u去重排序。跑完之后你会得到一个类似下面的清单:

docker.io/flannel/flannel:v0.24.2 docker.io/flannel/flannel-cni-plugin:v1.4.0-flannel1

看到这个输出你就明白,主镜像flannel/flannel和 init 镜像flannel/flannel-cni-plugin缺一不可。如果你的集群用的是早期版本,镜像里可能出现quay.io/coreos/flannel这种老地址;还有少数社区整理的 manifest 会额外引用containernetworking/plugins,用来补 bridge、host-local 这类基础 CNI 插件。我的建议是:任何第三方整理的 YAML 都不要直接拿来用,先用上面这条命令做一次"镜像清单审计",确认它引用的每个镜像你都能拿到,才往下走。

2.2 版本怎么对齐:k8s 小版本、flannel 版本与 CNI 插件的匹配关系

镜像名称确定之后,第二件事是版本对齐。flannel 的版本号在语义上跟随 Kubernetes 的发布节奏,但不存在"必须严格一一对应"的硬性规则——官方 manifest 里给出的 flannel 版本就是经过验证的组合,所以最省事的做法是:从你要用的那份 kube-flannel.yml 里读版本,不要自己随便挑一个 latest。

实操中我一般这样选型:Kubernetes 1.28 到 1.30 这个区间,flannel 主镜像用 v0.24.x 到 v0.25.x 都比较常见,flannel-cni-plugin 用 v1.4.0-flannel1 这种带 CNI 规范版本号的 tag。组件之间的匹配关系主要是"flannel 主进程支持的 CNI 规范版本要能被容器运行时接受",如果 cni-plugin 镜像的 CNI 版本和 containerd 的 CNI 实现差异过大,Pod 网络初始化时会报版本协商失败。

下表是我在几个交付项目里验证过的参考区间,注意这只是经验参考,动手前一定要以官方 release 页和你手上的 YAML 为准:

Kubernetes 版本flannel 主镜像参考flannel-cni-plugin 参考
1.26 ~ 1.27flannel:v0.21.x ~ v0.23.xflannel-cni-plugin:v1.1.0-flannel1 左右
1.28 ~ 1.29flannel:v0.24.xflannel-cni-plugin:v1.4.0-flannel1
1.30 ~ 1.31flannel:v0.25.x 上下跟随官方 manifest

另外注意一点:VXLAN 和 IPIP 只是后端模式的选择,不影响镜像本身。你不需要为不同后端准备不同的镜像包,同一份镜像,改 flannel 的 ConfigMap 里的Backend.Type就行。版本对齐这步的目标只有一个:让镜像清单里的每一个 tag 都真实存在于你的交付包中,并且能被目标节点的运行时架构接受。

2.3 三条命令确认节点镜像现状:grep 清单、crictl 查询、事件定位

去目标节点上动手之前,先确认现状。最常见的翻车是:明明导入了镜像,但 kube-flannel Pod 还在拉远端。所以要在节点上用下面几条命令核对"节点实际有的镜像"和"YAML 需要的镜像"是否完全一致。

如果是 containerd 运行时,用 crictl 查(crictl 是 kubelet 视角,比 ctr 更贴近真相):

crictl images | grep -i flannel

如果是 docker 运行时(或者装了 cri-dockerd 的节点),用 docker 查:

docker images | grep -i flannel

这两条命令的输出,要和 2.1 里 grep 出来的清单逐项比对。比对的重点不是"有没有 flannel 字样",而是 registry 前缀、仓库路径、tag 三者完全一致。比如 YAML 里写docker.io/flannel/flannel:v0.24.2,节点上只有flannel/flannel:latest,那 kubelet 仍会去远端拉取,因为 tag 不匹配在镜像查找逻辑里等于"镜像不存在"。

如果 Pod 已经在跑但状态不对,直接看事件:

kubectl describe pod -n kube-flannel \ $(kubectl get pods -n kube-flannel | awk '/kube-flannel/{print $1; exit}')

在输出的 Events 尾部找Failed to pull image或ErrImagePull字样,它会把你 kubelet 尝试拉取的完整镜像名打出来——这个镜像名才是你真正要准备的对象,而不是你记忆里的那个。

三个命令组合起来,就是一个完整的"缺失镜像定位"流程:先 grep 出 YAML 清单,再在节点上查实际镜像,最后用事件确认 kubelet 到底在拉什么、为什么失败。这一章盘清楚之后,下一章就可以在能联网的机器上做打包了。

3. 在能联网的机器上准备镜像包:拉取、重打 tag、导出 tar 的完整命令

3.1 镜像源怎么选:内部仓库优先,公共镜像源做补充

准备镜像包的第一步,是找一台能拉到 flannel 镜像的机器。这台机器不一定是集群节点,我一般叫它"打包机",在整个离线交付链条里,它负责把镜像变成 tar 包,目标节点只负责导入,不再触碰公网镜像仓库。

镜像源的选择按可靠性排序,我的习惯是这样的:如果有公司内部的镜像仓库,并且里面已经同步了 flannel 相关镜像,那优先用它,因为内部仓库地址固定、并发有保障,交付文档里写死一个内网地址就行。如果没有内部仓库,就用公共镜像源做中转——常见的做法是把 flannel 官方镜像同步到一个公共镜像源的路径下,再拉下来。需要提醒的是,这类公共镜像源的域名和可用性波动比较大,今天能用的地址下周可能就失效,所以不要把它写进正式交付文档,只在准备阶段当临时通道用。下面的命令里我用registry.internal.example.com占位,实操时替换成你实际能拉到的地址。

选源有个核心原则:打包机的架构要和目标节点一致。比如目标节点是 x86_64,打包机也是 x86_64,直接 pull 出来的镜像架构天然匹配。如果打包机是 ARM 的,后面就必须用--platform显式指定架构,这个坑在第 5 章专门讲。

3.2 单镜像操作:pull、tag、save 三步走

拿到镜像源之后,单个镜像的准备就是标准的三步:拉取、改成 YAML 里的原始名字、导出。下面这段以"内部仓库路径 + 常见 flannel 版本"为例,实际执行时把 REGISTRY 换成你能拉到的地址,把版本号换成 2.1 里 grep 出来的值:

export REGISTRY=registry.internal.example.com export FLANNEL_VER=v0.24.2 export CNI_VER=v1.4.0-flannel1 docker pull ${REGISTRY}/flannel/flannel:${FLANNEL_VER} docker tag ${REGISTRY}/flannel/flannel:${FLANNEL_VER} docker.io/flannel/flannel:${FLANNEL_VER} docker pull ${REGISTRY}/flannel/flannel-cni-plugin:${CNI_VER} docker tag ${REGISTRY}/flannel/flannel-cni-plugin:${CNI_VER} docker.io/flannel/flannel-cni-plugin:${CNI_VER} docker save \ docker.io/flannel/flannel:${FLANNEL_VER} \ docker.io/flannel/flannel-cni-plugin:${CNI_VER} \ -o flannel-images.tar

这段命令里最关键的一步是docker tag。为什么要把内部仓库路径改回docker.io/flannel/flannel?因为 kube-flannel.yml 里写的就是这个名字,kubelet 在找镜像时是按 YAML 里的字符串精确匹配的。如果你把内部仓库路径的原样保存进 tar,到了目标节点上镜像名就带了一截内网前缀,和 YAML 对不上,等于白导。docker save可以把多个镜像打到一个 tar 里,导出后建议ls -lh flannel-images.tar看一眼大小,flannel 两个镜像的 tar 通常在 100 MB 上下,如果只有几 MB,八成是只打进去一个,或者镜像拉成了空壳。

参数一览:

参数含义说明
REGISTRY镜像源地址你实际能拉到镜像的仓库,内部仓库或公共镜像源
FLANNEL_VERflannel 主镜像版本必须和 YAML 里的 tag 一致
CNI_VERflannel-cni-plugin 版本必须和 YAML 里的 tag 一致

这里有个容易搞混的点:如果 YAML 里镜像名没有docker.io/前缀,比如写的是flannel/flannel:v0.24.2,那 tag 的时候也按 YAML 原样来,不要自己脑补前缀。docker 和 containerd 都会把不带前缀的镜像解析到 docker.io,但为了和 YAML 逐字对齐、也为了方便后续脚本比对,我习惯让 tar 里的镜像名和 YAML 完全一致,一种写法写到底。

3.3 批量操作:基于镜像清单写一个 5 行的准备脚本

单镜像手工操作没问题,但 flannel 往往不止一个镜像,更别提以后如果还要准备 dashboard、metrics-server 的离线包,手工复制粘贴容易漏。所以我在第二章 grep 的基础上,把准备过程写成了一个小脚本,思路是把 YAML 里的镜像清单当成唯一输入源:

#!/usr/bin/env bash set -euo pipefail YAML=${1:-kube-flannel.yml} REGISTRY=${REGISTRY:-registry.internal.example.com} IMAGES=$(grep -E '^\s+image:' "$YAML" | awk '{print $2}' | sort -u) while IFS= read -r image; do name="${image#docker.io/}" docker pull "${REGISTRY}/${name}" docker tag "${REGISTRY}/${name}" "${image}" done <<< "$IMAGES" docker save ${IMAGES} -o flannel-images.tar echo "done: flannel-images.tar"

这段脚本的逻辑:先用 grep 把 YAML 里的镜像名全部提取出来作为循环输入;${image#docker.io/}是 bash 的字符串裁剪,把docker.io/前缀去掉,拼成内部仓库路径/flannel/flannel:v0.24.2这样的拉取地址;拉下来之后 tag 回 YAML 里的原始名字,保证 tar 里的镜像名和集群期望一致;最后把所有镜像一次性 save 到一个 tar 里。注意docker save那行直接用了IMAGES变量,如果镜像清单里有重复项,sort -u已经去了重,save 不会因此报错,但会重复占用 tar 空间,所以去重这步不能省。

脚本里有个边界情况要提醒:如果 YAML 里某个镜像用了 digest 形式(比如flannel/flannel@sha256:xxx),docker save需要额外处理,digest 镜像在 save 和 load 时都要求保留摘要信息。实操中 flannel 的官方 manifest 基本都用 tag,所以我一般要求团队在离线交付阶段不要引入 digest 写法,省得脚本在边界上翻车。

3.4 导出前校验:架构与 digest 检查

save 之前,花 10 秒钟做两项校验,能省掉第 5 章一半的排障时间。第一项是架构,第二项是 digest。

docker image inspect docker.io/flannel/flannel:${FLANNEL_VER} \ --format '架构: {{.Architecture}} / {{.Os}}' docker image inspect docker.io/flannel/flannel:${FLANNEL_VER} \ --format 'digest: {{index .RepoDigests 0}}'

第一条命令打印镜像的 CPU 架构和操作系统,amd64对应 x86_64 节点,arm64对应 ARM 节点。如果打包机是 ARM 而目标节点是 x86_64,这里就会暴露出来。第二条命令打印镜像的 RepoDigest,这个值在后面导入节点后可以用来做二次校验:目标节点导入完,用crictl images看到的镜像 ID 应该和这里的 digest 对得上,如果对不上,说明 tar 在传输过程中被改动过,或者打包时拉错了副本。

还有一个不太起眼但很实用的校验:直接看 tar 内容列表,确认两个镜像都在里面:

docker save docker.io/flannel/flannel:${FLANNEL_VER} \ docker.io/flannel/flannel-cni-plugin:${CNI_VER} \ | tar -t 2>/dev/null | grep -E 'manifest.json|layer' | head -5

这条命令把 save 的输出管道给 tar 去列目录,不用落地临时文件就能看 tar 的结构。看到多个 layer 目录加一个 manifest.json,说明结构正常。这一章做完,你手上应该有一个 flannel-images.tar,里面是 YAML 所需的全部镜像,名字和 tag 都已经对齐。接下来就是把它搬到目标节点上导入。

4. 把镜像包导入目标节点:docker 与 containerd 两条路线

4.1 docker 运行时:docker load 加 tag 还原

目标节点的容器运行时决定导入方式。老一点的集群用 docker 做运行时,kubelet 通过 cri-dockerd 或 dockershim 跟 docker 通信,这种情况下的镜像导入最直接:

docker load -i flannel-images.tar docker images | grep -i flannel

docker load会把 tar 里所有的镜像连同 tag 一起还原到本地镜像仓库。因为打包阶段我们已经把镜像名 tag 成了 YAML 里的原始名字,所以 load 之后不需要再做任何改名操作,kubelet 找镜像时就能直接命中本地,不会触发远端拉取。执行完docker images确认两行记录存在,并且 REPOSITORY 和 TAG 与 YAML 一致。

docker 路线有一个隐藏要求:tar 包是在打包机上用docker save生成的,load 的目标节点 docker 版本不能太老。老版本 docker 在 load 新格式镜像时偶尔会报invalid manifest之类的错,如果遇到,先看两边的 docker 版本,差得太多就让打包机用兼容的导出方式再打一次包,或者直接改用 ctr 走 containerd 导入,绕开版本差异。

4.2 containerd 运行时:ctr 导入的两个关键参数

现在 kubeadm 装出来的集群默认就是 containerd。containerd 没有 docker load 这种一键命令,最常用的是 ctr,但它有两个参数容易踩坑,第一个是命名空间,第二个是 digest 保留:

# 错误示范:不加 -n,导入到 default 命名空间 ctr images import flannel-images.tar # 正确示范:导入到 k8s.io 命名空间,并保留 digest ctr -n k8s.io images import --digests flannel-images.tar

为什么强调-n k8s.io?因为 containerd 是多命名空间的设计,kubelet 通过 CRI 接口操作镜像时,默认落在k8s.io这个命名空间里。如果你不加-n,镜像会进入default命名空间,crictl images查不到,kubelet 也看不到,最终还是去远端拉。这是离线导入 flannel 镜像时最高频的翻车点,没有之一。

--digests参数的作用是让导入过程保留镜像的 digest 信息。保留 digest 的好处是,后续用镜像 ID 做一致性校验时,crictl images显示的 ID 能和打包时的 RepoDigest 对上;不保留也能导入、也能跑,但如果你的交付流程里有"双人复核镜像一致性"这种要求,digest 是最可靠的凭据。

还需要注意,crictl 本身不支持 import 操作,不要试图用 crictl 去导入。导入只能用 ctr,导入完成之后再用 crictl 去验证,两者的分工是:ctr 管导入,crictl 管查询,别搞混。

4.3 导入后的验证:crictl 与 kubelet 视角的一致性检查

导入完成不代表 kubelet 一定认,所以验证要看"kubelet 视角"的输出,也就是 crictl:

crictl images | grep -i flannel

输出里应该看到两行,镜像名和你 grep 出来的 YAML 清单完全一致。这里要特别说明 crictl 和 ctr 的差异:ctr -n k8s.io images list查到的是 containerd 里的存储状态,crictl images查到的是 CRI 层面的镜像列表,两者底层一致,但 crictl 会按 CRI 规范对镜像做归一化展示。判断"kubelet 能不能找到镜像",永远以 crictl 为准。

如果集群里有 kubectl 权限,还可以做一次严格比对。把 DaemonSet 模板里引用的镜像和节点实际镜像拉出来做 diff:

diff \ <(kubectl get ds kube-flannel-ds -n kube-flannel \ -o jsonpath='{range .spec.template.spec.initContainers[*]}{.image}{"\n"}{end}{range .spec.template.spec.containers[*]}{.image}{"\n"}{end}' \ | sort -u) \ <(crictl images --no-trunc | awk '{print $1":"$2}' | grep -i flannel | sort -u)

这段命令左边从 DaemonSet 模板提取所有容器和 init 容器的镜像名,右边把节点上 crictl 能看到的 flannel 镜像列出来,diff 无输出就说明完全对齐。如果只在节点上操作、没有 kubectl 权限,就退一步用"grep YAML 清单 + crictl images"两边肉眼比对,重点照样是 registry、仓库路径、tag 三者全对。

4.4 让 kubelet 不再去远端拉取:镜像名与 YAML 完全一致才算数

这一节把"完全一致"拆成三个核对点,方便你照着检查。

第一,registry 前缀。YAML 写docker.io/flannel/flannel:v0.24.2,你的镜像也必须是这个;YAML 写flannel/flannel:v0.24.2,你的镜像保持原样即可。注意不要出现"YAML 写 docker.io 前缀,节点上镜像却没有前缀"这类半对齐状态,kubelet 的镜像查找是字符串精确匹配,差一个斜杠都算没有。

第二,仓库路径。flannel/flannel和flannel/flannel-cni-plugin是两个不同仓库,导入的时候经常有人把主镜像导了两次,cni-plugin 没导。tar 包里的镜像路径必须逐项对上 YAML,多一个少一个都不行。

第三,tag。版本号、大小写、是否有 flannel1 后缀,都要逐字符一致。v0.24.2、0.24.2、latest是三个不同的 tag,kubelet 只认它看到的那一个。

如果以上三点全对,kubelet 就不会发起远端拉取,Pod 能正常进入 Running。如果还有问题,大概率是 tar 包本身或者导入参数出了问题,这就是下一章要处理的排查场景。

5. 镜像导入与 flannel 启动的常见问题排查:5 个高频翻车现场

这一章把我在交付现场见过的高频问题按"现象、原因、解决"整理出来。每个问题都对应一个具体的操作失误,按顺序对照着查,能省下大量翻日志的时间。

5.1 现象:kube-flannel Pod 一直 ImagePullBackOff,事件里明确写着拉取失败

现象:apply kube-flannel.yml 之后,kubectl get pods -n kube-flannel看到 Pod 卡在 ImagePullBackOff 或 ErrImagePull,describe 看到 Events 里有Failed to pull image的记录。

原因:节点本地镜像仓库里没有 YAML 所引用的那个精确镜像名。最常见是只导入了主镜像没导入 cni-plugin,或者 tag 对不上。

解决:按第 3 章脚本重新准备完整镜像包,按第 4 章正确导入;导入后不用重启整个 DaemonSet,删掉当前 Pod 让 kubelet 重建即可,它会重新走一遍镜像查找:

kubectl delete pod -n kube-flannel -l app=kube-flannel

删除后 kubelet 会基于 DaemonSet 的期望状态立刻重建 Pod,新 Pod 在创建时就会检查本地镜像,命中后直接进入容器启动流程。

5.2 现象:ctr 导入成功,crictl 却查不到,kubelet 继续报拉取超时

现象:节点上执行ctr images import显示 imported,看起来导入成功了,但 kubelet 日志里还在反复拉镜像,crictl images里也没有 flannel。

原因:导入时没加-n k8s.io,镜像进到了default命名空间。kubelet 只在k8s.io命名空间里找镜像,所以它认为镜像不存在,继续按照 YAML 里的地址去远端拉。

解决:用ctr -n k8s.io images import --digests重新导入;导入后先用ctr -n k8s.io images list | grep flannel确认存储层面有了,再用crictl images | grep flannel确认 kubelet 视角也有了。两步都通过,才说明这次导入真正生效。

5.3 现象:主容器起来了,init 容器卡在 Init:CrashLoopBackOff

现象:kube-flannel Pod 的主容器状态正常或等待中,但 init 容器反复重启,describe 显示 init 容器的镜像拉取失败。

原因:flannel-cni-plugin这个 init 镜像没导入。init 容器起不来,flannel 的 CNI 二进制和配置文件就不会落到/opt/cni/bin和/etc/cni/net.d,后续主容器即便启动也无法完成节点网络配置。

解决:补齐flannel-cni-plugin镜像并导入,然后删除 Pod 触发重建。这个坑的教训是:判断镜像包是否齐全,不能只看主容器有没有起来,要把 YAML 里所有 initContainers 的 image 都算进"必要镜像"。

5.4 现象:镜像导入了,Pod 一启动就报 exec format error 或直接 panic

现象:Pod 能创建,但容器启动后立刻退出,日志里有exec format error,或者 containerd 直接报无法执行。

原因:架构不匹配。ARM 架构的镜像被导入到了 x86_64 节点,或者反过来。打包机的架构和目标节点不一致,又没有用--platform指定,就会打出错误架构的镜像。

解决:打包时用--platform显式指定目标架构;导入前用docker image inspect校验 Architecture;如果交付包要覆盖两种架构的节点,按架构分别打 tar,并在文件名里注明 amd64 和 arm64,运输时别拿错:

docker pull --platform linux/amd64 ${REGISTRY}/flannel/flannel:${FLANNEL_VER}

5.5 现象:节点上 flannel 镜像有好几个,但没一个和 YAML 匹配

现象:docker images或crictl images里能看到一堆 flannel 相关的镜像,latest、v0.24、带 flannel1 后缀的都有,但 Pod 依然在拉远端。

原因:之前从不同源拉过、手工 tag 出各种变体,镜像仓库里"看起来有",但 YAML 要的那个精确字符串不存在。

解决:以 YAML 清单为唯一标准,清理多余镜像,只保留精确匹配的那个。docker 节点用docker image rm删掉多余 tag,containerd 节点在确认无用后可以用ctr -n k8s.io images del清理,不熟悉的建议先留着,只新增精确镜像,别在节点上做高危删除操作。然后按 4.4 的核对三要素逐项比对,缺哪个补哪个。

另外再提醒一个习惯:不要在节点上改 YAML 去凑镜像名。改 YAML 会带来节点间配置漂移,正确做法永远是让镜像包去匹配 YAML,而不是反过来。

6. 把镜像准备固化成脚本,并用跨节点连通性做最终验证

最后分享一个我自己的习惯:把导入动作也固化成脚本,交付现场不依赖记忆。目标节点上放一个import-flannel.sh,自动识别运行时并导入:

#!/usr/bin/env bash set -euo pipefail TAR=${1:-flannel-images.tar} if command -v ctr >/dev/null 2>&1; then ctr -n k8s.io images import --digests "$TAR" crictl images | grep -i flannel elif command -v docker >/dev/null 2>&1; then docker load -i "$TAR" docker images | grep -i flannel else echo "no container runtime found" exit 1 fi

脚本根据节点上存在ctr还是docker命令自动走对应分支,导入完直接把验证结果打印出来,现场操作的人不用记命令。配合第 3 章的打包脚本,整条链路就两个字:可重复。

镜像导入只是手段,flannel 真正干活才是目的。Pod 进入 Running 之后,我一般按下面这条链路做最终验证,每一步都对应一个明确的结论:

# 1. 确认 VXLAN 网卡已创建 ip -d link show flannel.1 # 2. 确认节点子网已经分配 cat /run/flannel/subnet.env # 3. 跨节点连通性:在两个不同节点上各起一个测试 Pod,互相 ping kubectl run ping-a --image=busybox --restart=Never --overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"node-a"}}}' -- sleep 3600 kubectl run ping-b --image=busybox --restart=Never --overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"node-b"}}}' -- sleep 3600 kubectl exec ping-a -- ping -c 3 <ping-b 的 Pod IP> # 4. 网络插件之上的 DNS 链路 kubectl run test-dns --image=busybox --rm -i --restart=Never -- nslookup kubernetes.default

flannel.1存在,说明 flanneld 启动并完成了内核侧配置;subnet.env有内容,说明节点通过 kube-apiserver 成功注册了子网;跨节点 ping 通,说明 VXLAN 或 IPIP 的封装转发真正工作;nslookup能解析,说明 CNI、kube-proxy、CoreDNS 整条链路上层都通了。四步全过,这个镜像包才算真正交付完成。

我最早给客户做离线交付时,只压了主镜像,结果在现场被 init 容器卡了半小时,最后还是靠 grep YAML 才找到缺的 cni-plugin。从那以后,我把"先 grep 出镜像清单"写成了流程的第一步,所有离线包都按这个流程出,再没在 flannel 镜像上栽过跟头。希望这些经验能帮到你,少走一段我当时走过的弯路。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 9:25:58

SSM框架在智能餐厅管理系统的实战应用

1. 项目背景与核心价值每次走进餐厅看到服务员手写点单、前台堆积如山的纸质账单时&#xff0c;我都在想&#xff1a;这套流程真的不能更高效吗&#xff1f;三年前接手某连锁餐饮品牌数字化改造项目时&#xff0c;这套基于SSM框架的智能餐厅管理系统就是我的解决方案。它不仅将…

作者头像 李华
网站建设 2026/10/15 9:25:49

OpenStack部署实战:核心组件、网络规划与Kolla-Ansible自动化

1. 部署前的规划与架构设计1.1 节点划分与资源评估很多人一上来就急着安装&#xff0c;结果装到一半发现磁盘不够、内存爆掉&#xff0c;或者网络配置一塌糊涂。OpenStack部署真正考验人的不是命令本身&#xff0c;而是前期规划。它就像装修房子&#xff0c;先把水电布局想清楚…

作者头像 李华
网站建设 2026/10/15 9:25:42

Cocos Creator 工程 .gitignore 配置指南:避免仓库膨胀与协作陷阱

1. 从一次版本库膨胀事故说起团队里新来的同事把整个library和temp目录一股脑提交进了 Git&#xff0c;两周之后仓库体积从 80MB 涨到 1.2GB&#xff0c;git clone一次要等好几分钟&#xff0c;CI 拉代码的时间直接翻倍。这件事之后&#xff0c;我把 Cocos Creator 工程的忽略规…

作者头像 李华
网站建设 2026/10/15 9:25:40

ScriptX打印控件安装部署全指南:环境核对、注册配置与避坑排查

简介&#xff1a;ScriptX打印控件安装包面向需要在浏览器端实现网页打印、套打与精准排版控制的开发者&#xff0c;尤其适合维护老旧业务系统、使用IE内核或ActiveX打印方案的技术人员。压缩包共12个文件&#xff0c;约816KB&#xff0c;以dll动态库为核心&#xff0c;配合inf驱…

作者头像 李华
网站建设 2026/10/15 9:23:37

System One模型:大模型决策延迟与成本问题的工程化解法

做 AI 应用落地这两年&#xff0c;我越来越觉得「决策」是大模型应用里最被低估的一环。你们有没有遇到过这种情况&#xff1a;让模型判断用户问题该走哪个工具&#xff0c;它洋洋洒洒先输出一大段分析&#xff0c;最后才给一个 tag&#xff1b;让模型看看请求该不该拒绝&#…

作者头像 李华
网站建设 2026/10/15 9:23:27

AI SPC赋能制造业质量数据分析:从控制图到智能预警的实践指南

格创东智AI SPC这套东西&#xff0c;我最早接触是在一个电子制造客户那里。他们做PCBA贴片&#xff0c;AOI检测出来的数据每天几千条&#xff0c;质量工程师早上第一件事就是打开Excel&#xff0c;用各种透视表和公式去拼前一天的过程报告&#xff0c;一拼就是一两个小时。等我…

作者头像 李华