Skip to content

필사 모드: 在 Docker Hub 前面放代理缓存的三种方法 — registry:2、Nexus、ECR pull-through cache

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

问题不在速度,而在限额

CI 每跑一次,FROM python:3.12-slim 就从 Docker Hub 下来一次。十个节点就是十次,一天跑一百次流水线就是一百次。Docker Hub 在数着。

Docker 文档给出的限额是这样的。未登录的客户端 每个 IPv4 地址(或 IPv6 /64 网段)每 6 小时 100 次,用个人账号登录是 200 次,Pro、Team、Business 不限。像公司办公室或集群那样多台机器从 NAT 后面用同一个地址出去时,这 100 次是大家一起分的。

我没有只凭文档相信这些数字,而是读了响应头。Docker 为查看限额专门放了一个 ratelimitpreview/test 仓库,用匿名令牌请求它的 manifest,响应头里就带着剩余次数。

TOK=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s -I -H "Authorization: Bearer $TOK" \
  https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
ratelimit-limit: 100;w=3600
ratelimit-remaining: 85;w=3600

做了几次实验期间,remaining 从 91 → 88 → 85 递减。响应头里的窗口(w=3600)以秒计写着 3,600,而文档说是 6 小时。无论哪个对,有一点是确定的:这次查看请求本身也算一次。

代理缓存就是守住这个数字的装置。集群里所有节点都去问我的缓存而不是 Docker Hub,同一镜像就只从上游下来 一次

最小的实验:把 registry:2 当作 pull-through 缓存

Docker 发布的 registry:2 镜像只需一个环境变量就变成只读代理。

docker run -d --name regmirror -p 5001:5000 \
  -v $PWD/data:/var/lib/registry \
  -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
  registry:2

这时拉取 localhost:5001/library/nginx:1.27-alpine,缓存会把没有的层从 Docker Hub 取来放在磁盘上,再交给客户端。先确认本地守护进程没有任何 nginx 的层(docker images | grep nginx 为 0 行),然后把同一镜像拉三次,每次都删掉本地副本。

尝试耗时缓存目录备注
第 1 次(经缓存)7.81 秒22M(15 个 blob 文件)缓存为空,从上游填充
第 2 次(经缓存)3.95 秒22M(无变化)直接来自磁盘
第 3 次(经缓存)4.01 秒22M(无变化)直接来自磁盘
Docker Hub 直连2.86 秒对照

docker images 报告的镜像大小是 76.8MB,缓存却是 22M。缓存保存的是压缩后的层 blob,守护进程报告的是解压后的大小。

实验说明了什么

第一次拉取会变慢。 从上游取来、写到磁盘、再转交是两步,所以比直接拉取(2.86 秒)多花了将近三倍。

第二次起也没有比直连更快。 家用线路直连 Docker Hub 是 2.86 秒,缓存填满后经代理约 4 秒。代理容器跑在同一台笔记本上,相当于多了一跳。线路快、客户端只有一台时,代理缓存 不是为速度而设的装置。

得到的是上游请求次数。 第 2、3 次拉取时缓存目录一个字节都没增加。说明层没有再从上游下载。二十个节点的集群,Docker Hub 数到的次数就从二十接近于一。这就是代理缓存的价值。

有一点要老实写下。registry:2 不会把发往上游的请求记进 info 日志,所以我没能数出每次拉取做了几次 manifest 检查。从"磁盘没有增长"这一事实,只能说没有重新下载层。

Nexus:代理、托管,以及组

registry:2 的代理只看一个上游。公司里通常既要看 Docker Hub,又要推送自己做的镜像,还想两处用一个地址。Sonatype Nexus Repository 的 Docker 仓库有三种。

  • proxy — 缓存远端仓库。Docker Hub 的话,Remote storage 填 https://registry-1.docker.io,Docker Index 选 "Use Docker Hub"(搜索由 index.docker.io 负责,所以两者分开填写)。
  • hosted — 我推送的地方。
  • group — 把多个 proxy 和 hosted 合成 一个 URL。文档把组称为"向用户以只读方式暴露全部仓库的推荐方式",成员按"期望的顺序"加入。往组里加入新仓库后,不改客户端配置也能立刻看到。

