Calico v3.25.0离线包制作与导入:解决离线K8s网络插件难题

发布时间:2026/10/13 7:12:10
Calico v3.25.0离线包制作与导入:解决离线K8s网络插件难题 简介Calico 3.25.0离线包是一套完整的Kubernetes网络组件部署资源主要面向集群管理员、运维人员以及需要在断网或隔离环境中搭建集群的技术人员。离线安装是应对在线拉取镜像失败的有效手段本包将所有依赖镜像和配置进行封装实现无需外部网络即可完成插件安装。压缩包内共有三个文件整体大小约一百八十七点二四兆字节其中tar镜像压缩文件用于导入核心组件yaml编排清单用于定义部署结构txt说明文档则提供安装指引。当前已有四百五十三人学习或下载适合用于内网生产环境、离线测试与教学演示。借助该离线包可以快速部署Calico并启用网络策略同时加深对镜像导入、组件启动及配置项的理解为后续集群排障和维护提供有效支撑。这是一个兼具实用性与学习价值的资源包。1. calico-image-v3.25.0离线包到底解决的什么场景做过内部机房交付的人都有这种经历Kubernetes集群的Pod网段明明配好了节点却一直NotReadykube-system下面全是ContainerCreating。查了半天真正的缺口往往只有一个——容器网络插件Calico的镜像根本拉不下来。在没有外网访问条件的离线环境里只要提前把Calico相关镜像准备好再打包搬运到目标节点问题就能解决这也是calico-image-v3.25.0离线包存在的意义。这篇文章不绕弯子直接按做离线包的实际流程来拆先讲清楚v3.25.0离线包里需要哪些镜像再给出从拉取、导出、压缩到导入的完整操作最后把我在docker和containerd两种运行时里踩过的坑一条条摆出来。适合正在维护内网K8s集群、需要给客户交付网络插件或者自己搭离线实验环境的工程师。看完你至少能独立产出一个能用的v3.25.0离线包并且知道导入后怎么验证、出了问题看哪里。2. v3.25.0离线包有什么镜像组成与部署方式先理清做离线包最忌讳拿到一个别人给的tar就直接传因为Calico的镜像清单和部署方式强相关。先用几分钟把清单和架构理清后面能省掉大把排障时间。这一章的核心任务是让你在动手拉镜像之前知道自己到底在打包什么。2.1 Calico v3.25.0的镜像组成一份典型的images.txt长这样Calico在Kubernetes上不是一个单体组件而是由几个容器分工协作。理解每个镜像的作用才知道离线包里的镜像能不能删、怎么删。下面的表是v3.25.0默认部署里最常见的几个镜像。镜像名作用是否默认必装calico/node每台节点上的agent负责路由、隧道、访问控制策略必装calico/kube-controllers控制器监听Pod、Node、策略资源并做调谐必装calico/cniCNI插件二进制和安装脚本负责把插件放进宿主机必装calico/pod2daemon-flexvolFlexVolume驱动用于挂载Calico运行所需的证书和配置启用flexvol时需要calico/typha中间层组件帮node减轻对API Server的压力大规模集群推荐calico/apiserver可选的API服务用于网络策略管理界面集成默认不装自己动手做离线包的第一步是从Calico的manifest文件里提取image字段而不是凭记忆手写清单。假设你已经下载了v3.25.0对应的calico.yaml用下面这条命令可以拿到全部镜像名# 从calico.yaml中提取所有image字段去掉重复项 grep -oE image: [^ ] calico.yaml | awk {print $2} | sort -u这段命令的逻辑很简单grep用正则匹配image:后面到第一个空格的内容awk取第二列就是镜像名sort -u去重。需要注意如果manifest里镜像写的是image: calico/node:v3.25.0提取结果就是带tag的全名。如果写的是带sha256:...的digest形式提取结果里会带着一长串digest这种包在导入时校验更严格也更容易遇到平台不匹配问题。我一般建议离线包优先保留普通tagdigest形式适合对供应链安全要求极高的环境但操作成本明显更高。2.2 按部署方式定内容manifest安装和Operator安装是两套镜像集Calico v3.25.0支持两种主流部署方式一种是直接应用manifest另一种是用Operator编排。同样的版本离线包内容差很多。如果用manifest方式默认就是上面表格里那几个镜像包小、逻辑简单如果用Operator方式还需要额外打包operator、csi等相关镜像包体积变大排障链路也变长。个人习惯是除非团队已经有成熟的Operator运维体系否则离线交付优先选manifest方式。原因有三个。第一manifest方式直接暴露所有核心组件出问题看Pod日志就能定位第二离线包小传输压力低第三升级时只需要换镜像tag不需要处理Operator自身的版本兼容。如果你确实用了Operator那么打包时要检查kubectl get deploy -n calico-system里出现的所有镜像不能只照着网上流传的清单来。给一张对比表帮助你决定用哪种方式部署方式镜像数量管理复杂度离线包体积适合场景manifest约4-6个低小中小集群、交付交付operator约8-10个高大大规模集群、自动化运维这个版本选择直接决定了后面打包的镜像列表所以不要跳过。2.3 打包前的版本和架构核对离线包最容易在这里翻车镜像清单确认后还要确认两件事目标集群的Kubernetes版本是否在Calico v3.25.0的支持范围内以及目标节点的CPU架构是什么。Kubernetes版本太老Calico用的API字段可能不识别太新某些行为又可能被废弃。这部分以官方兼容矩阵为准不要凭感觉。打包机上先跑下面这些命令把环境信息记下来# 确认打包机的CPU架构 uname -m # 确认目标Kubernetes集群版本 kubectl version --outputjsonpath{.serverVersion.gitVersion} # 确认目标节点可能用的运行时版本 docker version --format {{.Server.Version}} ctr version说明一下参数uname -m输出x86_64、aarch64这类信息如果是x86_64后面docker pull会默认拉amd64平台镜像如果目标节点是arm64导入后大概率直接exec format error。kubectl version的output参数是为了拿到干净的版本号方便你对照兼容表。ctr version用来确认containerd存在后面决定用docker load还是ctr import。这一步不做后面所有步骤都可能白做。尤其是异构集群我见过有人把amd64的tar传到arm64节点load完后一启动就崩浪费一整晚。2.4 拉取镜像前还要决定离线包怎么落地Calico镜像拉到本地之后你有两条落地路径。一条是把镜像导出成tar再一台台导入到目标节点本地运行时另一条是推送到内网私有镜像仓库所有节点统一从内网仓库拉取。离线包这个标题通常指前者但实际交付中后者也很常见。如果走tar路线拉镜像时保持原始tag即可导入后镜像名和manifest里对得上kubelet在imagePullPolicy为IfNotPresent时不会尝试外连。如果走内网仓库路线拉完镜像后要把每个镜像重打tag成内网仓库地址然后统一push。比如# 把镜像重打tag为内网仓库地址 docker tag calico/node:v3.25.0 registry.local/calico/node:v3.25.0 docker push registry.local/calico/node:v3.25.0这里registry.local是内网镜像仓库地址的占位实际替换成你们的仓库域名和端口。push之后节点在创建Pod时会从内网仓库拉取离线环境完全可行。考虑到标题是离线包后面章节以tar方式为主线但你要知道还有仓库这条更省事的路最后一章再展开。3. 做包的实操拉镜像、存tar、压缩、校验一条龙清单理清环境确认完这一步就是把镜像真正变成可以搬运的文件。整个过程分四步拉镜像、重打tag或保持原样、docker save、压缩校验。每一小步都有值得注意的细节。3.1 用循环脚本拉全镜像避免手敲漏项镜像数量看起来不多但手敲docker pull容易漏尤其是有typha和apiserver的部署。我习惯把images.txt当作唯一事实来源再写一个循环去读它。下面是常用脚本#!/bin/bash # 从images.txt拉取所有镜像 # images.txt每一行一个镜像名支持#注释和空行 while read -r image; do # 跳过注释行和空行 if [[ $image ~ ^#.* ]] || [[ -z $image ]]; then continue fi echo Pulling: $image docker pull $image done images.txt解释一下几个关键点read -r防止镜像名里的反斜杠被转义[[ $image ~ ^#.* ]]用来跳过以#开头的注释行continue让循环继续处理下一行而不是退出。echo条打印当前拉的镜像方便你观察是否卡住。这个脚本的容错性比docker pull $(cat images.txt)好后者一旦images.txt里有注释或换行符shell会把整行当成一个参数传给docker直接报错。拉镜像期间如果某个镜像特别大不要急等待即可。拉完后可以做一次快速对账# 统计images.txt里有多少个非注释镜像 grep -vc ^# images.txt # 统计本地已存在多少Calico镜像 docker images | grep calico | wc -l如果两个数字对不上说明有镜像没拉全先排查再继续。3.2 docker save的两种写法固定列表 vs 全量文件列表镜像拉全后用docker save导出tar。这里有两种写法各有适用场景。第一种是显式列出所有镜像名适合镜像数量少、列表稳定的情况# 显式导出4个核心镜像到一个tar docker save -o calico-images-v3.25.0.tar \ calico/node:v3.25.0 \ calico/kube-controllers:v3.25.0 \ calico/cni:v3.25.0 \ calico/pod2daemon-flexvol:v3.25.0第二种是从images.txt读取适合镜像多、或者你希望脚本可复用# 从文件读取镜像列表去掉注释行后交给docker save docker save -o calico-images-v3.25.0.tar $(grep -v ^# images.txt)参数说明-o指定输出文件会生成一个包含所有镜像层的tar归档。$(grep -v ^# images.txt)用命令替换把非注释行展开成参数列表。这种写法在镜像数量几十个以内没问题如果上百个命令行参数可能超长到时候再改成逐镜像追加。还有一个容易忽略的点docker save导出的是当前机器架构对应的镜像。你在amd64机器上docker pull calico/node:v3.25.0得到的是linux/amd64平台镜像save到tar里也只会是这个平台。如果你需要同时给arm64节点用就必须在打包机上用docker pull --platform linux/arm64再拉一份arm64镜像然后单独save一个tar不能指望一个tar通吃。3.3 压缩离线包用xz还是gzip、校验文件怎么放原始tar文件体积通常在几百MB到1GB以上直接放到U盘或走网线传输压力大。我一般用xz压缩压缩率比gzip更高尤其对docker镜像这种重复层比较多的大文件效果明显。命令如下# 压缩tar为xz格式-k表示保留原tar xz -k -T0 calico-images-v3.25.0.tar # 生成SHA256校验文件 sha256sum calico-images-v3.25.0.tar.xz calico-images-v3.25.0.tar.xz.sha256参数说明-k在压缩完成后保留原始tar文件方便你对比-T0让xz使用所有CPU核心压缩速度会快很多但CPU会短暂打满。sha256sum生成的校验文件内容是“哈希值 文件名”后面传输到目标节点后可以直接用sha256sum -c验证。如果磁盘空间紧张可以在确认xz文件完整后手动删掉原始tar但建议至少在做完传输前的本地校验前保留。磁盘预算要给足docker本身存储镜像要占一份空间save出来的tar是一份xz压缩文件又是一份所以打包目录至少准备镜像总大小的3倍剩余空间。目标节点导入时也需要类似空间因为解压和导入是同时发生的。3.4 把离线包送进离线环境scp、内部共享目录和U盘的取舍包做出来后传输方式要根据现场条件选择。节点少直接scp或U盘拷贝最直接节点多用内部共享目录加脚本批量拉取更高效。# 从打包机传到目标节点假设目标节点是10.0.0.5 scp calico-images-v3.25.0.tar.xz calico-images-v3.25.0.tar.xz.sha256 user10.0.0.5:/opt/calico-pkg/scp的源文件一次传两个路径由你决定。传输完到目标节点上先做校验# 进入包所在目录校验文件完整性 cd /opt/calico-pkg sha256sum -c calico-images-v3.25.0.tar.xz.sha256输出出现OK说明文件没损坏输出FAILED就要重新拷贝。这一步绝对不能跳过U盘拷贝、网络传输都可能导致文件损坏导入时报unexpected EOF再查就晚了。如果是大批量节点不要对每台都scp更好的办法是把包放到一台中转入口挂出一个共享目录其他节点从共享目录直接读取或者先把包传到一台节点再用内网广播的方式分发。传输工具不限但校验动作必须一致。4. 导入目标节点并让Kubernetes认账docker load、ctr import与crictl检查离线包传到目标节点导入只是第一步真正的难点是让kubelet通过CRI看到这些镜像。Calico的Pod由kubelet创建kubelet只认CRI接口后面的镜像列表不认docker images。所以这一章重点解决“导入后怎么让Kubernetes认账”的问题。4.1 先确认节点用的是docker还是containerd运行时Kubernetes从1.24开始默认使用containerd作为运行时但老旧集群或某些发行版里仍然用docker。导入命令完全不同先搞清楚运行时能省去大量无效操作。# 用crictl探测当前CRI运行时类型 crictl info --output go-template --template {{.runtimeType}} 2/dev/null || echo docker这段命令的逻辑是如果系统里装了crictl且能连上CRI socket就会输出runtimeType例如containerd如果crictl不存在或连接失败命令返回非0就回退输出docker。更保险的做法是同时看socket文件和systemd服务# 检查containerd socket是否存在以及服务是否在运行 ls -l /var/run/containerd/containerd.sock 2/dev/null systemctl is-active containerd docker 2/dev/null注意systemctl is-active可以一次查多个服务它会输出每个服务的状态。如果两行输出分别是active和inactive说明两个进程都存在但kubelet最终用的是配置的CRI端点。对于离线包导入我们只需要和CRI对齐所以下一步用对应的导入命令。4.2 使用docker load导入镜像并验证镜像列表如果节点仍然由docker提供CRI兼容层也就是Kubernetes通过dockershim调用docker那么导入直接用docker load即可。这也是老版本K8s最顺滑的方式。# 解压xz包-k保留原压缩包避免重复解压 xz -dk calico-images-v3.25.0.tar.xz # 导入tar到docker docker load -i calico-images-v3.25.0.tar # 查看Calico镜像是否完整 docker images | grep -E calico.*v3.25.0参数说明-d是decompress-k保留压缩包如果你确认不再需要压缩包可以去掉-k来节省空间。docker load执行成功后每一行会输出Loaded image: calico/node:v3.25.0这就是导入成功的信号。最后一条grep命令查看REPOSITORY和TAG两列如果某个镜像名显示none说明打包时镜像tag丢失或dangling最好回到打包机重新save。这里有个容易误解的地方即使docker images能看到kubelet如果没有配置dockershim也不一定认。所以还需要用crictl确认下一节会讲。4.3 纯containerd环境用ctr导入到k8s.io命名空间如果目标节点是纯containerddocker load就用不上了。要用ctr导入而且必须指定正确的namespace。# 将tar导入containerd的k8s.io命名空间 ctr -n k8s.io images import calico-images-v3.25.0.tar # 验证kubelet能否看到镜像 crictl images | grep calico解释一下这个关键参数-n k8s.io指定containerd的命名空间为k8s.io这是CRI插件的默认命名空间kubelet只从这个命名空间读取镜像。如果你不写-n默认会导入到default命名空间ctr自己能看到crictl和kubelet都看不到。这是离线包导入里最高频的坑也是为什么我单独拿出来强调。导入过程中如果镜像tar里带digestcontainerd可能会做校验输出比较慢属正常现象。导入完成后crictl images应该能列出calico镜像。如果crictl images看不到但ctr -n k8s.io images list能看到多半是crictl连接的socket不对可以用crictl -r unix:///run/containerd/containerd.sock images指定socket再试。4.4 导入后应用manifest验证calico-node从Pending到Running镜像导入只是前置条件Calico网络插件真正生效要靠manifest里的DaemonSet和控制器。导入完成后直接在目标集群应用Calico资源# 应用Calico的manifest文件 kubectl apply -f calico.yaml # 查看calico-node Pod状态 kubectl get pods -n kube-system -l k8s-appcalico-node -o wide # 查看节点是否Ready kubectl get nodes如果Pod一直是Pending先看有没有调度问题如果Pod在Running和CrashLoopBackOff之间反复用下面的命令看日志# 查看Calico-node日志定位启动失败原因 kubectl logs -n kube-system -l k8s-appcalico-node --tail50日志是最直接的反馈源。常见问题包括CNI二进制没装到宿主机、felix启动失败、BGP配置冲突等这些在日志里都会有具体关键字。跨节点验证可以找一个已经存在的Pod进容器ping另一个节点的Pod IP# 在Pod里ping其他Pod IP确认网络通 kubectl exec -it pod-name -n namespace -- ping other-pod-ip注意pod-name和namespace要替换成实际值other-pod-ip可以从kubectl get pods -o wide拿到。这一步验证不通过说明网络策略或路由还有问题要回去看Calico配置。5. 避坑v3.25.0离线包最容易让人返工的5个问题这一章所有内容都来自我实际做离线交付时踩过又填平的坑。每条按现象、原因、解决来写直接对照你遇到的问题。5.1 镜像导入了Pod还是ImagePullBackOff现象crictl images能看到calico镜像但创建的Calico Pod一直ImagePullBackOffEvents里写着Failed to pull image calico/node:v3.25.0。原因最常见的是镜像导入到了containerd的default命名空间而不是k8s.io。其次是manifest里的imagePullPolicy被改成了Alwayskubelet会无视本地镜像存在强制尝试拉取。第三种情况是manifest里的tag和导入的tag不一致比如导入的是v3.25.0Pod里写的是latest。解决用ctr -n k8s.io images import重新导入镜像到正确命名空间修改manifest里的imagePullPolicy为IfNotPresent确保Pod里的镜像tag和你导入的完全一致一个字符都不能差。验证时用crictl images而不是docker images因为kubelet只看CRI这一层。5.2 节点是arm64导入了amd64的tar容器起不来现象容器启动后马上退出Event或日志里出现exec format error然后CrashLoopBackOff。原因docker pull在x86_64打包机上默认拉取linux/amd64平台的镜像save出来的tar也只能是amd64。把这份tar导入arm64节点CPU无法执行amd64二进制自然起不来。解决打包前确认目标节点架构。如果集群里有arm64节点必须在任意一台arm64机器上执行docker pull和save或者用docker pull --platform linux/arm64拉取指定平台镜像# 在amd64打包机上拉取arm64平台镜像 docker pull --platform linux/arm64 calico/node:v3.25.0 docker pull --platform linux/arm64 calico/cni:v3.25.0 # 导出成独立tar命名上标明平台 docker save -o calico-images-v3.25.0-arm64.tar \ calico/node:v3.25.0 \ calico/kube-controllers:v3.25.0 \ calico/cni:v3.25.0 \ calico/pod2daemon-flexvol:v3.25.0这里的--platform linux/arm64会让docker主动拉取arm64平台manifest保存时tar里的镜像层就对应arm64。如果集群同时有amd64和arm64节点就分别做两个tar不要想用一份包覆盖。5.3 calico-node频繁重启日志里出现CNI install失败现象calico-node Pod能Running但每30秒重启一次日志里出现Unable to locate CNI installation files或类似错误。原因Calico的CNI插件不是由calico-node容器负责安装的而是依赖calico/cni镜像启动一个初始化容器把二进制写到宿主机的/opt/cni/bin目录。如果你打包时漏掉了calico/cni镜像或者宿主机该目录没有写权限SELinux阻止容器写入安装就会失败。解决先确认离线包里包含了calico/cni镜像再检查宿主机目录权限# 查看CNI目录是否存在以及calico插件有没有被写进去 ls -ld /opt/cni/bin stat -c %y /opt/cni/bin/calico 2/dev/nullstat -c %y会输出calico二进制的最新修改时间如果文件不存在说明安装脚本根本没执行成功。临时排障可以放宽目录权限chmod 777 /opt/cni/bin但生产环境不建议最好调整Pod的SecurityContext或宿主机的SELinux策略给容器最小化写入权限。5.4 containerd导入成功但crictl images依然看不到镜像现象用ctr -n k8s.io images import导入tar提示成功但紧接着crictl images | grep calico没有任何输出。原因crictl默认连接的CRI socket和目标containerd不一定是同一个。有些节点装了多个容器运行时或者/etc/crictl.yaml里配置的endpoint指向了别的socket导致crictl看到的镜像列表和kubelet实际用的不一致。解决显式指定crictl的socket地址再查# 指定containerd的CRI socket验证 crictl -r unix:///run/containerd/containerd.sock images | grep calico如果这样能看到镜像说明/etc/crictl.yaml配置有问题修正里面的endpoint即可。如果依然看不到再用ctr -n k8s.io images list对比确认镜像tag是否完整。这条的排查逻辑是先确认运行时是哪个socket再确认namespace最后确认tag。5.5 解压后的tar在导入时出现unexpected EOF或layer missing现象docker load -i calico-images-v3.25.0.tar执行到一半报Error processing tar file: unexpected EOF或者导入完成后某个镜像层缺失。原因传输过程中xz文件损坏或者导入节点磁盘空间不足导致解压出来的tar被截断。也可能是U盘拷贝时文件系统缓存没刷干净。解决导入前必须先做两件事# 校验xz包哈希 sha256sum -c calico-images-v3.25.0.tar.xz.sha256 # 检查目标空间是否足够 df -h /var/lib/docker /run/containerdsha256sum -c会读校验文件里的哈希和当前文件对比输出OK才继续。df -h检查docker和containerd的数据目录剩余空间一般至少要预留镜像包大小2倍以上因为导入过程需要临时解包。如果校验失败只能重新传输不要赌运气。6. 把导入过程固化成脚本一份顺手可用的calico离线加载工具到这一步完整的离线包你已经做出来了导入流程也走通了。最后我建议把导入动作固化成一个脚本避免下次换了目标节点又重复踩坑。下面这个脚本是我习惯用的最小可用版本#!/bin/bash # calico-load.sh - 导入calico-image-v3.25.0离线包 # 用法: ./calico-load.sh [镜像tar] [运行时] set -euo pipefail ARCHIVE${1:-calico-images-v3.25.0.tar} RUNTIME${2:-auto} # 自动探测运行时类型 if [ $RUNTIME auto ]; then if crictl info --output go-template --template {{.runtimeType}} 2/dev/null | grep -qi containerd; then RUNTIMEcontainerd else RUNTIMEdocker fi fi # 导入镜像 if [ $RUNTIME docker ]; then echo docker load: $ARCHIVE docker load -i $ARCHIVE else echo ctr import: $ARCHIVE ctr -n k8s.io images import $ARCHIVE fi # 验证镜像 echo 验证Calico镜像 if command -v crictl /dev/null 21; then crictl images 2/dev/null | grep calico || docker images | grep calico else docker images | grep calico fi脚本逻辑不复杂但有两个设计值得保留。一个是set -euo pipefail只要导入或验证过程有异常脚本立即退出不会带着错误状态继续往下走另一个是运行时自动探测回退如果crictl不可用就默认按docker处理适配老节点。进阶一点的做法是把校验也加进脚本里在导入前强制检查哈希# 在load/import之前先校验压缩包 sha256sum -c $ARCHIVE.xz.sha256这样整个导入链路就成了“校验-解压-导入-验证”四步自动化不需要人工盯着。最后说一下我的个人习惯每次导入完成后我会把crictl images | grep calico的输出存成一个文本文件和离线包放在同一个目录。这样下次巡检时不需要挨个上节点查直接对比文件就知道哪台机器的镜像版本对不上。Calico离线包这件事真正难的不是镜像有多大而是环境差异带来的不确定性。如果你也在做v3.25.0离线交付希望这篇笔记能帮你少走一步弯路也希望你的离线包一次就能导入成功。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询