CI 老被 Docker Hub 的 429 卡住,自建 Registry 把镜像搬进内网 下午三点二十几个 CI 任务挤在同一条出口 IP 上拉基础镜像Docker Hub 甩回 429Too 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 EngineRustFS 已部署没装的话按官方 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 时会撞 InvalidAccessKeyIdRustFS 官方集成指南的排查表里专门列了这条。两个提醒桶必须在 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: trueregionendpoint会覆盖 API 域名从 Distribution 3 起必须显式开路径风格只填前者驱动起不来官方为此专门开过 issuesecure: 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 busyboxregistry-host:5000/team/base/busybox:latestdockerpushregistry-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 侧再确认一遍可以敲一下它的 APIcurlhttp://registry-host:5000/v2/_catalog返回的仓库列表里出现team/base/busybox就对上了。还有一个值得知道的细节S3 后端下registry 收到拉取请求时会返回 307让客户端拿着预签名 URL带签名的临时直链去直连 RustFS大 blob 的流量由 RustFS 直接吐给拉取方registry 进程本身不扛传输。这意味着必须能连上 RustFS 的不只是 registry——每一台拉镜像的机器都得能直连 RustFS 的 S3 端点。本文的拓扑里大家都在内网天然满足但如果哪天 RustFS 只在内网可达、构建机在外面症状会很迷惑push 正常上传走 registrypull 卡死下载被重定向。解法也有两个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 挂 TLSDistribution 官方有不安全配置的说明页也给了 nginx 反代终结 TLS 的配方。RustFS 自身也能上 TLSRUSTFS_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_ENABLEDtrue重启跑完 GC 再切回来所以别把 GC 裸挂成 cron 一跑了之。另外同一支团队要是还想跑第二套 registry比如给测试环境单开一套可以在 S3 驱动里加一个rootdirectory键作为对象前缀同一个桶里隔出互不干扰的命名空间官方文档对它有说明。上生产前的最后一张单子RustFS 健康检查curl http://host:9000/health通桶在 registry 启动前已建好registry 用的是独立访问密钥既不是根密钥也不是默认的 rustfsadminconfig.yml 里regionendpoint、forcepathstyle: true、secure三个键逐一核对registry 镜像 tag 固定别用 latest 漂着CI 登录、push、部署机 pull 三步各跑通一次每台拉取机的 daemon 配置检查过insecure-registries或者 registry 已挂 TLSRustFS 若启用了 TLSregistry 容器已信任其证书自签 CA 装进信任链或设了AWS_CA_BUNDLE垃圾回收任务执行时切只读模式再跑不是裸 cron。相关入口Distribution S3 存储驱动文档、Distribution GitHub 仓库、RustFS Docker Registry 集成指南、RustFS Harbor 集成指南需要在同一桶上要 UI、RBAC 或复制能力时看这个、RustFS Docker 安装、RustFS GitHub 仓库。