上周把第一批同步的容器镜像服务发出来之后,评论区一直在问“Rancher 的镜像什么时候安排”。这两天终于把 Rancher 官方在 Docker Hub 上维护的 151 个镜像仓库全部同步完成,继续沿用之前的策略:免费、不限速、不限流量,多架构镜像一个不落。如果你正在部署 Rancher、K3s,或者日常要维护基于 Rancher 的 Kubernetes 集群,这篇内容可以直接帮你省掉大量拉镜像等待时间。
先说清楚一个容易混淆的概念:151 个镜像仓库指的是 Docker Hub 上 rancher 组织名下的 151 个 repository,不是 151 个镜像文件。每个 repository 下面往往挂着十几个甚至几十个 tag,实际同步的镜像层数量是成千上万的。这篇文章我会把这次同步的完整情况、多架构同步原理、接入方式和踩坑记录都整理出来,方便你直接抄作业,也方便你之后自己搭类似服务时少走弯路。
1. 为什么把 Rancher 官方 151 个镜像仓库整体同步过来
1.1 从一次线上问题说起:镜像拉取慢并不只是慢的问题
我在几个 Rancher 技术交流群里经常看到有人说“集群节点状态不更新了”“kubelet stopped posting node status”。大多数人第一反应是 etcd 出问题、网络出问题,或者是证书过期。但实际排查过程中,我发现有一个很容易被忽略的触发点:某个关键组件镜像拉不下来,导致 Pod 一直停留在 ImagePullBackOff 状态,节点上的 kubelet 反复重启,最终节点心跳上报就异常了。
在部分网络环境下,访问 Docker Hub 的官方仓库并不是每次都能顺畅拉取。尤其是 Rancher 这种部署时要拉一大批组件的产品,只要其中一个镜像卡住,整个部署流程就卡住了。之前现场实施一套 Rancher 集群,光拉镜像就花了两三个小时,期间还反复超时。后来我干脆做了深度同步方案,把所有相关镜像提前同步到自己的镜像服务里,部署时直接替换前缀,速度才有明显改善。
所以这次第二批同步我选择了 Rancher 官方维护的整个仓库列表,本质上是想解决一个很实际的问题:让 Rancher 生态下的镜像拉取变成一件确定性的事情,而不是碰运气。
1.2 Rancher 官方仓库为什么值得整体同步
Rancher 作为 Kubernetes 多集群管理平台,它在 Docker Hub 上维护的镜像并不是只有 rancher/rancher 这一个仓库。安装完 Rancher Server 之后,它还要往下游集群分发各种 agent、Fleet 组件、监控套件、备份恢复工具。这些组件散落在几十个仓库里,Rancher 官方为了让用户离线部署方便,甚至把第三方依赖镜像也重新打包到自己的组织下面。
这也是我选择“整体同步”而不是“挑几个核心镜像同步”的核心原因。只同步 rancher/rancher 主镜像没有意义,因为安装过程中 Rancher 会自动去拉其他依赖镜像,一旦缺一个,一样会失败。官方组织下维护的 151 个仓库,基本覆盖了 Rancher 主程序、Fleet、Agent、Webhook、Backup Operator、Mirrored 第三方组件等一系列内容。把这些仓库整体同步过来,才能保证在断网或弱网环境里也能完整部署一套 Rancher。
1.3 “同步镜像”和“反代拉取”是两种完全不同的服务模式
这里我要稍微展开讲一下服务模式的区别,避免你用错方式。
常见的镜像加速服务分两类:一类是反向代理,用户请求镜像时服务端才去 Docker Hub 回源拉取,第一次访问往往很慢,而且要频繁缓存;另一类是主动同步,提前把上游镜像复制到自己的 Registry 里,用户拉取时直接命中,速度由你的存储与带宽决定,和 Docker Hub 当时的网络状态无关。
我这次做的就是主动同步方案。151 个 Rancher 官方仓库在同步周期内被完整拷贝到自建 Registry,用户访问时不会触发回源,所以拉取速度基本能做到带宽上限。这也是为什么敢说“不限速、不限流量”——因为镜像已经在本地了,回源压力被转移到了同步阶段,而不是用户请求阶段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 151 个仓库的真实构成:这些镜像分别解决什么问题
2.1 仓库分类清单
为了让你对这次同步规模有直观理解,我按用途把 151 个仓库分成了几类。实际数量会随着上游仓库调整而略有变化,但大体构成如下:
| 分类 | 典型仓库 | 说明 |
|---|---|---|
| Rancher 核心组件 | rancher/rancher、rancher/rancher-agent、rancher/rancher-webhook | Rancher Server 与集群代理 |
| Fleet/GitOps | rancher/fleet、rancher/fleet-agent、rancher/gitjob | 持续交付与 GitOps 相关 |
| 备份与恢复 | rancher/backup-restore-operator、rancher/backup-restore-operator-crd | 集群备份恢复工具 |
| Operator 框架 | rancher/rancher-operator、rancher/wrangler、rancher/terraform-controller | 各种控制器 |
| 监控告警 | rancher/prometheus-federator、rancher/mirrored-prometheus-* | 监控套件相关 |
| 依赖镜像重打包 | rancher/mirrored-* 一系列仓库 | 上游第三方镜像的官方重打包版本 |
其中 rancher/mirrored-* 这批仓库占了大头。Rancher 为了保证安装时的版本可追溯,会把一些常用的第三方镜像重新打包到自己的组织里,比如 metrics-server、coredns、nginx-ingress-controller 等。用户部署集群时,Rancher 会默认去拉这些镜像,如果只同步 rancher/rancher 而不同步 mirrored-*,依然会在创建下游集群时卡住。
2.2 每个仓库下的 tag 数量远比你想象得多
同步 151 个仓库听起来不多,但实际同步工作量很大。我简单统计了一下,rancher/rancher 一个仓库下就有几百个 tag,包括 v2.6.x、v2.7.x、v2.8.x、v2.9.x 以及 latest、alpha、rc 等。其他组件仓库虽然没这么多,但加起来 tag 总量是几千级别。
所以在同步策略上必须保留全部 tag,而不是只同步 latest 或某个特定版本。很多人在私有化部署 Rancher 时习惯固定版本,比如标准安装文档里写的是 rancher/rancher:v2.7.9,如果你只同步了 latest,到时候找不到对应 tag 一样白搭。
2.3 组件版本之间是强绑定关系
Rancher 主程序、agent、Fleet 这些组件之间存在严格的版本对应关系。rancher/rancher:v2.7.9 安装后,需要对应的 rancher/rancher-agent:v2.7.9,同时 Fleet 组件也会被安装到对应版本。如果你只挑了某一个仓库同步,很可能会出现主程序和 agent 版本不匹配,集群导入后节点无法正常注册。
这也是我坚持“按官方组织全量同步”的原因之一。把 151 个仓库整体同步过来,等于把 Rancher 官方发布过的所有版本组合都保留了一份,用户不需要自己去拼凑哪个版本配哪个镜像。
3. 多架构同步背后的机制与具体实现
3.1 理解 manifest list 和多架构镜像
Docker 镜像的多架构支持依赖一个叫 manifest list 的索引结构,也叫 fat manifest。你可以把它想象成一个总目录,里面按 CPU 架构列出了各个平台的子清单,比如 linux/amd64、linux/arm64、linux/arm/v7。每个子清单再指向真实的镜像层数据。
拉取镜像时,Docker 会根据当前机器的架构自动选择对应的子清单。所以在 amd64 服务器上拉取一个多架构镜像,你只会得到 amd64 的层;在树莓派上拉取时,会得到 arm64 的层。
这个机制带来的问题是:如果同步工具不支持 --all 参数,或者你在同步时没有显式保留所有架构,那么同步过去的镜像可能只是“半份”。比如在 amd64 机器上用旧版工具同步,服务端可能只保存了 amd64 的 manifest,用户用 arm64 节点拉取时就会报 no matching manifest for linux/arm64。
3.2 用 skopeo 批量复制多架构镜像
这次同步主要使用的是 skopeo,它比 docker pull + docker push 更适合做仓库之间的直接复制,因为它不需要把镜像层展开到本地文件系统,速度更快,也不会污染本地 Docker 存储。
核心命令是这样的:
bash复制skopeo copy --all \
--src-creds dockerhub_username:dockerhub_password \
--dest-creds registry_username:registry_password \
docker://docker.io/rancher/rancher:v2.7.9 \
docker://registry.example.com/rancher/rancher:v2.7.9
关键点就是 --all 参数。加上它之后,skopeo 会把 manifest list 以及所有平台的子 manifest 和镜像层全部复制过去;不带它的话,skopeo 只会复制当前执行命令时所在平台的镜像,这是一个非常容易踩的坑。
如果要同步整个仓库列表,我习惯先用一个文本文件保存仓库名,再配合循环脚本去做。简单示例:
bash复制cat repos.txt | while read repo; do
tags=$(skopeo list-tags docker://docker.io/rancher/$repo | jq -r '.Tags[]')
for tag in $tags; do
skopeo copy --all \
docker://docker.io/rancher/$repo:$tag \
docker://registry.example.com/rancher/$repo:$tag
done
done
实际执行时我还会用 xargs -P 4 限制并发数,避免同时发起太多请求把 Docker Hub 的限流阈值打爆。
3.3 如何验证多架构是否同步完整
同步完成后不能只看仓库数量,必须验证架构完整性。推荐使用 skopeo inspect 或 docker buildx imagetools inspect。
以 rancher/rancher:v2.7.9 为例:
bash复制docker buildx imagetools inspect registry.example.com/rancher/rancher:v2.7.9
正常输出会列出多个 Platform,比如 linux/amd64、linux/arm64、linux/s390x。如果只看到一个平台,说明同步时多架构信息丢了,需要重新用 --all 参数同步。
对于同时维护 151 个仓库的情况,我会写一个定时校验脚本,拉取每个仓库指定 tag 的 manifest 列表,与上游 Docker Hub 的架构集合做对比。架构缺失的镜像会被重新同步,确保用户在任何平台上拉取都能成功。
4. 免费不限速镜像服务的实际使用方式
4.1 两种使用方式:mirror 模式和独立前缀模式
我的镜像服务支持两种接入方式,你可以根据自己的场景选择。
第一种是 mirror 模式。把服务地址配置到 Docker daemon 的 registry-mirrors 里,之后所有 docker pull 命令都不需要改镜像名,Docker 会自动先从 mirror 拉取,拉不到再回源 Docker Hub。适合单纯想加速、不想改任何部署文件的情况。
第二种是 独立前缀模式。直接拉取 registry.example.com/rancher/rancher:v2.7.9 这样的地址,服务端把 rancher 组织下的镜像完整提供出来。这种模式适合需要明确指定镜像前缀的环境,比如 Rancher 本身的 system-default-registry 配置,或者离线内网环境里的二次分发。
4.2 Docker 配置 registry-mirrors
编辑 /etc/docker/daemon.json:
json复制{
"registry-mirrors": ["https://registry.example.com"]
}
然后重启 Docker:
bash复制systemctl restart docker
执行 docker info,在输出里看到 Registry Mirrors 字段包含对应地址就说明生效了。之后继续用原来的镜像名拉取即可:
bash复制docker pull rancher/rancher:v2.7.9
Docker 会优先走 mirror,只有 mirror 中没有这个镜像时才会回源。由于我们已经把 151 个仓库全量同步过去了,绝大多数场景不会触发回源。
4.3 拉取后如何替换回标准镜像名
在 Kubernetes 或 Rancher 部署时,很多 YAML 文件里的镜像名是写死的,比如 rancher/rancher:v2.7.9,不带任何前缀。如果你用的是独立前缀模式,可以先拉取再打 tag,这样不需要修改 YAML:
bash复制docker pull registry.example.com/rancher/rancher:v2.7.9
docker tag registry.example.com/rancher/rancher:v2.7.9 rancher/rancher:v2.7.9
这一步看着简单,但批量操作时需要小心,最好写个脚本把 tag 一并处理,避免手动敲打错字。
bash复制docker pull registry.example.com/rancher/fleet:latest
docker tag registry.example.com/rancher/fleet:latest rancher/fleet:latest
这种“拉取后改名”的方式在单机 Docker 环境里没有问题,但如果要部署到多节点集群,建议还是使用 mirror 模式,或者直接修改 Rancher 的镜像前缀配置,让每个节点都从我们的服务拉取。
5. 将镜像服务接入 Rancher / K3s 的正确姿势
5.1 K3s 中使用 containerd registry mirror
K3s 默认容器运行时是 containerd,不能直接使用 Docker 的 /etc/docker/daemon.json。需要修改 /etc/rancher/k3s/registries.yaml:
yaml复制mirrors:
docker.io:
endpoint:
- "https://registry.example.com"
改完重启 K3s:
bash复制systemctl restart k3s
这里要特别注意 YAML 格式的缩进,docker.io 是默认的镜像命名空间,K3s 拉取所有 docker.io 前缀的镜像时都会走我们配置的 endpoint。Rancher 主程序在部署下游集群时,很多组件镜像名都以 docker.io 或隐式的 rancher/ 开头,所以这个配置能让集群里的每个节点都自动命中镜像服务。
5.2 设置 system-default-registry 让 Rancher 全家桶走独立前缀
如果你更希望使用独立前缀模式,最好的方式是在启动 Rancher Server 时设置 CATTLE_SYSTEM_DEFAULT_REGISTRY 环境变量。
例如:
bash复制docker run -d --privileged \
-p 80:80 -p 443:443 \
-e CATTLE_SYSTEM_DEFAULT_REGISTRY=registry.example.com \
rancher/rancher:v2.7.9
设置之后,Rancher 在创建下游集群时,会把所有系统组件的镜像地址都加上 registry.example.com 前缀。比如原来从 Docker Hub 拉取 rancher/mirrored-metrics-server,现在会变成 registry.example.com/rancher/mirrored-metrics-server。
这个环境变量也可以在 Rancher UI 的全局设置中修改,对应的是 system-default-registry 配置项。需要注意的是,修改只对之后创建或更新的集群生效,已经存在的集群里的镜像地址不会自动替换。
5.3 离线环境里的多架构二次分发
很多读者是在内网环境部署 Rancher,没有外网访问条件。这种情况下,从镜像服务拉取后还要想办法传进内网。最常见的做法是 docker save / docker load,但这里有个坑:docker save 默认只保存当前机器架构对应的镜像,不支持直接保存完整多架构 manifest list。
所以如果是多架构离线分发,我推荐用 skopeo copy 把镜像复制到本地目录:
bash复制skopeo copy --all \
docker://registry.example.com/rancher/rancher:v2.7.9 \
dir:/data/rancher-v2.7.9
然后把本地目录拷贝到内网机器,再用 skopeo copy 导入内网 Registry。整个过程不经过 Docker daemon,能完整保留多架构信息,内网里的 amd64 和 arm64 节点都能正常使用。
6. 同步过程中踩过的坑和修复记录
6.1 旧版 skopeo 导致架构信息丢失
第一批同步时,有一批镜像在 amd64 服务器上验证正常,但同事在 arm64 节点上拉取发现报 no matching manifest for linux/arm64。一开始我还以为是网络问题,后来用 docker buildx imagetools inspect 查看同步前后的 manifest,发现目标仓库里只剩 linux/amd64 一个平台。
问题出在同步命令漏了 --all 参数。旧版 skopeo 在不带 --all 的情况下,只会复制当前平台的元数据,不会处理整个 manifest list。修复方式很简单:所有批量复制脚本统一加上 --all,然后重新同步缺失的仓库。之后我在代码仓库里加了一条检查规则,凡是出现 skopeo copy 的地方都必须显式声明 --all,防止再犯同样的错误。
6.2 Docker Hub 匿名拉取被限流
同步 151 个仓库的 tag 列表和镜像层数据时,最容易碰到的问题是 Docker Hub 的匿名拉取限流。一次全量同步跑下来,可能触发 429 Too Many Requests,镜像层只复制了一半,后续任务全部失败。
解决方式是在 skopeo 命令里加上 --src-creds,传入 Docker Hub 账号。购买一个便宜的付费 Docker Hub 订阅后,限流阈值会大幅提高。即便如此,我仍然把并发数控制在 4 到 6,并且在循环脚本里对失败任务做重试。
bash复制for i in {1..5}; do
skopeo copy --all \
--src-creds myuser:mypass \
docker://docker.io/rancher/rancher:v2.7.9 \
docker://registry.example.com/rancher/rancher:v2.7.9 \
&& break
sleep 10
done
这种“失败重试”看似简单,但在批量同步时能省下大量人工介入时间。
6.3 非标准 tag 被过滤掉
整理仓库列表后,我最初写了个过滤规则,只同步形如 v2.7.9 的版本 tag。后来发现 Rancher 官方仓库里存在大量非标准 tag,比如 sha256-xxxx 或带日期时间后缀的构建产物。如果不保留这些 tag,某些特殊版本或回滚需求就无法满足。
后来我改成直接用 skopeo list-tags docker://docker.io/rancher/$repo 获取完整 tag 列表,不做任何正则过滤,全部同步。标签占用空间多一点,但换来的是与上游完全一致的可用性。
6.4 免费服务也要防止滥用
既然承诺了不限速、不限流量,服务端就不做基于速率的限制。但这不代表没有保护措施。我在网关层对单 IP 的并发连接数做了限制,防止有人写死循环并发拉取导致带宽被打满。并发连接数限制不影响图片或镜像的下载速度,只影响同时在下载的连接数量,实际使用中个人用户不会感知到差别。
这事的核心原则是:免费公共服务必须兼顾可用性和公平性,不然遇到一两个恶意脚本,整个服务就瘫痪了,所有用户都会受影响。
6.5 定时校验上游 digest 及时发现变更
Rancher 官方仓库虽然不是天天发版,但每个月都有更新。为了保持同步内容不落后,我写了定时任务,每天检查核心仓库的 latest 和常用版本 tag 的 digest 是否与上游一致。
校验命令也比较简单:
bash复制skopeo inspect --format '{{.Digest}}' \
docker://registry.example.com/rancher/rancher:v2.7.9
skopeo inspect --format '{{.Digest}}' \
docker://docker.io/rancher/rancher:v2.7.9
如果两者不一致,就把这个 tag 重新同步一遍。日常维护不用频繁做全量同步,这种增量对齐方式既能保证数据新鲜,又能控制带宽成本。
第二批的 151 个 Rancher 官方镜像仓库目前已经全部到位,核心仓库、mirrored 依赖、多架构清单都验证过一遍。下一步我准备把 Longhorn 相关镜像也整理成第三批同步计划,毕竟 Rancher 生态里提到持久化存储就绕不开 Longhorn。如果你在接入过程中遇到报错,可以先检查是不是架构不匹配,再看 tag 是否存在,最后看是否需要配置 Docker Hub 凭据重试;还搞不定的话,把 skopeo 的完整报错贴出来,我抽空帮你定位。
