3. 镜像存储原理:Docker 镜像分层存储机制、Manifest 与 Blob 的关系、存储驱动对比
理解 Docker 镜像的存储原理是优化 Harbor 制品存储成本的基础。镜像并非一个单一的大文件,而是一组按层(Layer)组织的只读文件系统快照。每一层代表 Dockerfile 中的一条指令(如 RUN、COPY),且层与层之间通过内容寻址(Content-Addressable)的方式唯一标识。
3.1 Docker 镜像分层存储机制
分层存储的核心优势在于共享与复用。当多个镜像基于相同的基础层(如 ubuntu:22.04)构建时,这些基础层在物理存储上只保存一份,从而大幅降低存储成本。
关键概念: 每一层(Layer)是一个只读的 tar 归档文件,包含该层新增、修改或删除的文件。层与层之间通过 Union File System(联合文件系统)技术叠加,形成一个完整的容器视图。
分层存储的典型流程如下:
- 基础层(Base Layer): 从基础镜像(如
alpine:latest)拉取,位于堆叠的最底层。 - 中间层(Intermediate Layer): 每执行一条 Dockerfile 指令(如
RUN apt-get update),生成一个新的只读层。 - 容器层(Container Layer): 当容器运行时,在最上层创建一个可读写层(R/W Layer)。容器内所有写操作都发生在此层,容器删除后该层随之消失。
成本优化启示: 在构建镜像时,将频繁变动的指令(如
COPY 源代码)放在 Dockerfile 底部,将不常变动的指令(如 RUN apt-get install)放在顶部,可以最大化层复用率,减少 Harbor 中冗余 Blob 的存储。
3.2 Manifest 与 Blob 的关系
在 Harbor 的存储后端(如 Registry V2),镜像的物理存储由两个核心概念构成:Manifest(清单) 和 Blob(二进制大对象)。理解它们的关系是分析存储成本的关键。
| 概念 | 描述 | 存储内容 | 大小特征 |
|---|---|---|---|
| Manifest | 镜像的元数据清单,描述镜像的配置和层列表 | JSON 格式,包含层 SHA256 摘要、大小、媒体类型 | 通常很小(1KB ~ 10KB) |
| Blob | 实际的数据块,包括镜像层(Layer)和配置文件(Config) | 压缩后的 tar 归档文件(.tar.gz) | 从几 KB 到几百 MB 不等 |
| Config Blob | 镜像的运行时配置(如环境变量、入口点、默认命令) | JSON 格式的配置文件 | 通常小于 10KB |
Manifest 与 Blob 之间通过内容寻址建立引用关系。Manifest 中记录的层摘要(Digest)直接指向存储仓库中的 Blob。这种设计使得 Blob 天然具备去重(Deduplication)能力:只要两个镜像引用了相同摘要的层,物理存储上就只保存一份 Blob。
成本陷阱: 如果频繁推送相同内容但标签不同的镜像(如
myapp:latest 和 myapp:v1.0),Manifest 会更新,但底层的 Blob 如果内容未变,则不会产生新的存储开销。然而,如果每次构建都生成新的层(如未利用构建缓存),则会产生大量冗余 Blob,直接推高存储成本。
3.3 存储驱动对比:overlay2 vs. aufs
存储驱动(Storage Driver)负责在宿主机上管理镜像层和容器层的联合挂载。不同的存储驱动在性能、稳定性和功能上存在差异。在 Harbor 的镜像存储成本优化场景中,理解存储驱动有助于评估层共享效率和磁盘 I/O 开销。
| 特性 | overlay2 | aufs |
|---|---|---|
| 内核支持 | Linux 内核 4.0+(推荐 4.9+) | Linux 内核 3.8+(但未合入主线) |
| 层数限制 | 最多 128 层(硬限制) | 最多 127 层(硬限制) |
| 性能 | 优秀,inode 利用率高,页面缓存共享好 | 良好,但 inode 消耗较高 |
| 存储效率 | 层共享基于硬链接,节省磁盘空间 | 层共享基于硬链接,但元数据开销略大 |
| 稳定性 | Docker 官方推荐,生产环境首选 | 已被 Docker 弃用(自 Docker 18.06 起) |
| 适用场景 | 所有现代 Linux 发行版(Ubuntu 16.04+、CentOS 7.4+、Debian 9+) | 仅适用于旧版 Ubuntu(14.04/16.04) |
成本优化建议: 在 Harbor 节点和 Docker 构建节点上,优先使用 overlay2 存储驱动。它提供更好的 inode 效率和页面缓存共享,能够减少因元数据膨胀导致的额外磁盘开销。对于仍在运行 aufs 的旧系统,应尽快迁移至 overlay2 以降低长期存储成本。
存储驱动的工作原理决定了层数据在磁盘上的组织方式。以 overlay2 为例,每个镜像层对应一个独立的目录,目录中包含该层的文件系统快照。层与层之间通过 lowerdir(只读层)和 upperdir(容器可写层)进行联合挂载。
# overlay2 挂载示例
mount -t overlay overlay \
-o lowerdir=/var/lib/docker/overlay2/l/ABC:/var/lib/docker/overlay2/l/DEF, \
upperdir=/var/lib/docker/overlay2/container-id/diff, \
workdir=/var/lib/docker/overlay2/container-id/work \
/merged
在 Harbor 的存储成本台账中,存储驱动的影响主要体现在以下方面:
- 层共享粒度: overlay2 的硬链接机制使得相同内容的层在磁盘上只占用一个 inode,而 aufs 的元数据管理方式可能导致额外的目录项开销。
- 垃圾回收效率: overlay2 在删除层时,硬链接计数归零后立即释放空间;aufs 的层删除流程相对复杂,可能导致空间释放延迟。
- Blob 存储路径: 无论使用哪种存储驱动,Harbor 后端的 Blob 存储(如 S3、NFS、本地文件系统)都不受影响,但节点本地缓存(如
/var/lib/docker)的利用率会因驱动不同而有所差异。
迁移检查清单: 若要从 aufs 迁移到 overlay2,需确认内核版本 ≥ 4.0,并修改 Docker 守护进程配置(
/etc/docker/daemon.json 中设置 "storage-driver": "overlay2")。迁移后建议运行 docker system df 检查磁盘使用情况,验证层共享效率是否提升。