3、镜像仓库的架构:存储模型、认证机制与API接口

镜像仓库,说白了就是存放和分发容器镜像的「中央仓库」。你平时用 docker pull 拉镜像,背后就是它在干活。Docker Hub 是公共的,Harbor 是企业自建的。但不管哪种,核心架构都绕不开三件事:镜像怎么存、谁有权限拉、客户端怎么跟它通信。

我刚开始接触容器时,以为镜像仓库就是个文件服务器。后来在生产环境里踩过几次坑,才明白它的存储模型、认证流程和 API 设计,直接决定了你能否稳定、安全地分发镜像。这一节,咱们就把这三块掰开揉碎讲清楚。

3.1 存储模型:镜像在仓库里到底长什么样?

镜像仓库的存储模型,核心就是 分层 + 内容寻址。每个镜像由一堆只读层(Layer)堆叠而成,每一层都有一个唯一的 Digest(哈希值)。仓库不关心你镜像叫什么名字,它只认这些哈希。

举个例子,你拉一个 nginx:latest,仓库实际上返回的是这个镜像的 Manifest 文件。Manifest 里记录了所有层的 Digest 和大小。客户端拿到 Manifest 后,再根据 Digest 去拉每一层。

关键点: 镜像仓库里存的是两层结构——Manifest(清单)和 Blob(二进制大对象,即每一层的数据)。Manifest 指向 Blob,Blob 才是真正的镜像内容。

我在项目中遇到过一个问题:有同事直接删了仓库里某个 Blob 文件,结果所有引用这个 Blob 的镜像都拉不下来了。为什么?因为 Manifest 里记录的 Digest 还在,但实际数据没了。所以,千万别手动操作仓库的存储后端,一定要用仓库提供的 GC(垃圾回收)机制。

3.1.1 存储目录结构(以 Docker Registry 为例)

如果你直接看 Docker Registry 的存储后端(比如本地文件系统或 S3),它的目录结构大概是这样的:

/docker/registry/v2/
├── blobs/                # 所有层数据
│   └── sha256/
│       ├── 4a/           # Digest 的前两位作为子目录
│       │   └── 4a...abc  # 完整的 Digest 文件名
│       └── ...
└── repositories/         # 仓库元数据
    └── library/
        └── nginx/
            ├── _manifests/
            │   ├── revisions/   # 所有版本的 Manifest
            │   └── tags/        # Tag 到 Manifest 的映射
            └── _layers/         # 该仓库引用的层索引

你看,blobs 目录是全局共享的,所有镜像的层都平铺在这里。而 repositories 只存元数据,比如某个 Tag 指向哪个 Manifest,Manifest 又引用了哪些 Blob。这种设计的好处是:相同内容的层只存一份,节省大量磁盘空间。

避坑指南: 我曾经在 Harbor 里遇到过 GC 不彻底的问题。原因是某些层被多个 Manifest 引用,GC 判断引用计数时出了偏差。建议你在生产环境开启 GC 前,先用 --dry-run 参数模拟跑一遍,确认不会误删数据。

3.2 认证机制:谁有权限拉取和推送?

镜像仓库的认证,不是简单的「用户名+密码」就完事了。它遵循一套标准的 Token 认证流程,核心是 Bearer Token(承载令牌)。

你想想看,如果每次拉镜像都要把密码发过去,那密码在网络传输中很容易泄露。所以 Docker 设计了一套更安全的方案:先认证,拿 Token;再凭 Token 去拉镜像。

3.2.1 认证流程三步走

  1. 客户端发起请求:docker pull nginx,客户端直接请求 Registry 的 API。
  2. Registry 返回 401:告诉客户端「你没权限,去这个地址拿 Token」。响应头里会带一个 Www-Authenticate 字段,里面包含认证服务的 URL 和需要的权限范围(Scope)。
  3. 客户端去认证服务拿 Token:客户端拿着你的用户名密码(或 Token),去认证服务换取一个 Bearer Token。然后带着这个 Token 重新请求 Registry。

这个流程,说白了就是「认证与授权分离」。Registry 只负责授权(检查 Token 有没有权限),认证服务(如 Docker Hub 的 Auth Service 或 Harbor 的 OIDC)负责认证(你是谁)。

注意: 如果你自建 Harbor 并配置了 LDAP 认证,一定要确保 LDAP 服务器的高可用。我曾经遇到过 LDAP 宕机,导致整个 Harbor 无法认证,所有 CI/CD 流水线全部卡住。后来我们给 Harbor 配了本地备用用户,作为紧急通道。

3.2.2 Token 的结构

Token 本身是一个 JWT(JSON Web Token),里面包含了:

字段 说明
sub 用户名或客户端 ID
aud 目标 Registry 地址
scope 权限范围,如 repository:library/nginx:pull
exp 过期时间,通常只有几分钟

我个人习惯把 Token 的过期时间设短一点(比如 5 分钟),配合客户端的长连接复用,既安全又高效。你想想看,如果 Token 有效期太长,万一泄露了,别人就能一直用你的权限拉镜像。

