2. 容器镜像基础:镜像分层结构(Layer)、Manifest与Config、镜像仓库(Registry)的架构
容器镜像是容器化应用交付的核心单元。理解镜像的内部结构——分层文件系统、元数据描述(Manifest & Config)以及远程存储分发系统(Registry),是优化镜像拉取性能的前提。本节将拆解这三个关键组件,为后续的网络拓扑感知调度奠定知识基础。
2.1 镜像分层结构(Layer)
容器镜像采用分层(Layer)文件系统设计,这是实现存储复用、增量传输和快速启动的关键。每一层代表一组文件变更(添加、修改或删除),所有层叠加后构成容器的完整根文件系统。
2.1.1 分层原理与只读特性
- 基础层(Base Layer):通常源自基础操作系统(如 Ubuntu、Alpine),提供基本的用户空间工具和库。
- 中间层(Intermediate Layers):由 Dockerfile 中的每条指令(如
RUN、COPY、ADD)生成。每条指令创建一个新层。 - 顶层(Top Layer):容器运行时在只读镜像层之上创建的可写层(Container Layer),所有运行时修改(写日志、生成临时文件)均在此层,容器销毁时该层随之消失。
2.1.2 层的内容与存储格式
每一层在物理存储上是一个压缩的 tar 包(tarball),包含该层引入的文件系统快照。层之间通过内容寻址(Content-Addressable)进行标识,即使用该层内容的 SHA256 哈希值作为唯一 ID。
| 属性 | 说明 |
|---|---|
| 层 ID | 基于层内容的 SHA256 摘要,确保不可篡改和唯一性。 |
| 存储格式 | 压缩 tar 包(通常为 .tar.gz 或 .tar.zst),解压后为文件系统快照。 |
| 元数据 | 包含文件权限、所有者、时间戳等,存储在层对应的 JSON 文件中。 |
| 父子关系 | 通过 parent 字段指向上一层,形成链式结构。 |
2.2 Manifest 与 Config:镜像的元数据描述
镜像的物理层数据需要一份“目录”来描述其组成和配置。这份目录由 Manifest(清单)和 Config(配置)两部分构成,遵循 OCI(Open Container Initiative)镜像规范。
2.2.1 Config 文件
Config 是一个 JSON 对象,记录了镜像的运行时配置和层的历史信息。它不包含文件系统数据,而是描述容器启动时应该如何配置。
{
"created": "2024-01-15T10:00:00Z",
"architecture": "amd64",
"os": "linux",
"config": {
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
"Cmd": ["/bin/bash"],
"WorkingDir": "/app",
"ExposedPorts": {"8080/tcp": {}}
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
"sha256:86e8f2f7c9b1a0c6c7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c"
]
},
"history": [
{"created": "2024-01-14T08:00:00Z", "created_by": "/bin/sh -c apt-get update"},
{"created": "2024-01-15T10:00:00Z", "created_by": "COPY app /app"}
]
}
- rootfs.diff_ids:按顺序列出每一层未压缩内容的 SHA256 哈希值,用于验证层完整性。
- history:记录构建历史,用于审计和缓存失效判断。
2.2.2 Manifest 文件
Manifest 是一个 JSON 列表(或单个对象),指向 Config 文件和所有层的压缩后的存储位置(通常是 Registry 上的 Blob 地址)。它是 Registry 返回给客户端的第一个元数据。
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7023,
"digest": "sha256:fc1a6b8f2c4d5e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32654,
"digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 12345,
"digest": "sha256:86e8f2f7c9b1a0c6c7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c"
}
]
}
GET /v2/<name>/manifests/<reference> 获取 Manifest,解析出 Config 和所有 Layer 的 digest,然后并发下载这些 Blob。Manifest 的大小直接影响首次元数据获取的延迟。
2.3 镜像仓库(Registry)的架构
Registry 是镜像的远程存储和分发中心。一个高效的 Registry 架构需要支持高并发、低延迟的拉取,以及跨地域的镜像同步。
2.3.1 核心组件与数据模型
Registry 内部将镜像数据抽象为两种资源:Manifest 和 Blob。Blob 即镜像层的数据块(压缩 tar 包),Manifest 是引用 Blob 的索引。
| 组件 | 存储内容 | 访问方式 |
|---|---|---|
| Blob Store | 镜像层数据(压缩 tar 包)、Config 文件 | 通过 digest(SHA256)进行内容寻址 |
| Manifest Store | 镜像清单(JSON),引用 Config 和 Layers 的 digest | 通过 tag 或 digest 访问 |
| Tag Store | 标签到 Manifest digest 的映射(如 latest → sha256:abc...) |
通过 tag 查询 |
2.3.2 典型 Registry 架构(以 Harbor 为例)
企业级 Registry(如 Harbor、Docker Registry 2.0)通常采用分层架构,以支持高可用和水平扩展。
2.3.3 镜像拉取的核心流程
当客户端(如 kubelet)发起拉取请求时,涉及以下步骤:
- 认证与授权:客户端向 Registry API 发送凭证,获取访问令牌。
- 获取 Manifest:通过
GET /v2/<name>/manifests/<tag>获取镜像清单,解析出 Config 和所有 Layer 的 digest 及大小。 - 获取 Config:通过
GET /v2/<name>/blobs/<digest>下载 Config 文件,验证镜像配置。 - 并发下载 Layers:根据 Manifest 中的 layers 列表,并发发起多个 Blob 下载请求。Registry 支持 Range 请求,支持断点续传。
- 解压与验证:客户端对下载的压缩层进行解压,并计算其 SHA256 摘要与 Manifest 中的 digest 比对,确保数据完整性。
- 挂载层:将解压后的层按顺序叠加到容器的根文件系统。
2.4 本章小结
容器镜像的分层结构实现了高效的存储复用和增量传输;Manifest 与 Config 提供了镜像的完整元数据描述,是客户端与 Registry 交互的契约;Registry 架构通过内容寻址和分层存储支撑了大规模镜像分发。理解这三者的关系,是后续设计网络拓扑感知调度策略的基础——优化目标正是让“层数据的传输路径”尽可能短、尽可能快。