导读:本期聚焦于林则安创作的《容器文件系统的 Copy-on-Write 到底是如何工作的?》,敬请观看详情。容器镜像体积为什么比虚拟机镜像小得多?启动速度凭什么做到秒级?答案很大程度来自文件系统层的 Copy-on-Write 机制。它允许多个容器共享同一份只读底层数据,任何修改都写入私有可写层,既节省空间又避免相互干扰。本文从 OverlayFS 入手,拆解 lowerdir、upperdir、merged 三层结构,说明读文件时如何向上查找、写文件时如何触发 copy-up,并结合删除操作的白名单文件解释 Linux 文件系统实现细节。还会对比 AUFS、DeviceMapper 等常见存储驱动的实现差异,以及 COW 在高并发写入、原地修改大文件场景下的性能陷阱。理解了这些,再排查容器磁盘占用和 IO 瓶颈会更有方向。

容器镜像看起来像一个完整的文件系统,但它并不是把每个容器的所有文件都存一份。镜像由多个只读层堆叠而成,运行容器时,Docker 在这些层之上创建一个可写层。进程读取文件时,系统会从最上层向下逐层查找;进程修改文件时,系统不会改动底层的只读数据,而是把目标文件复制到可写层,再执行写入。这就是容器文件系统的 Copy-on-Write 机制,简称 COW。它让几十个容器可以共享同一份底包,只有改动部分才占用新空间,从而显著降低磁盘占用、提升启动速度,但也引出了查找、删除、写放大等一系列实现细节。

容器文件系统的 Copy-on-Write 到底是如何工作的?

一、镜像分层与 COW 要解决的问题

传统虚拟机通常为每个实例分配完整虚拟磁盘,即使只改动一个配置,也要预留几 GB 空间。容器镜像采用分层设计,例如一个基于 Ubuntu 的镜像可能由基础层、软件安装层、配置层叠加而成。每一层只记录相对前一层的差异。运行容器时新增的可写层最初是空的,所有读操作都会穿透到只读层。

这种分层设计最大的好处是复用。同一台宿主机上运行几十个基于相同镜像的容器时,底层只读层只保存一份,每个容器只需要额外存储自己的私有修改。镜像仓库传输也节省带宽,因为已经拉取过的公共层可以直接复用。但如果每次修改都直接写入共享层,会让所有使用该层的容器互相影响。COW 的规则是任何修改都进入私有可写层,从根源上隔离。即使容器删除,只读镜像层仍然保持不变,可以被其他容器继续引用。

需要注意的是,COW 并不是在容器启动时就把整个镜像复制一遍,而是延迟到实际写入那一刻才复制。读操作几乎零开销,只有首次写文件才发生复制。这个机制带来两个直接问题:一是首次写入某个文件时会出现额外延迟,二是随着时间推移,可写层可能因为复制大文件而迅速膨胀。

二、OverlayFS 的目录结构与 copy-up 流程

OverlayFS 是 Linux 内核提供的联合文件系统,overlay2 是 Docker 目前默认的存储驱动。它把多个目录合并成一个视图,涉及四个关键路径:lowerdirupperdirworkdir 以及最终的挂载点 mergedlowerdir 是只读底层目录,可以由多个用冒号分隔的层组成;upperdir 是可写层,保存所有修改;merged 是合并后的视图,容器内看到的就是这个目录;workdir 是 OverlayFS 执行内部原子操作时使用的目录,必须和 upperdir 位于同一文件系统。

可以用下面命令手工挂载一个 overlay 文件系统,观察各目录之间的关系:

mkdir -p /tmp/lower /tmp/upper /tmp/work /tmp/merged
mount -t overlay overlay -o lowerdir=/tmp/lower,upperdir=/tmp/upper,workdir=/tmp/work /tmp/merged

当进程读取一个文件时,OverlayFS 会先在 upperdir 中查找。如果存在,直接返回该文件;如果不存在,再按照 lowerdir 中从高到低的顺序逐层查找。因此上层文件会遮蔽下层同名文件,镜像中的配置层可以覆盖基础层的默认配置。删除操作并不是真正删掉底层文件,而是在 upperdir 中创建一个同名的 whiteout 文件,它是一个字符设备,主次设备号都为 0。对于目录删除,还会设置 trusted.overlay.opaque 扩展属性,表示屏蔽所有下层同名目录的内容。