3.3 API 接口:客户端怎么跟仓库打交道?

镜像仓库的 API 遵循 Docker Registry HTTP API V2 规范。这套 API 是 RESTful 风格的,所有操作都围绕 Manifest 和 Blob 展开。

我刚开始看这套 API 时,觉得它设计得挺巧妙。它把镜像的拉取和推送拆成了几个原子操作,每个操作都可以独立重试,非常适合网络不稳定的环境。

3.3.1 核心 API 端点

方法 路径 说明
GET /v2/ 检查 API 是否可用(返回 200 表示正常)
GET /v2/<name>/manifests/<reference> 获取 Manifest(reference 可以是 Tag 或 Digest)
PUT /v2/<name>/manifests/<reference> 推送 Manifest
GET /v2/<name>/blobs/<digest> 获取某个层的数据
POST /v2/<name>/blobs/uploads/ 开始上传一个层(返回上传会话 URL)
PATCH /v2/<name>/blobs/uploads/<uuid> 上传层的数据块(支持断点续传)
PUT /v2/<name>/blobs/uploads/<uuid> 完成上传(需提供最终 Digest)
DELETE /v2/<name>/manifests/<reference> 删除 Manifest(注意:不会自动删除 Blob)

你看,上传一个层需要三步:POST 创建会话 → PATCH 传数据 → PUT 确认完成。为什么这么麻烦?因为大镜像的层可能有好几百兆,一次传完容易超时。分块上传 + 断点续传,才是生产环境该有的设计。

实战经验: 我在优化 CI/CD 流水线时,发现推送大镜像经常失败。后来排查发现是网络抖动导致 PATCH 请求超时。解决方案是:在客户端设置合理的重试策略,比如对 PATCH 请求重试 3 次,每次间隔 2 秒。同时,把上传块的大小从默认的 4MB 调到 16MB,减少请求次数。

3.3.2 拉取镜像的完整流程

咱们用 docker pull nginx:latest 举个例子,看看背后到底调用了哪些 API:

  1. GET /v2/:检查 Registry 是否可用。
  2. GET /v2/library/nginx/manifests/latest:获取 latest Tag 对应的 Manifest。返回的 Manifest 里包含了所有层的 Digest 列表。
  3. GET /v2/library/nginx/blobs/sha256:xxx:根据 Manifest 里的 Digest,逐个拉取每一层的数据。这一步会并发执行,加快拉取速度。
  4. 客户端把所有层解压并堆叠成完整的文件系统,镜像拉取完成。

嗯,这里要注意:如果 Manifest 里引用的某个层在本地已经存在(比如你之前拉过其他镜像,共享了同一层),客户端会跳过这一步,直接复用本地缓存。这就是为什么你拉一个包含很多基础层的新镜像时,速度会很快。

3.4 架构全景图:一张图看懂镜像仓库

下面这张 SVG 图,把镜像仓库的核心架构、存储模型、认证流程和 API 调用关系串在了一起。你可以把它当作本章的知识地图。

镜像仓库核心架构图 镜像仓库核心架构图 Docker 客户端 认证服务 (Auth) Registry API 存储后端 Manifest Blob (层数据) ① 请求 Token ② 返回 Token ③ 带 Token 请求 ④ 读写数据 核心流程说明: 1. 客户端先向认证服务申请 Token(携带用户名/密码或 OIDC 凭证) 2. 认证服务验证身份后,返回一个短期有效的 Bearer Token 3. 客户端带着 Token 请求 Registry API,执行 pull/push 操作 4. Registry API 从存储后端读写 Manifest 和 Blob,完成镜像分发

3.5 企业级仓库的额外考量

如果你用的是 Harbor 这类企业级仓库,除了上面说的核心功能,还有几个点值得关注:

  • 镜像复制:Harbor 支持跨机房、跨集群的镜像同步。我建议你配置「推模式」——源仓库主动推送到目标仓库,这样目标仓库不需要暴露公网端口,更安全。
  • 漏洞扫描:Harbor 集成了 Clair 或 Trivy,推送镜像时会自动扫描漏洞。我个人习惯把扫描结果作为 CI/CD 流水线的一个门禁:高危漏洞数量超过阈值,直接阻断部署。
  • 垃圾回收:删除镜像后,Blob 不会立即删除。需要手动触发 GC 或设置定时任务。我曾经在生产环境忘记配置 GC,结果存储空间被废弃的 Blob 占满,导致新镜像推送失败。
  • 审计日志:谁在什么时间拉取了哪个镜像,这些日志必须保留。特别是在金融、医疗等合规要求严格的行业,审计日志是硬性需求。
重要提醒: 不要在生产环境直接删除 Harbor 的存储目录或数据库记录。Harbor 的元数据和实际数据是强关联的,手动操作很容易导致数据不一致。正确的做法是:通过 Harbor 的 Web UI 或 API 删除镜像,然后运行 GC 清理残留的 Blob。