当你 docker pull nginx 下载一个 187MB 的镜像,然后 docker run nginx 启动容器只花了 0.3 秒——为什么这么快?因为 OverlayFS 让多个容器共享同一个镜像的只读层,每个容器只需要一个薄薄的可写层。镜像不需要复制,容器不需要解压,OverlayFS 把多个目录”叠加”成一个统一的文件系统视图。
OverlayFS 是 Linux 内核的联合挂载文件系统,自 3.18 版本合入主线。它是 Docker 和 containerd 的默认存储驱动,也是容器镜像分层复用的底层基础。
容器镜像的分层存储并非一开始就基于 OverlayFS。Docker 早期使用 AUFS(Another UnionFS),由 Junjiro Okajima 于 2006 年开发,它是最早的联合文件系统之一。但 AUFS 一直未能合入 Linux 主线内核,Docker 需要用户手动安装 AUFS 模块,这带来了兼容性噩梦。2014 年,OverlayFS 合入 Linux 3.18 主线内核,Docker 1.12 开始支持 overlay 存储驱动。初版 OverlayFS 只支持一个 lowerdir,而容器镜像通常有多个只读层,Docker 不得不硬链接合并多层——既低效又脆弱。OverlayFS.v2(Linux 4.0+)支持多个 lowerdir,彻底解决了这个问题。此后 overlay2 成为 Docker 和 containerd 的默认存储驱动,AUFS 逐渐退出历史舞台。理解这段演进,有助于理解为什么 OverlayFS 的某些设计看起来”不够优雅”(如 whiteout 用字符设备标记删除而非原生支持),因为这些设计是在”合入主线内核”的约束下做出的妥协。
前置知识
- Ch02 Linux Namespace 深入:Mount Namespace 是 OverlayFS 在容器中生效的前提——每个容器有独立的挂载命名空间
- Ch03 Cgroup v2 深入:Cgroup 的 IO 控制器可以限制 OverlayFS 的磁盘 IO
- Linux 文件系统挂载:
mount -t overlay命令是使用 OverlayFS 的基本方式
如果你需要补课 Linux 文件系统基础,推荐阅读姊妹系列「从零剖析 Linux 操作系统」中的 VFS 与文件系统 章节。
本章将深入 OverlayFS 的架构设计、whiteout 机制、copy-up 语义,以及容器运行时如何使用 OverlayFS。
一、OverlayFS 基本原理
1.1 三层结构
OverlayFS 由三个目录组成:
| 目录 | 作用 | 读写性 |
|---|---|---|
| lowerdir | 底层目录(可以有多个) | 只读 |
| upperdir | 上层目录(只有一个) | 可读写 |
| workdir | 工作目录(内核内部使用) | 内部使用 |
# 手动创建 OverlayFSmkdir -p /tmp/overlay/{lower1,lower2,upper,work,merged}
# 在 lower 层创建文件echo "I am base layer" > /tmp/overlay/lower1/base.txtecho "I am app layer" > /tmp/overlay/lower2/app.txtecho "I am shared config" > /tmp/overlay/lower1/config.txt
# 挂载 OverlayFSsudo mount -t overlay overlay /tmp/overlay/merged \ -o lowerdir=/tmp/overlay/lower2:/tmp/overlay/lower1,\upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work
# 查看合并后的文件系统ls /tmp/overlay/merged/# app.txt base.txt config.txt
# 读取文件(从 lower 层直接读取)cat /tmp/overlay/merged/base.txt# I am base layer1.2 lowerdir 的叠加规则
当有多个 lowerdir 时,右边的优先级高于左边(即最右边的 lowerdir 最先被查找):
# 两个 lowerdir 都有同名文件echo "from lower1" > /tmp/overlay/lower1/same.txtecho "from lower2" > /tmp/overlay/lower2/same.txt
# lower2 优先级更高(在 lowerdir 参数中更靠右)cat /tmp/overlay/merged/same.txt# from lower21.3 OverlayFS 的查找流程
二、OverlayFS 写操作全流程
读操作直接从 lowerdir 或 upperdir 返回,写操作则涉及 copy-up 和 whiteout 两条路径:
三、Copy-Up 机制
3.1 写时复制(Copy-on-Write)
当修改 lowerdir 中的文件时,OverlayFS 不会直接修改 lowerdir(它是只读的),而是将文件复制到 upperdir,然后在 upperdir 中修改。这就是 copy-up:
# 修改 lower 层的文件(触发 copy-up)echo "modified" >> /tmp/overlay/merged/base.txt
# 查看 upperdir——文件被复制到这里ls /tmp/overlay/upper/# base.txt
cat /tmp/overlay/upper/base.txt# I am base layer# modified
# lowerdir 的原始文件不变cat /tmp/overlay/lower1/base.txt# I am base layer3.2 Copy-Up 的触发条件
| 操作 | 是否触发 Copy-Up | 说明 |
|---|---|---|
| 读取文件 | 直接从 lowerdir 读取 | |
| 修改文件内容 | 整个文件复制到 upperdir | |
| 修改文件属性(chmod/chown) | 元数据复制到 upperdir | |
| 创建新文件 | (直接在 upperdir 创建) | 不涉及 lowerdir |
| 删除文件 | (创建 whiteout) | 见下节 |
| 修改目录属性 | 目录元数据复制 |
3.3 Copy-Up 的性能影响
Copy-Up 有两个性能问题:
- 大文件修改:即使只修改 1 字节,也要复制整个文件
- 元数据修改:chmod/chown 也会触发 copy-up
# 性能对比:修改大文件# 创建一个 1GB 的文件在 lower 层dd if=/dev/zero of=/tmp/overlay/lower1/bigfile bs=1M count=1024
# 修改 1 字节(触发 copy-up,复制整个 1GB 文件)time echo "x" >> /tmp/overlay/merged/bigfile# real 0m0.832s ← copy-up 1GB 文件的时间
# 对比:直接在 upperdir 创建新文件time dd if=/dev/zero of=/tmp/overlay/merged/newfile bs=1M count=1024# real 0m0.521s ← 直接写入,无需 copy-up在容器中修改大文件会导致 copy-up,消耗大量磁盘 IO 和时间。最佳实践是:大文件应放在 Volume 中,而非容器的可写层。
四、Whiteout 机制
4.1 删除文件:Whiteout 标记
当删除 lowerdir 中的文件时,OverlayFS 不能真的删除 lowerdir 的文件(它是只读的),而是在 upperdir 中创建一个 whiteout 标记文件(0 字节的字符设备,major=0, minor=0):
# 删除 lower 层的文件rm /tmp/overlay/merged/config.txt
# 查看 upperdir——出现了 whiteout 文件ls -la /tmp/overlay/upper/# -rw-r--r-- base.txt# c--------- config.txt ← whiteout 标记(字符设备 0:0)
# whiteout 文件的设备号stat /tmp/overlay/upper/config.txt# Device type: 0,0 ← major=0, minor=0 标识 whiteout4.2 删除目录:Opaque 目录
删除 lowerdir 中的目录更复杂——需要在 upperdir 中创建同名目录,并设置 opaque 标记(扩展属性 trusted.overlay.opaque=y):
# 创建目录结构mkdir -p /tmp/overlay/lower1/mydirecho "file1" > /tmp/overlay/lower1/mydir/file1.txtecho "file2" > /tmp/overlay/lower1/mydir/file2.txt
# 重新挂载(确保看到最新内容)sudo mount -t overlay overlay /tmp/overlay/merged \ -o lowerdir=/tmp/overlay/lower2:/tmp/overlay/lower1,\upperdir=/tmp/overlay/upper,workdir=/tmp/overlay/work
# 删除目录rm -rf /tmp/overlay/merged/mydir
# 查看 upperdirls -la /tmp/overlay/upper/mydir/# -c--------- file1.txt ← whiteout# -c--------- file2.txt ← whiteout
# 或者使用 opaque 目录(更高效)# 设置扩展属性setfattr -n trusted.overlay.opaque -v "y" /tmp/overlay/upper/mydir4.3 Whiteout 类型对比
| Whiteout 类型 | 适用场景 | 实现 |
|---|---|---|
| 字符设备 0:0 | 删除单个文件 | upperdir 中创建 0 字节字符设备 |
| 目录内 whiteout | 删除目录内文件 | 目录内每个文件一个 whiteout |
| Opaque 目录 | 隐藏整个 lower 目录 | 设置 trusted.overlay.opaque 扩展属性 |
五、容器中的 OverlayFS
5.1 Docker 的 OverlayFS 布局
Docker 使用 OverlayFS 的存储驱动为 overlay2,目录结构如下:
/var/lib/docker/overlay2/├── <layer-id-1>/ # 镜像第 1 层(base)│ ├── diff/ # 层内容│ └── link # 短链接名├── <layer-id-2>/ # 镜像第 2 层(app)│ ├── diff/ # 层内容│ ├── lower # 指向下一层的短链接│ └── link # 短链接名├── <container-id>/ # 容器可写层│ ├── diff/ # upperdir(容器修改)│ ├── lower # 指向镜像层的短链接│ ├── merged/ # 合并后的文件系统│ └── work/ # workdir└── l/ # 短链接目录 ├── <short-id-1> -> ../<layer-id-1>/diff ├── <short-id-2> -> ../<layer-id-2>/diff └── ...# 查看容器的 OverlayFS 挂载mount | grep overlay
# 查看容器的镜像层docker inspect nginx --format '{{json .RootFS.Layers}}' | python3 -m json.tool
# 查看容器可写层的大小docker ps -s# SIZE: 容器可写层大小# VIRTUAL SIZE: 镜像 + 可写层总大小5.2 镜像层复用
OverlayFS 的最大优势是镜像层复用——多个容器共享同一个镜像的只读层:
5.3 容器层叠模型
| 层 | 读写性 | 内容 | 生命周期 |
|---|---|---|---|
| 镜像基础层 | 只读 | OS 基础文件 | 镜像存在期间 |
| 镜像中间层 | 只读 | 应用依赖 | 镜像存在期间 |
| 镜像顶层 | 只读 | 应用代码 | 镜像存在期间 |
| 容器可写层 | 读写 | 运行时修改 | 容器删除即丢失 |
| Volume | 读写 | 持久化数据 | 独立于容器生命周期 |
六、容器 OverlayFS 的完整挂载路径
从 docker run 到内核 mount 系统调用,OverlayFS 的挂载经过以下步骤:
七、OverlayFS 与其他存储驱动对比
7.1 Docker 存储驱动对比
| 存储驱动 | 技术 | 性能 | 稳定性 | 适用场景 |
|---|---|---|---|---|
| overlay2 | OverlayFS | 默认推荐 | ||
| fuse-overlayfs | FUSE OverlayFS | Rootless 容器 | ||
| devicemapper | Device Mapper | 旧系统兼容 | ||
| btrfs | Btrfs 子卷 | Btrfs 文件系统 | ||
| zfs | ZFS 数据集 | ZFS 文件系统 | ||
| vfs | 纯目录复制 | 调试/测试 |
# 查看当前存储驱动docker info | grep "Storage Driver"# Storage Driver: overlay2
# 修改存储驱动(需要清空 /var/lib/docker)# 编辑 /etc/docker/daemon.json{ "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true" ]}八、OverlayFS 高级特性
8.1 多层 lowerdir 优化
Linux 4.0+ 支持 OverlayFS 多层 lowerdir(最多 500 层),避免了深层嵌套挂载:
# 旧方式:嵌套挂载(每 128 层需要一次嵌套)# mount -t overlay overlay /merged -o lowerdir=l3:l2:l1,upperdir=upper,workdir=work# mount -t overlay overlay /merged2 -o lowerdir=l6:l5:l4:/merged,upperdir=upper2,workdir=work2
# 新方式:多层 lowerdir(一次挂载)sudo mount -t overlay overlay /merged \ -o lowerdir=l6:l5:l4:l3:l2:l1,upperdir=upper,workdir=work8.2 OverlayFS 元数据复制
Linux 5.11+ 支持 metacopy 特性——当只修改文件元数据(chmod/chown)时,只复制元数据而非整个文件:
# 启用 metacopy(在 daemon.json 中)# "storage-opts": ["overlay2.metacopy=on"]
# 效果:chmod 只复制元数据(几 KB),而非整个文件8.3 index 和 xino 选项
# index: 硬链接保证(同一 lower 文件的硬链接在 merged 中保持一致)# "storage-opts": ["overlay2.index=on"]
# xino: 扩展 inode 编号(避免 inode 冲突)# "storage-opts": ["overlay2.xino=on"]九、动手实践
9.1 完整的 OverlayFS 容器文件系统模拟
#!/bin/bash# 模拟 Docker 的 OverlayFS 容器文件系统
# 1. 创建镜像层mkdir -p /tmp/container-sim/{layer1,layer2,layer3,upper,work,merged}
# Layer 1: 基础系统mkdir -p /tmp/container-sim/layer1/{bin,lib,etc,dev,proc,sys,usr,var}echo "root:x:0:0:root:/root:/bin/bash" > /tmp/container-sim/layer1/etc/passwdecho "nameserver 8.8.8.8" > /tmp/container-sim/layer1/etc/resolv.conf
# Layer 2: 应用依赖mkdir -p /tmp/container-sim/layer2/usr/local/{bin,lib}echo "#!/bin/sh" > /tmp/container-sim/layer2/usr/local/bin/myappecho "echo 'Hello from myapp!'" >> /tmp/container-sim/layer2/usr/local/bin/myappchmod +x /tmp/container-sim/layer2/usr/local/bin/myapp
# Layer 3: 应用配置mkdir -p /tmp/container-sim/layer3/etc/myappecho "port=8080" > /tmp/container-sim/layer3/etc/myapp/config.ini
# 2. 挂载 OverlayFS(模拟容器启动)sudo mount -t overlay mycontainer /tmp/container-sim/merged \ -o lowerdir=/tmp/container-sim/layer3:/tmp/container-sim/layer2:/tmp/container-sim/layer1,\upperdir=/tmp/container-sim/upper,workdir=/tmp/container-sim/work
# 3. 查看合并后的文件系统echo "=== 合并后的文件系统 ==="find /tmp/container-sim/merged -type f
# 4. 修改文件(触发 copy-up)echo "nameserver 1.1.1.1" > /tmp/container-sim/merged/etc/resolv.conf
# 5. 查看可写层echo "=== 可写层(upperdir) ==="find /tmp/container-sim/upper -type f
# 6. 删除文件(创建 whiteout)rm /tmp/container-sim/merged/etc/myapp/config.ini
# 7. 查看 whiteoutecho "=== Whiteout 标记 ==="ls -la /tmp/container-sim/upper/etc/myapp/
# 8. 清理sudo umount /tmp/container-sim/mergedrm -rf /tmp/container-sim9.2 镜像层大小分析
#!/bin/bash# 分析 Docker 镜像的层大小
IMAGE=$1
echo "=== 镜像层分析 ==="echo "层 ID | 大小 | 命令"echo "------|------|------"
# 获取镜像历史docker history $IMAGE --no-trunc --format \ '{{.ID}} | {{.Size}} | {{.CreatedBy}}' | head -20
echo ""echo "=== 总大小 ==="docker images $IMAGE --format '{{.Size}}'
echo ""echo "=== 层复用分析 ==="echo "使用该镜像层的容器:"docker ps --filter "ancestor=$IMAGE" --format "{{.ID}}\t{{.Names}}"十、OverlayFS 常见问题与排查
10.1 可写层膨胀
容器运行时间越长,可写层可能越大(日志、缓存、临时文件):
# 查看容器的可写层大小docker ps -s# SIZE 列显示可写层大小
# 查看可写层中的文件docker diff mycontainer# A /app/logs/access.log ← 新增文件# C /etc/nginx/nginx.conf ← 修改文件# D /app/cache/old_data ← 删除文件(whiteout)
# 清理可写层中的大文件docker exec mycontainer find /app/logs -name "*.log" -mtime +7 -delete10.2 OverlayFS 与 inotify
OverlayFS 对 inotify 的支持有限——在 merged 目录上的 inotify 监听可能无法正确报告 lowerdir 中的变更:
# inotify 在 OverlayFS 上的已知限制# 1. lowerdir 中的文件变更可能不会触发 inotify 事件# 2. copy-up 后的文件变更可以正常触发# 3. Linux 5.11+ 改善了 inotify 支持
# 解决方案:将需要 inotify 的目录放在 Volume 中docker run -v /app/data:/app/data myapp10.3 OverlayFS 性能调优
# 1. 减少层数(合并 RUN 指令)# 每条 RUN 一个层# RUN apt-get update# RUN apt-get install -y nginx# 合并# RUN apt-get update && apt-get install -y nginx
# 2. 使用 BuildKit 缓存挂载# RUN --mount=type=cache,target=/var/cache/apt \# apt-get update && apt-get install -y nginx
# 3. 大文件使用 Volume# docker run -v /data:/app/data myapp
# 4. 查看 OverlayFS 的内核统计cat /proc/fs/overlay/stats附、实践:手工挂载 OverlayFS
本节手工创建 OverlayFS 目录结构并挂载,观察 copy-up 和 whiteout 机制。所有命令需要 root 权限。
附.1 创建 OverlayFS 目录结构
mkdir -p /tmp/overlay-demo/{lower1,lower2,upper,work,merged}
# 在 lower 层创建文件echo "I am base layer (lower1)" > /tmp/overlay-demo/lower1/base.txtecho "I am app layer (lower2)" > /tmp/overlay-demo/lower2/app.txtecho "I am shared config" > /tmp/overlay-demo/lower1/config.txtecho "I will be modified" > /tmp/overlay-demo/lower1/modify.txt附.2 挂载 OverlayFS
mount -t overlay overlay \ -o lowerdir=/tmp/overlay-demo/lower2:/tmp/overlay-demo/lower1,\upperdir=/tmp/overlay-demo/upper,workdir=/tmp/overlay-demo/work \ /tmp/overlay-demo/merged注意:
lowerdir中多个目录用:分隔,右侧优先级更高。这里lower2在前,lower1在后,所以lower2的文件会覆盖lower1中同名文件。
附.3 查看合并后的文件
ls /tmp/overlay-demo/merged/# app.txt base.txt config.txt modify.txt
cat /tmp/overlay-demo/merged/base.txt# I am base layer (lower1)
cat /tmp/overlay-demo/merged/app.txt# I am app layer (lower2)OverlayFS 将 lower1 和 lower2 的文件”叠加”成一个统一视图。
附.4 Copy-up 测试:修改文件
# 修改 merged 中的文件echo "I am modified!" > /tmp/overlay-demo/merged/modify.txt
# 查看 merged 中的内容(已修改)cat /tmp/overlay-demo/merged/modify.txt# I am modified!
# 查看 upperdir 中的内容(copy-up 发生在这里)cat /tmp/overlay-demo/upper/modify.txt# I am modified!
# 查看 lowerdir 中的原始文件(未变!)cat /tmp/overlay-demo/lower1/modify.txt# I will be modified关键观察:修改文件时,OverlayFS 将文件从 lowerdir 复制到 upperdir(copy-up),然后在 upperdir 中修改。lowerdir 的原始文件始终不变——这正是容器镜像层可以安全共享的原因。
附.5 Whiteout 测试:删除文件
# 删除 merged 中的 app.txtrm /tmp/overlay-demo/merged/app.txt
# merged 中已无 app.txtls /tmp/overlay-demo/merged/# base.txt config.txt modify.txt
# upperdir 中出现 whiteout 标记(字符设备 0,0)ls -la /tmp/overlay-demo/upper/# c--------- 2 root root 0, 0 May 8 01:33 app.txt# -rw-r--r-- 1 root root 15 May 8 01:33 modify.txt
# lowerdir 中的原始文件仍在cat /tmp/overlay-demo/lower2/app.txt# I am app layer (lower2)关键观察:删除文件时,OverlayFS 在 upperdir 中创建一个**字符设备(0,0)**作为 whiteout 标记。merged 层看到 whiteout 标记后,会忽略 lowerdir 中的同名文件。lowerdir 的原始文件始终不变。
注意:实验结束后清理:
umount /tmp/overlay-demo/merged && rm -rf /tmp/overlay-demo
十一、本章小结
上一章剖析了Cgroup v2 的资源控制。
| 概念 | 说明 | 关键点 |
|---|---|---|
| lowerdir | 只读底层目录 | 可多层,右优先 |
| upperdir | 可写上层目录 | 容器修改存储于此 |
| workdir | 工作目录 | 内核内部使用 |
| copy-up | 写时复制 | 修改 lower 文件时触发 |
| whiteout | 删除标记 | 字符设备 0:0 |
| opaque | 目录隐藏 | 扩展属性标记 |
OverlayFS 是容器快速启动和镜像层复用的关键。理解 copy-up 和 whiteout 机制,有助于优化 Dockerfile(减少层数、避免大文件修改)和排查存储问题(可写层膨胀、性能下降)。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






