3. 镜像存储原理:Docker 镜像分层存储机制、Manifest 与 Blob 的关系、存储驱动对比

理解 Docker 镜像的存储原理是优化 Harbor 制品存储成本的基础。镜像并非一个单一的大文件,而是一组按层(Layer)组织的只读文件系统快照。每一层代表 Dockerfile 中的一条指令(如 RUN、COPY),且层与层之间通过内容寻址(Content-Addressable)的方式唯一标识。

3.1 Docker 镜像分层存储机制

分层存储的核心优势在于共享与复用。当多个镜像基于相同的基础层(如 ubuntu:22.04)构建时,这些基础层在物理存储上只保存一份,从而大幅降低存储成本。

关键概念: 每一层(Layer)是一个只读的 tar 归档文件,包含该层新增、修改或删除的文件。层与层之间通过 Union File System(联合文件系统)技术叠加,形成一个完整的容器视图。

分层存储的典型流程如下:

  1. 基础层(Base Layer): 从基础镜像(如 alpine:latest)拉取,位于堆叠的最底层。
  2. 中间层(Intermediate Layer): 每执行一条 Dockerfile 指令(如 RUN apt-get update),生成一个新的只读层。
  3. 容器层(Container Layer): 当容器运行时,在最上层创建一个可读写层(R/W Layer)。容器内所有写操作都发生在此层,容器删除后该层随之消失。
Docker 镜像分层存储结构 容器层(可读写层) 镜像层 3:RUN apt-get install python3 镜像层 2:COPY requirements.txt /app/ 镜像层 1:FROM python:3.11-slim 层共享机制 • 只读层可被多个 镜像共享 • 内容寻址:层 ID 基于 SHA256 计算 • 删除镜像时仅删除 引用计数归零的层 层 ID 基于内容哈希(SHA256)生成,确保唯一性与完整性
成本优化启示: 在构建镜像时,将频繁变动的指令(如 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。

Manifest 与 Blob 引用关系 Manifest (v2) mediaType, config, layers[] config.digest Config Blob sha256:abc...def layers[].digest Layer Blob 1 sha256:ghi...jkl Layer Blob 2 sha256:mno...pqr Blob 通过 SHA256 摘要唯一标识,Manifest 通过摘要引用 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 检查磁盘使用情况,验证层共享效率是否提升。