下午三点,二十几个 CI 任务挤在同一条出口 IP 上拉基础镜像,Docker Hub 甩回 429(Too Many Requests),流水线整批卡住,等下一个六小时窗口。这不是段子,Docker Hub 的限流政策白纸黑字写在官方 usage 文档里:匿名用户每 6 小时 100 次 pull,按 IPv4 地址或 IPv6 /64 子网计;认证的免费个人账号 200 次;付费的 Pro/Team/Business 才不限次。另外 429 也不全是一个来路:配额耗尽的拉取限流之外,还有并发过猛触发的滥用限流,后者连付费账号都不豁免。付费确实能解锁配额,但有两个问题它管不了:所有构建机的拉取流量都要出境走一遍 Hub,快慢看网络脸色;公司自己的私有镜像,也总得有个家。
这篇讲团队侧的解法:在内网自建一台 Docker Registry,存储后端接 RustFS,推镜像、拉镜像全部留在内网,配额这件事由你自己的磁盘说了算。个人开发者想省点拉取流量的话,那是另一条路(daemon 的 registry-mirrors,见 Distribution 官方 mirror 配方),本文不展开。
分工:谁管分发,谁管存放
Registry 本体用 Docker 官方开源的 Distribution,也就是 registry 镜像背后的项目,Apache 2.0 协议。它的存储层是插件式的:本地文件系统、S3、Azure 都能接。我们选 S3 这条路,把镜像 blob(构成镜像分层的数据块)全部放进对象存储,registry 容器本身变成无状态的,备份、扩容、容量管理都归存储层管,镜像仓库和生产数据不再抢同一块盘。
存储后端用 RustFS,一个面向 AI 时代、从零原生打造的高性能分布式对象存储,100% S3 兼容、Apache 2.0 开源。如果团队已经有一套 S3 兼容存储在跑,选 S3 后端等于把镜像仓库的运维并进现有的容量、备份、监控体系里;filesystem后端当然也能用,但那意味着 registry 要自己挂一块专属盘,备份和扩容都得单独操心。我们的目标就是前者。S3 兼容在这里是硬指标:Distribution 的 S3 驱动并不认识 RustFS,它只管对着一个 S3 端点说标准的话,端点背后是谁它不关心。
前置条件:
- 一台内网机器,装好 Docker Engine;
- RustFS 已部署,没装的话按官方 Docker 安装文档起一个单节点,评估够用;
- 任意一台能跑 AWS CLI 的机器,用来建桶和检查数据。
先给 Registry 备好桶和密钥
RustFS 的 S3 API 默认在 9000 端口,控制台在 9001。容器起好后先摸一下健康检查:
curl--failhttp://<rustfs-host>:9000/health建桶用 AWS CLI 最省事,--endpoint-url指向 RustFS 即可(用法出自 RustFS 官方 AWS CLI 文档。注意一个细节:AWS CLI 对 RustFS 走的是路径风格寻址,这个信息后面配 registry 时会再用到):
aws configure# 填访问密钥、私钥,region 填 us-east-1aws s3 mb s3://docker-registry --endpoint-url http://<rustfs-host>:9000密钥有两个来源:容器启动时通过RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY环境变量设置的根密钥,或者在控制台(9001 端口)的“访问密钥”页新建,可带过期时间和描述,操作步骤见 IAM 访问密钥文档。给 registry 单独发一把密钥比复用根密钥干净,回收也方便。还有一件容易踩的事:这把密钥要通过 registry 自己的配置传进去,S3 驱动不读 AWS 凭证环境链,指望它自动继承AWS_ACCESS_KEY_ID那套变量,push 时会撞 InvalidAccessKeyId,RustFS 官方集成指南的排查表里专门列了这条。
两个提醒:桶必须在 registry 启动前建好,Distribution 的文档写明 bucket 要在驱动初始化之前就存在;本地试玩时 RustFS 的默认密钥是 rustfsadmin/rustfsadmin,出了本机就不该再见到这对值。
Registry 侧:一份能直接抄的 config.yml
下面这份配置的键名逐个对照过官方 S3 存储驱动文档:
version:0.1log:fields:service:registrystorage:s3:accesskey:<RustFS 访问密钥>secretkey:<RustFS 私钥>region:us-east-1bucket:docker-registryregionendpoint:http://<rustfs-host>:9000forcepathstyle:truesecure:falsedelete:enabled:truehttp:addr::5000几处容易犹豫的键说明一下:
regionendpoint指向 S3 兼容存储的端点,用 Amazon S3 原厂时反而不要填它;forcepathstyle: true正好和上一节的路径风格对上。官方 S3 驱动文档对regionendpoint的说明原话是 from version 3+ it’s required to be used withforcepathstyle: true:regionendpoint会覆盖 API 域名,从 Distribution 3 起必须显式开路径风格,只填前者驱动起不来(官方为此专门开过 issue);secure: false是因为示例里 RustFS 走的是 HTTP,两者之间上了 TLS 就改回 true(自签证书的信任问题见下一节);region对 S3 兼容存储填us-east-1即可,RustFS 官方文档的 AWS CLI 示例也用这个值;delete.enabled开着,后面垃圾回收才有东西可删。
起容器,把配置文件挂到/etc/distribution/config.yml,这是 registry:3 镜像内配置文件的固定路径,容器内监听 5000 端口:
dockerrun-d--nameregistry--restartunless-stopped\-p5000:5000\-v$PWD/config.yml:/etc/distribution/config.yml\registry:3版本在这里固定一下:本文按 registry:3 写,老镜像 registry:2 对应 Distribution 2.x,行为有差异,别混着抄。对照官方材料时留意一个口径:RustFS 官方集成指南是用 registry:2 验证的(所以那页的示例看不到 forcepathstyle 这个键),registry:3 的关键差异就是必须显式开它。配置写法也有两种:全部走REGISTRY_STORAGE_S3_*环境变量(官方指南用的就是这种),或像本文这样写 config.yml,两种写法都能用(同时存在时环境变量优先,官方不建议用环境变量覆盖整节配置),本文选后者是为了把所有键一次看全。
推一遍、拉一遍,再进桶里看一眼
dockerpull busyboxdockertag busybox<registry-host>:5000/team/base/busybox:latestdockerpush<registry-host>:5000/team/base/busybox:latest另找一台机器拉同一个地址,能拉下来,仓库对外就是通的。然后到 RustFS 里确认数据真的落了桶:
aws s3lss3://docker-registry--recursive\--endpoint-url http://<rustfs-host>:9000|head能看到docker/registry/v2/开头的一串对象,那就是镜像的 layer 和 manifest(描述镜像由哪些层组成的清单文件)。想从 registry 侧再确认一遍,可以敲一下它的 API:
curlhttp://<registry-host>:5000/v2/_catalog返回的仓库列表里出现team/base/busybox就对上了。还有一个值得知道的细节:S3 后端下,registry 收到拉取请求时会返回 307,让客户端拿着预签名 URL(带签名的临时直链)去直连 RustFS,大 blob 的流量由 RustFS 直接吐给拉取方,registry 进程本身不扛传输。这意味着必须能连上 RustFS 的不只是 registry——每一台拉镜像的机器都得能直连 RustFS 的 S3 端点。本文的拓扑里大家都在内网,天然满足;但如果哪天 RustFS 只在内网可达、构建机在外面,症状会很迷惑:push 正常(上传走 registry),pull 卡死(下载被重定向)。解法也有两个:S3 驱动有个redirectendpoint参数,专门用来生成对外可达端点的预签名 URL,适合内外网端点分离的架构;或者把redirect.disable设为 true,让 registry 自己代理传输,代价是放弃直连的轻量优势。这也是这套架构里 registry 本身很轻的原因。
CI 接进来:改一个地址的事
CI 侧没有黑魔法,把镜像地址从 Docker Hub 换成内网 registry 就完事。以 GitLab CI 为例:
build:stage:buildimage:dockervariables:IMAGE:registry.internal:5000/team/app:$CI_COMMIT_SHORT_SHAscript:-echo "$REGISTRY_PASSWORD"|docker login registry.internal:5000-u "$REGISTRY_USER"--password-stdin-docker build-t "$IMAGE" .-docker push "$IMAGE"部署端同样把 pull 地址指过来。基础镜像可以定期从 Hub 同步一份进内网仓库(skopeo 之类工具都能干;这是把镜像复制进来,不是 daemon 的 mirror 代理缓存,两码事)。同步脚本也得控制节奏:Hub 的限流按拉取计,不认你用什么客户端,大批量无脑同步,同步阶段自己就先撞上 429。同步到位后,构建环节就彻底不碰 Hub 了。还有一件容易忘的事:镜像只进不出,桶迟早被废弃 layer 撑满,得靠垃圾回收定期清,执行前提见下一节。收益按克制的方式说:配额上限从 Hub 公布的那组公共数字,变成你自己的存储容量和带宽;拉取速度取决于内网和 RustFS 的吞吐,网上那些“快了 N 倍”的说法,还是以你们自己环境的实测为准。
三个高频坑
regionendpoint没带forcepathstyle。从 Distribution 3 起,regionendpoint必须搭配forcepathstyle: true,只填前者,registry 起不来,日志里是 S3 驱动初始化失败。
拉取端报http: server gave HTTP response to HTTPS client。registry 对外是 HTTP 时,每台拉取机器的 Docker daemon 都要把这个地址加进insecure-registries再重启 daemon。正式环境更建议直接给 registry 挂 TLS,Distribution 官方有不安全配置的说明页,也给了 nginx 反代终结 TLS 的配方。RustFS 自身也能上 TLS(RUSTFS_TLS_PATH环境变量,见官方 TLS 配置),registry 与它之间把secure改回 true 即可。但改 true 之前有个前提:registry 容器必须信任 RustFS 的证书。RustFS 用自签证书时,Go 的 TLS 客户端不会买账,S3 驱动握手直接失败,日志里是x509: certificate signed by unknown authority。解法是把自签 CA 装进 registry 容器的系统信任链(证书挂到/usr/local/share/ca-certificates/后执行 update-ca-certificates),或者给容器设AWS_CA_BUNDLE环境变量指向证书文件。
以为镜像数据在 registry 容器里。S3 后端下容器里几乎没有状态,删容器重建不丢数据;备份的对象是 RustFS 的数据卷和桶,registry 容器里没有需要备份的东西。清理镜像要走 registry 的垃圾回收(官方 GC 文档),且storage.delete.enabled得开着,直接去桶里删对象会让 registry 的元数据对不上。GC 本身也不是随手就能跑的:官方文档明确要求执行期间 registry 处于只读模式或干脆停掉:GC 边扫边有新镜像 push 进来,刚上传的 layer 还没被 manifest 引用住,可能被当成垃圾误删,产出损坏的镜像。正规做法是先让 registry 以storage.maintenance.readonly.enabled: true(环境变量REGISTRY_STORAGE_MAINTENANCE_READONLY_ENABLED=true)重启,跑完 GC 再切回来,所以别把 GC 裸挂成 cron 一跑了之。另外,同一支团队要是还想跑第二套 registry(比如给测试环境单开一套),可以在 S3 驱动里加一个rootdirectory键作为对象前缀,同一个桶里隔出互不干扰的命名空间,官方文档对它有说明。
上生产前的最后一张单子
- RustFS 健康检查
curl http://<host>:9000/health通; - 桶在 registry 启动前已建好,registry 用的是独立访问密钥,既不是根密钥也不是默认的 rustfsadmin;
- config.yml 里
regionendpoint、forcepathstyle: true、secure三个键逐一核对; - registry 镜像 tag 固定,别用 latest 漂着;
- CI 登录、push、部署机 pull 三步各跑通一次;
- 每台拉取机的 daemon 配置检查过
insecure-registries,或者 registry 已挂 TLS; - RustFS 若启用了 TLS,registry 容器已信任其证书(自签 CA 装进信任链,或设了
AWS_CA_BUNDLE); - 垃圾回收任务执行时切只读模式再跑,不是裸 cron。
相关入口:Distribution S3 存储驱动文档、Distribution GitHub 仓库、RustFS Docker Registry 集成指南、RustFS Harbor 集成指南(需要在同一桶上要 UI、RBAC 或复制能力时看这个)、RustFS Docker 安装、RustFS GitHub 仓库。