写文件时,如果目标文件来自只读层,OverlayFS 会触发一次 copy-up。具体流程是:先把原文件完整复制到 upperdir 的对应路径,然后让写操作发生在副本上。如果只是修改大文件中的几个字节,也会复制整个文件,这就是写放大。copy-up 结束后,该文件的所有后续读写都在 upperdir 中进行。对于新建文件,则直接在 upperdir 创建,不涉及复制。重命名、修改权限等元数据操作也可能触发 copy-up,因为只读层的元数据不能被直接修改。

三、COW 带来的性能影响与排查方法

从性能角度看,COW 对频繁读取小文件通常比较友好,因为大部分数据可以直接命中只读层的页缓存。但首次写某个大文件时可能产生明显卡顿。例如把一个 20GB 的数据库数据文件放进容器镜像,第一次执行修改会先复制 20GB 到可写层,再写入 4KB 数据,IO 和磁盘占用量都会显著增加。对于 SQLite、MySQL、PostgreSQL、虚拟机磁盘镜像、消息队列日志等需要原地写入的场景,写放大问题尤其突出。

删除大文件也不会立即从底层回收空间。如果文件来自镜像只读层,删除操作只是在可写层生成一个 whiteout,底层数据仍然占用磁盘。因此仅看容器内文件大小往往会误判磁盘占用。可以通过 docker inspect 查看 GraphDriver 中记录的 LowerDirUpperDirMergedDir 实际路径,再结合 du 命令对比可写层增长情况。

docker inspect --format '{{json .GraphDriver.Data}}' my-container

为了减少 COW 的代价,有几种常用优化方式。第一,将高写入目录挂载到 Docker volume,让数据绕过容器可写层。第二,对日志等临时数据使用 tmpfs,避免写入磁盘。第三,避免把数据库、搜索引擎索引、消息队列数据目录打进镜像。第四,定期执行 docker system prune,清理已经停止的容器和未使用的镜像层。第五,如果使用 overlay2,尽量减少在可写层上进行大量原地更新,把可变数据外置。

四、常见存储驱动中的 COW 实现差异

Docker 支持多种存储驱动,不同驱动在实现 Copy-on-Write 时粒度不同。AUFS 是早期 Docker 默认驱动,它支持多个底层分支和可写分支,文件级 COW 行为与 OverlayFS 类似,但需要内核补丁,一直没有进入 Linux 主线。DeviceMapper 则采用块级 COW,基于 thin-provisioning 和 snapshot 机制,默认按 64KB 块分配空间,修改一个块只复制该块,对大文件的随机写可能更节省空间,但元数据开销和配置复杂度更高。

OverlayFS 的 overlay2 驱动是当前 Linux 内核 4.0 以上环境的主流选择。它比早期 overlay 驱动支持更多的 lower 层,并且原生使用内核联合文件系统,不依赖额外补丁。Btrfs 和 ZFS 则是另一种思路,它们直接利用文件系统原生的 copy-on-write、快照和子卷能力来实现镜像层,适合已经深度使用这些文件系统的场景。不同驱动在写放大、删除延迟、页缓存效率上存在差异,因此排查容器 IO 问题时,需要先确认 docker info 显示的 Storage Driver 类型。

五、镜像构建中的 COW 隐藏成本

COW 不仅影响运行期,也会影响镜像构建。Dockerfile 中每一条 RUN 指令都会生成一个新的只读层。如果先复制一个 1GB 文件到镜像,再在下一条指令中删除它,最终镜像体积并不会减少,因为前一层仍然保存着这个文件,后一层只是添加了 whiteout。构建时应尽量把下载、解压、清理等操作合并到同一条 RUN 命令中,避免产生无用的中间层。

另外,镜像层一旦生成就不能修改。即使后续层覆盖了之前的配置,旧数据依然存在。这种不可变性保证了镜像可复用和可追踪,但也要求开发者在构建阶段就注意层级规划。对于数据频繁变化的场景,最佳实践仍然是把数据放在 volume 中,让 COW 只负责应用代码和依赖文件,而不是承载业务数据。

容器文件系统Copy-on-WriteOverlayFS修改时间:2026-08-26 20:01:55

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。