如何构建私有 Docker Hub 镜像站


Docker Hub 限制自从去年 11 月份开始,Docker 公司为了降低运营成本就对 Docker Hub 上 pull 镜像的策略进行了如下限制:

  • 未登录用户,每 6 小时只允许 pull 100 次
  • 已登录用户,每 6 小时只允许 pull 200 次

而且,限制的手段也非常地粗暴,通过判断请求镜像的 manifest 文件的次数。请求一个镜像的 manifest 文件就算作一次 pull 镜像。即便是 pull 失败了,也会算作一次。随后也有很多大佬分享绕过 Docker Hub 限制的办法,比如搭建私有的镜像仓库,然后再给客户端配置上 registry-mirrors 参数,就可以通过本地的镜像仓库来拉取镜像。

  • 突破 Docker Hub 限制,全镜像加速服务:https://moelove.info/2020/09/20/%e7%aa%81%e7%a0%b4-DockerHub-%e9%99%90%e5%88%b6%e5%85%a8%e9%95%9c%e5%83%8f%e5%8a%a0%e9%80%9f%e6%9c%8d%e5%8a%a1/
  • 绕过从 Docker Hub pull 镜像时的 429 toomanyrequests:https://nova.moe/bypass-docker-hub-429/
  • 如何绕过 Docker Hub 拉取镜像限制:https://www.chenshaowen.com/blog/how-to-cross-the-limit-of-dockerhub.html

但是以上方法都比较局限:

  • 首先镜像需要挨个手动 push 到本地镜像仓库;
  • 其次本地镜像仓库中的镜像无法和官方镜像保持同步更新,如果要使用新的 tag 好的镜像仍然需要手动将镜像从 Docker Hub 上 pull 下来,然后再 push 到本地镜像仓库;
  • 还有手动 push 镜像是比较混乱的,如果使用的镜像比较多,比如公有云容器服务,这时候再手动 push 的话管理起来是极其不方便的。

