如何构建私有 Docker Hub 镜像站

k8s技术圈

共 6076字,需浏览 13分钟

 ·

2021-09-19 13:41

自去年 11 月份开始,Docker 公司为了降低运营成本对 Docker Hub 上 pull 镜像的策略进行了限制,本文介绍了构建私有 Docker Hub 镜像站的方式,可将 Docker Hub 上的 library repo 的镜像同步到本地镜像仓库。

作者:木子(才云)

编辑:Sarah(K8sMeetup)

K8sMeetup
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 也能自动地将镜像同步到本地镜像仓库
K8sMeetup
获取镜像 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 上 library repo 的镜像列表。
通过 docker registry 命令行
在 Docker 官方文档中 docker registry 有提到可以列出某个 registry 中的镜像,但这个功能仅限于 Docker Enterprise Edition. 版本,而社区的版本中未有该命令。遂放弃……

通过 registry v2 API
  • get-images.list

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

通过 official-images repo
以 Debian 为例,在 Docker Hub 上镜像的 tag 基本上都是这样子的:
Supported tags and respective Dockerfile links
  • bullseye, bullseye-20210208
  • bullseye-backports
  • bullseye-slim, bullseye-20210208-slim
  • buster, buster-20210208, 10.8, 10, latest
  • buster-backports
  • buster-slim, buster-20210208-slim, 10.8-slim, 10-slim
  • experimental, experimental-20210208
  • jessie, jessie-20210208, 8.11, 8
  • jessie-slim, jessie-20210208-slim, 8.11-slim, 8-slim
每一行都代表着同一个镜像,如:buster, buster-20210208, 10.8, 10, latest。一行中镜像虽然有多个 tag,但这些 tag 指向的 manifest 其实都是一致的。镜像 tag 的关系有点类似于 C 语言里的指针变量,是引用的关系。
但这么多的信息是如何高效地管理的呢?于是顺藤摸瓜发现了:library repo 里的镜像构建信息都是由 official-images 这个 repo 来管理的。

在这个 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 即可。

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

但事情没我想象中的那么简单,在自己的机器上 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"
K8sMeetup
GitHub Actions 同步镜像
SSH 连接 runner
在刚开始写这篇博客的时候也没有想到使用 GitHub Actions,在刷 GitHub 动态的时候无意间发现了它,于是又一顿操作看看 GitHub Actions 是否能用来同步镜像。
首先参考 SSH 连接到 GitHub Actions 虚拟服务器连接到 runner 的机器上:
  • .github/workflows/ssh.yaml

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

于是想着可以验证一下这个账户是否有限制:

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

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

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

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

K8sMeetup

如何食用?

如果你也想将 Docker Hub 上 library repo 的镜像搞到本地镜像仓库,可以参考如下方法:
劝退三连😂
  • 镜像仓库建议使用 docker registry 或者 harbor,具体的部署方法可以在互联网上找到。
  • 需要个大盘鸡(大硬盘机器),当前 Docker Hub 上还在维护的 tag 镜像总大小为 128 GB 左右。
  • 如果是长期使用,本地镜像仓库的存储空间至少 1TB 以上。
  • 由于是使用 GitHub Actions 的机器将镜像 push 到本地镜像仓库,因此本地镜像仓库需要有个公网 IP
增加配置
首先 fork 官方的 repo docker-library/official-images 到自己的 GitHub 账户下,然后 fork 这个 repo muzi502/sync-library-images 到自己的 GitHub 账户下,最后在自己的 sync-library-images 这个 repo 的 Settings > Secrets 中配置好如下三个变量:
  • REGISTRY_DOMAIN 设置为本地镜像仓库的域名;
  • REGISTRY_USER 设置为本地镜像仓库的用户名;
  • REGISTRY_PASSWORD 设置为本地镜像仓库的密码。
在看点一下
浏览 69
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报