如果你使用过 Docker,一定对“镜像分层”有印象:一个镜像可以由多个只读层(layer)叠加而成,最上面再加一个可写层(容器层)。这种灵活构建和高效复用的能力,依赖的就是 UnionFS(联合文件系统) 技术。
什么是联合文件系统?
UnionFS 是一种可以将多个目录(或文件系统)叠加在一起,对外呈现为单一合并视图的机制。它在挂载时,允许指定多个分支(branch),这些分支可以具有不同的读写权限。最终看起来,所有文件都位于同一个目录树中,但实际上它们可能分散在不同的物理层上。
对于 Docker 来说,这些分支就是镜像的层:
- 多个只读层对应基础镜像和后续构建步骤添加的层(如安装软件、复制文件等);
- 一个可写层对应启动容器时新创建的容器层,用于保存运行时产生的修改。
写时复制(Copy-on-Write)的精髓
当你在容器中修改一个文件时,文件系统并不会直接修改只读层中的原始文件,而是触发 写时复制(Copy-on-Write):
- 系统发现该文件位于下层的只读层中;
- 将该文件复制到顶部的可写层;
- 修改操作在可写层的这份副本上完成。
如果不再需要该修改,删除容器后,只需丢弃这个可写层即可,底层只读层完全不受影响。这种机制带来了两大好处:
- 高效共享:同一台宿主机上运行 100 个基于相同基础镜像(比如
ubuntu:22.04)的容器时,基础镜像的只读层在内存和磁盘上仅需保存一份,可写层各自独立,极大节省了存储空间和内存。 - 快速启动:启动新容器不需要复制整个根文件系统,只需新建一个薄薄的可写层并与已有层联合挂载。这也是容器可以“秒级启动”的关键。
Docker 使用的具体实现
Docker 并不绑定单一的联合文件系统,而是支持多种后端驱动,常见的有:
- OverlayFS(现代 Linux 推荐):已集成在主流 Linux 内核中,性能好、设计简洁。默认分两层联合:
lowerdir(只读层)和upperdir(可写层),通过一个merged目录呈现合并视图。 - AUFS(早期 Docker 使用):曾是 Docker 默认驱动,但一直未被主线内核合并,逐渐被 OverlayFS 取代。
- devicemapper、btrfs 等:通过块级别快照来模拟分层,现在较少直接推荐使用。
以 OverlayFS 为例,可以直观地感受联合挂载:
mkdir lower upper merged work
echo "来自只读层" > lower/file.txt
sudo mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged
cat merged/file.txt # 输出 “来自只读层”
echo "修改内容" >> merged/file.txt
cat upper/file.txt # 文件已被复制到可写层并修改
当查看 merged 目录时,文件系统自动合并了两个目录的内容;修改时,行为完全符合写时复制规则。
镜像构建中的层复用
每条 Dockerfile 指令(RUN、COPY、ADD)都会创建一个新的只读层,镜像就是这些层的叠加。当再次构建时,如果某一层及其之前的所有层都没有变化,Docker 会直接使用缓存,只重建发生变化的层和之后的层。这极大加快了代码更新和持续集成的速度。
实用意义总结
- 存储节约:多个镜像和容器共享相同的底层只读层,物理磁盘上只占用一份空间。
- 启动加速:容器不需要复制系统镜像,联合挂载加上写时复制实现了几乎即时的环境就绪。
- 构建效率:分层缓存让重复构建变得飞快,只有发生变化的步骤才需要重新执行。
- 环境一致性:基础镜像作为只读层被所有容器共享,用户无法直接修改,保证了环境基准的统一。
理解 UnionFS 与写时复制,你就掌握了 Docker 镜像体积小、启动快、缓存聪明背后的技术基石。这也是容器技术轻量化特征的根本来源。