因此经过一番折腾终于摸索出了一个方案,将 Docker Hub 上 library repo 的镜像同步到本地镜像仓库,最终做到上游如果更新了镜像 tag 也能自动地将镜像同步到本地镜像仓库
获取镜像 tag对于 Docker Hub 上的镜像,我们使用到最多的就是 library 这个 repo 即 Official Images on Docker Hub(https://docs.docker.com/docker-hub/official_images/),里面包含大部分开源软件和 Linux 发行版的基础镜像。

  • Provide essential base OS repositories (for example, ubuntu, centos) that serve as the starting point for the majority of users.
  • Provide drop-in solutions for popular programming language runtimes, data stores, and other services, similar to what a Platform as a Service (PAAS) would offer.
  • Exemplify Dockerfile best practices and provide clear documentation to serve as a reference for other Dockerfile authors.
  • Ensure that security updates are applied in a timely manner. This is particularly important as Official Images are some of the most popular on Docker Hub.

library 的镜像常见的特点就是当我们使用 docker 客户端去 pull 一个镜像时,无需指定该镜像的 repo ,比如 ubuntu:latest,其他非 library 的镜像需要指定镜像所属的 repo,比如 jenkins/slave:latest。这部分代码是硬编码在 docker 的源码当中的。

我们虽然日常访问的是 https://hub.docker.com ,但是我们在 https://github.com/docker/distribution/blob/master/reference/normalize.go#L13 中可以看到实际 docker 使用的地址是一个硬编码的 docker.io

如何构建私有 Docker Hub 镜像站

我们可以通过如下几种办法来获取 Docker Hub 上 library repo 的镜像列表。
通过 docker registry 命令行在 Docker 官方文档中 docker registry 有提到可以列出某个 registry 中的镜像,但这个功能仅限于 Docker Enterprise Edition. 版本,而社区的版本中未有该命令。遂放弃……

如何构建私有 Docker Hub 镜像站

通过 registry v2 API

  • get-images.list

如何构建私有 Docker Hub 镜像站

通过 Docker Hub 的 API 获取到的镜像 tag 实在是太多了,截至今日 Docker Hub 上整个 library repo 的项目一共有 162 个,而这 162 个项目中的镜像 tag 数量多达五万两千多个。总的镜像仓库存储占用空间的大小预计至少 5TB。其中的镜像我们真正需要用到的估计也不到 0.1%,因此需要想个办法减少这个镜像列表的数量,获得的镜像列表更精确一些,通用一些。

如何构建私有 Docker Hub 镜像站

通过 official-images repo以 Debian 为例,在 Docker Hub 上镜像的 tag 基本上都是这样子的:

Supported tags and respective Dockerfile links
  • bullseyebullseye-20210208
  • bullseye-backports
  • bullseye-slimbullseye-20210208-slim
  • busterbuster-2021020810.810latest
  • buster-backports
  • buster-slimbuster-20210208-slim10.8-slim10-slim
  • experimentalexperimental-20210208
  • jessiejessie-202102088.118
  • jessie-slimjessie-20210208-slim8.11-slim8-slim

每一行都代表着同一个镜像,如:busterbuster-2021020810.810latest。一行中镜像虽然有多个 tag,但这些 tag 指向的 manifest 其实都是一致的。镜像 tag 的关系有点类似于 C 语言里的指针变量,是引用的关系。但这么多的信息是如何高效地管理的呢?于是顺藤摸瓜发现了:library repo 里的镜像构建信息都是由 official-images 这个 repo 来管理的。

如何构建私有 Docker Hub 镜像站

在这个 official-images repo 里 library 目录下有以镜像 name 命名的文件,而文件的内容记录着与 Docker Hub 相对应的 tag 信息。由此我们可以根据这个 repo 获取 library repo 镜像的 tag。好处在于虽然这样得到的镜像列表并不是全面的,但这个 repo 里记录的镜像 tag 都是官方还在维护的,并不会包含一些旧的或者 CI 测试的镜像。这样获得的镜像列表更通用一些。拿出 Linux 文本处理三剑客,一顿操作搓出了个脚本来生成镜像以及镜像的数量。惊奇的发现,通过这种方式获取到的镜像数量为 Docker Hub 的 registry API 获取到的镜像数量的十分之一左右。根据如下数据可以得出,Docker Hub 真实需要的镜像数量为 1517 个,而 5590 个镜像中包含了多个 tag 指向同一个镜像的情况,因此,我们只需要将这些相同镜像的 tag pull 一次即可,其余的镜像通过 retag 的方式打上 tag 即可。

如何构建私有 Docker Hub 镜像站


本地同步镜像获取到镜像列表之后,我们就可用使用 skopeo copy 直接将镜像 copy 到本地的镜像仓库中啦。结合上述步骤,使用不到 20 行的脚本就能完成:

如何构建私有 Docker Hub 镜像站

但事情没我想象中的那么简单,在自己的机器上 pull 了不到 150 个镜像的时候就报错退出了,提示 toomanyrequests: You have reached your pull rate limit. 错误。

ime=”2021-02-12T07:08:51Z” level=fatal msg=”Error parsing image name "docker://ubuntu:latest":
Error reading manifest latest in docker.io/library/ubuntu: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit"


GitHub Actions 同步镜像SSH 连接 runner在刚开始写这篇博客的时候也没有想到使用 GitHub Actions,在刷 GitHub 动态的时候无意间发现了它,于是又一顿操作看看 GitHub Actions 是否能用来同步镜像。首先参考 SSH 连接到 GitHub Actions 虚拟服务器连接到 runner 的机器上:

  • .github/workflows/ssh.yaml

如何构建私有 Docker Hub 镜像站

使用 SSH 连接到 action runner 的机器里意外发现,在 ~/.docker/config.json 文件里竟然已经有了个 login 的 Docker Hub 账户。哦豁.jpg。由于 docker login 的配置文件只是简单的 base64 加密,解码后拿到真实的 user 和 token。

如何构建私有 Docker Hub 镜像站

于是QQ买号地图想着可以验证一下这个账户是否有限制:

如何构建私有 Docker Hub 镜像站

但失败了,提示 {"details":"incorrect username or password"}。估计这个账户是个 bot 账户,只能用于 pull 镜像,其他的 API 请求都没权限使用。至于这个账户有没有限制,还需要做下测试。另外意外地发现 runner 的机器里集成了很多工具,其中 skopeo 也包含在内,实在是太方便了。我们就使用 skopeo inspect 去请求镜像的 manifests 文件,看看最多能请求多少会被限制。于是花了点时间搓了个脚本用于去获取 Docker Hub 上 library repo 中的所有镜像的 manifests 文件。

  • get-manifests.sh

如何构建私有 Docker Hub 镜像站

经过一番长时间的测试,在获取了 20058 个镜像的 manifest 文件之后依旧没有被限制,于是大胆猜测,runner 里内置的 Docker Hub 账户 pull library 镜像是没有限制的。估计是 GitHub 和 docker inc 达成了 py 交易,用这个账户去 pull 公共镜像没有限制。

如何构建私有 Docker Hub 镜像站

定时同步镜像从上述步骤一可知,在 GitHub Actions runner 机器里自带的 docker login 账户没有限制,那我们最终就选定使用它来同步镜像到本地 registry 吧。参照 GitHub Actions 照葫芦画瓢搓了个 action 的配置文件:

如何构建私有 Docker Hub 镜像站

增量同步默认设置的为 6 小时同步一次上游最新的代码,由于定时更新是使用的增量同步,即通过 git diff 的方式将当前分支最新的 commit 和上游 Docker Hub 官方的 repo 最新 commit 进行比较,找出变化的镜像。因此如果是首次同步,需要全量同步,在同步完成之后会给 repo 打上一个时间戳的 tag ,下次同步的时候就用这个 tag 和上游 repo 最新 commit 做差异比较。

如何构建私有 Docker Hub 镜像站


如何食用?

如果你也想将 Docker Hub 上 library repo 的镜像搞到本地镜像仓库,可以参考如下方法:劝退三连

上一篇:USB HUB


下一篇:Docker login/logout 命令