3、镜像仓库的架构:存储模型、认证机制与API接口
镜像仓库,说白了就是存放和分发容器镜像的「中央仓库」。你平时用 docker pull 拉镜像,背后就是它在干活。Docker Hub 是公共的,Harbor 是企业自建的。但不管哪种,核心架构都绕不开三件事:镜像怎么存、谁有权限拉、客户端怎么跟它通信。
我刚开始接触容器时,以为镜像仓库就是个文件服务器。后来在生产环境里踩过几次坑,才明白它的存储模型、认证流程和 API 设计,直接决定了你能否稳定、安全地分发镜像。这一节,咱们就把这三块掰开揉碎讲清楚。
3.1 存储模型:镜像在仓库里到底长什么样?
镜像仓库的存储模型,核心就是 分层 + 内容寻址。每个镜像由一堆只读层(Layer)堆叠而成,每一层都有一个唯一的 Digest(哈希值)。仓库不关心你镜像叫什么名字,它只认这些哈希。
举个例子,你拉一个 nginx:latest,仓库实际上返回的是这个镜像的 Manifest 文件。Manifest 里记录了所有层的 Digest 和大小。客户端拿到 Manifest 后,再根据 Digest 去拉每一层。
我在项目中遇到过一个问题:有同事直接删了仓库里某个 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。这种设计的好处是:相同内容的层只存一份,节省大量磁盘空间。
--dry-run 参数模拟跑一遍,确认不会误删数据。
3.2 认证机制:谁有权限拉取和推送?
镜像仓库的认证,不是简单的「用户名+密码」就完事了。它遵循一套标准的 Token 认证流程,核心是 Bearer Token(承载令牌)。
你想想看,如果每次拉镜像都要把密码发过去,那密码在网络传输中很容易泄露。所以 Docker 设计了一套更安全的方案:先认证,拿 Token;再凭 Token 去拉镜像。
3.2.1 认证流程三步走
- 客户端发起请求:
docker pull nginx,客户端直接请求 Registry 的 API。 - Registry 返回 401:告诉客户端「你没权限,去这个地址拿 Token」。响应头里会带一个
Www-Authenticate字段,里面包含认证服务的 URL 和需要的权限范围(Scope)。 - 客户端去认证服务拿 Token:客户端拿着你的用户名密码(或 Token),去认证服务换取一个 Bearer Token。然后带着这个 Token 重新请求 Registry。
这个流程,说白了就是「认证与授权分离」。Registry 只负责授权(检查 Token 有没有权限),认证服务(如 Docker Hub 的 Auth Service 或 Harbor 的 OIDC)负责认证(你是谁)。
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 确认完成。为什么这么麻烦?因为大镜像的层可能有好几百兆,一次传完容易超时。分块上传 + 断点续传,才是生产环境该有的设计。
PATCH 请求超时。解决方案是:在客户端设置合理的重试策略,比如对 PATCH 请求重试 3 次,每次间隔 2 秒。同时,把上传块的大小从默认的 4MB 调到 16MB,减少请求次数。
3.3.2 拉取镜像的完整流程
咱们用 docker pull nginx:latest 举个例子,看看背后到底调用了哪些 API:
- GET /v2/:检查 Registry 是否可用。
- GET /v2/library/nginx/manifests/latest:获取
latestTag 对应的 Manifest。返回的 Manifest 里包含了所有层的 Digest 列表。 - GET /v2/library/nginx/blobs/sha256:xxx:根据 Manifest 里的 Digest,逐个拉取每一层的数据。这一步会并发执行,加快拉取速度。
- 客户端把所有层解压并堆叠成完整的文件系统,镜像拉取完成。
嗯,这里要注意:如果 Manifest 里引用的某个层在本地已经存在(比如你之前拉过其他镜像,共享了同一层),客户端会跳过这一步,直接复用本地缓存。这就是为什么你拉一个包含很多基础层的新镜像时,速度会很快。
3.4 架构全景图:一张图看懂镜像仓库
下面这张 SVG 图,把镜像仓库的核心架构、存储模型、认证流程和 API 调用关系串在了一起。你可以把它当作本章的知识地图。
3.5 企业级仓库的额外考量
如果你用的是 Harbor 这类企业级仓库,除了上面说的核心功能,还有几个点值得关注:
- 镜像复制:Harbor 支持跨机房、跨集群的镜像同步。我建议你配置「推模式」——源仓库主动推送到目标仓库,这样目标仓库不需要暴露公网端口,更安全。
- 漏洞扫描:Harbor 集成了 Clair 或 Trivy,推送镜像时会自动扫描漏洞。我个人习惯把扫描结果作为 CI/CD 流水线的一个门禁:高危漏洞数量超过阈值,直接阻断部署。
- 垃圾回收:删除镜像后,Blob 不会立即删除。需要手动触发 GC 或设置定时任务。我曾经在生产环境忘记配置 GC,结果存储空间被废弃的 Blob 占满,导致新镜像推送失败。
- 审计日志:谁在什么时间拉取了哪个镜像,这些日志必须保留。特别是在金融、医疗等合规要求严格的行业,审计日志是硬性需求。