客户端侧的配置,只需把一个 Repository Connector 端口(例如 nexus.example.com:8082)注册为守护进程的 mirror。此后 docker pull nginx 按组 → 成员的顺序查找,hosted 里没有就由 proxy 从 Docker Hub 取来并缓存。

Nexus 把 ECR 作为上游 时,认证比较特别。ECR 的令牌有效期是 12 小时,按 Nexus 文档,它在第一次拉取时缓存该令牌,并每六小时刷新一次。把 AWS 凭据交给 Nexus 一次,其余由 Nexus 自己转。

ECR pull-through cache:在 AWS 内部缓存

集群在 AWS 上的话,不必另外运维缓存,ECR 来做这件事。在规则(pull through cache rule)里写上上游和前缀,拉取 <账号>.dkr.ecr.<区域>.amazonaws.com/<前缀>/library/nginx:1.27-alpine 时,ECR 会从上游取来并保存在我账号下的 ECR 仓库里。

文档里确认的规则如下。

  • 无需认证的上游是 ECR Public、Kubernetes 容器镜像仓库、Quay 三个。Docker Hub、Azure Container Registry、GitHub 和 GitLab Container Registry、Chainguard 必须有 Secrets Manager 密钥,其他账号的 ECR 用 IAM 角色认证。
  • 该密钥的名字必须以 ecr-pullthroughcache/ 开头,并且与规则处于同一账号和区域。这样 Docker Hub 是以账号拉取的,用的是该账号的限额而不是匿名的 100 次。
  • 再次拉取同一标签时,若 过去 24 小时内已与上游核对过,就不再询问上游,直接返回缓存。窗口过期则检查新版本并更新。若有覆盖标签来部署的习惯,可能在长达一天内拿到旧镜像。
  • 即使上游更新失败,最后的缓存副本仍会下发。也就是说 Docker Hub 挂了,用已拉取过的镜像仍能部署。
  • 给缓存仓库开启标签不可变(immutability)后,同一标签的更新会被阻止。
  • 多架构镜像会把 manifest 列表及其中的全部架构都拉下来。只想要一个,就用该架构的 digest 拉取。
  • 第一次拉取时 ECR 必须能访问上游,可能需要通往互联网的路由。只有 PrivateLink 端点的 VPC 第一次拉取可能失败,文档建议预先准备路由。之后的拉取不需要。
  • AWS Lambda 不支持通过 pull-through cache 规则生成的镜像。

选哪一个

registry:2 proxyNexus(proxy + group)ECR pull-through cache
运维负担一个容器一台服务器加磁盘管理无(托管)
上游一个多个组成一组每条规则一个
推送自己的镜像不可(只读)到 hosted到普通 ECR 仓库
更新检查每次拉取都核对上游 manifest可配置每标签每 24 小时一次
适合家庭实验室、几个节点公司内部,连语言包也放一处AWS 内的 EKS、ECS

在我们的流水线里

LabHub 的构建由 Jenkins 用 kaniko 做镜像推到私有 Harbor,再由 ArgoCD 部署该标签。基础镜像在 Dockerfile 里连 digest 一起固定。

FROM python:3.12-slim@sha256:78387bc3…

前面放了代理缓存,这一行也不变。digest 是内容的哈希,不论经过哪个缓存,字节必须一致才能通过。缓存守住限额,digest 守住内容。两个装置做的是不同的事。

一句话

代理缓存不是让拉取更快的装置,而是 把上游数到的次数减为一 的装置。节点只有几个,registry:2 就够;仓库有多个,用 Nexus 的组做出一个地址;在 AWS 里,就让 ECR 代为缓存。

현재 단락 (1/54)

CI 每跑一次,`FROM python:3.12-slim` 就从 Docker Hub 下来一次。十个节点就是十次,一天跑一百次流水线就是一百次。Docker Hub 在数着。

작성 글자: 0원문 글자: 3,727작성 단락: 0/54