Docker镜像由只读增量层堆叠构成,每层仅存与前层的差异;容器运行时添加可写层,存储驱动(如overlay2、zfs等)决定层的物理组织与访问机制,影响性能、共享及写入行为。

理解 Docker 镜像的层级结构是掌握不同存储驱动(如 overlay2、aufs、btrfs、zfs、devicemapper)行为差异的关键入口。镜像本身由只读层(layers)堆叠而成,而容器运行时会在其上添加一个可写层;存储驱动决定了这些层如何在宿主机文件系统中组织、读写和共享。
镜像层本质:只读的增量快照
Dockerfile 中每条指令(RUN、COPY、ADD 等)都会生成一个新层,该层只保存与前一层的差异(diff),不是完整文件系统副本。例如:
- FROM ubuntu:22.04 → 基础层(含 rootfs)
- RUN apt update && apt install -y curl → 新层仅包含 curl 及其依赖的新增/修改文件(如 /usr/bin/curl、/var/lib/dpkg/status 的变更)
- COPY app.py /app/ → 新层只存 app.py 文件本身
所有层按顺序叠加后构成最终根文件系统视图。这种设计天然支持层共享——多个镜像可复用相同的基础层,节省磁盘空间。
存储驱动决定层的物理存放与访问方式
不同驱动对“层”这一逻辑概念的实现机制差异显著,直接影响性能、兼容性和功能支持:
- overlay2(主流,默认):每个镜像层对应宿主机上一个独立目录(/var/lib/docker/overlay2/<id>/diff),通过 mount 的 upperdir + lowerdir + workdir 实现多层合并视图。层间硬链接共享相同 inode(如未修改的 /bin/bash),节省空间且启动快
- devicemapper(已弃用):将每层映射为精简配置的逻辑卷(thin LV),依赖 device-mapper 内核模块。写时复制靠块级快照,IO 开销大、碎片多,不推荐新部署
- zfs/btrfs:利用文件系统原生快照能力,每层对应一个子卷或快照。支持压缩、加密、跨池克隆,但要求底层文件系统支持且配置复杂
- aufs(已移除):早期使用,通过多目录联合挂载实现层叠,但内核主线不维护,存在稳定性风险
从容器写入看驱动差异:可写层如何工作
容器启动时,存储驱动需为可写层提供隔离、高效的写入能力:
- overlay2:使用 copy-up —— 首次修改某文件时,才将其从只读层完整拷贝到 upperdir,再修改。小文件随机写友好,但大量小文件读+改会触发多次 copy-up
- zfs:直接在可写子卷中修改,无 copy-up 开销,但每次写入都产生新数据块(写时分配),配合 ARC 缓存效果好
- devicemapper:写操作触发 thin-provisioning 的块级 copy-on-write,延迟高,尤其在 SSD 上易出现 IOPS 瓶颈
可通过 docker info | grep "Storage Driver" 查看当前驱动,并用 docker image inspect <image> 观察 RootFS.Layers 数组验证层结构。
调试与验证:观察实际层布局
以 overlay2 为例,进入 /var/lib/docker/overlay2 目录:
- 每个子目录名(如 ab12cd34...)对应一个层 ID
- diff/ 存该层独有的文件(即 diff 内容)
- link 文件记录短别名(用于硬链接去重)
- lower 文件列出其依赖的下层别名链
对比 btrfs 驱动下的 /var/lib/docker/btrfs/subvolumes/,你会看到每个层是一个带快照属性的子卷,没有 diff 目录——因为差异由文件系统快照元数据隐式管理。


















