深度解析 Linux OverlayFS:透明玻璃、便利贴与写时复制
一、开篇:当你遇到 Overlay,你遇到了什么?
在 Linux 系统运维和容器开发的日常工作中,你大概率会在以下场景中与“Overlay”狭路相逢:
- 执行
df -Th时,发现某个挂载点的类型是overlay; - 在 Docker 的
docker info输出中,看到Storage Driver: overlay2; - 或者你在制作嵌入式系统根文件系统时,需要把只读的 squashfs 和可写的临时目录合并起来。
如果你把这里的 Overlay 理解成“网络隧道”或者“视频画中画”,那就跑偏了。在 Linux 文件系统层面,Overlay 指的就是 OverlayFS——一种联合挂载文件系统。它的终极使命很简单:将两个或多个目录(其中至少一个是只读的)叠加(Stack)起来,对外呈现为一个统一的、可读写的虚拟目录。
二、形象类比:一张原稿纸 + 一块透明玻璃板
为了让你脑海中立刻建立起画面感,请想象一张桌子:
- 桌面底层:铺着一张写满了字的原稿纸。这张纸被死死按在桌上,你不能涂改它,也不能撕掉它(只读层,Lowerdir)。
- 桌面上层:悬空盖着一块完全透明的玻璃板(读写层,Upperdir)。你可以用白板笔在玻璃上随意写字、画图、贴便利贴。
- 你的视角:你站在正上方低头看下去,你眼里看到的“完整画面”,就是玻璃上的笔迹和原稿纸内容叠加在一起的合成图(合并视图,Merged)。
OverlayFS 干的事,就是帮你制造出这个“从上往下看”的合并视角。你在这个视角里做的一切修改,实际上都只发生在玻璃板(上层)上,底下的原稿纸(下层)永远保持原样。
三、核心概念与数据结构的硬核拆解
在 Linux 内核中,OverlayFS 的挂载涉及以下四个关键目录,缺一不可:
| 参数 | 全称 | 角色定位 | 读写属性 | 生命周期 |
|---|---|---|---|---|
| Lowerdir | 下层目录 | 原稿纸:基础文件系统,存放底层静态数据 | 只读(Read-Only) | 永久存在,永不修改 |
| Upperdir | 上层目录 | 玻璃板:存放所有修改、新增、删除操作的痕迹 | 可读写(Read-Write) | 随容器或会话创建,可销毁 |
| Workdir | 工作目录 | 草稿纸:用于 OverlayFS 内部原子操作的临时空间 | 可读写 | 必须为空目录,用于辅助事务操作 |
| Merged | 挂载点 | 你的眼睛:最终呈现的虚拟合并视图 | 可读写(对用户而言) | 用户在此目录进行操作 |
特别注意: workdir 必须和 upperdir 在同一个文件系统上,这是内核的硬性要求。
四、动手实验:手把手教你创建一个 OverlayFS
理论总是枯燥的,我们来在终端里实际操练一把。不需要装任何额外软件,Linux 内核 3.18+ 均原生支持。
# 1. 创建实验所需的所有目录
mkdir -p /tmp/overlay-test/{lower,upper,work,merged}
# 2. 在下层(只读层)放入一些原始文件
echo "我是底层原稿,不可修改" > /tmp/overlay-test/lower/immutable.txt
echo "底层共享数据" > /tmp/overlay-test/lower/share.txt
# 3. 执行 OverlayFS 挂载命令(这是核心)
sudo mount -t overlay overlay \
-o lowerdir=/tmp/overlay-test/lower,\
upperdir=/tmp/overlay-test/upper,\
workdir=/tmp/overlay-test/work \
/tmp/overlay-test/merged
# 4. 现在进入 merged 目录,看看你看到了什么
ls /tmp/overlay-test/merged/
# 输出: immutable.txt share.txt
此时,你看到的 merged 目录就是底层两个文件的完整镜像。
接下来见证奇迹(写时复制,CoW):
# 5. 尝试修改底层来的文件
echo "我在玻璃板上加了注释" >> /tmp/overlay-test/merged/share.txt
# 6. 查看 merged 里的文件,发现已改变
cat /tmp/overlay-test/merged/share.txt
# 输出:底层共享数据\n我在玻璃板上加了注释
# 7. 查看底层的原始文件,发现它丝毫未变!
cat /tmp/overlay-test/lower/share.txt
# 输出:底层共享数据(依旧是原样)
为什么会这样? 这就是 写时复制(Copy-on-Write,CoW) 机制在起作用。当你试图修改下层(Lower)的只读文件时,OverlayFS 会先将该文件完整复制一份到上层(Upper)目录,然后你的所有修改都发生在上层这个副本上。从此,merged 看到的将是上层副本,下层的原文件被“透明地遮挡”了。
再试试删除操作(Whiteout 遮挡):
# 8. 在 merged 里删除一个下层文件
rm /tmp/overlay-test/merged/immutable.txt
# 9. 查看 merged,确实不见了
ls /tmp/overlay-test/merged/
# 输出: share.txt
# 10. 但查看 lower 目录,文件依然存在
ls /tmp/overlay-test/lower/
# 输出: immutable.txt share.txt
# 11. 查看 upper 目录,你会发现一个特殊文件
ls /tmp/overlay-test/upper/
# 输出: share.txt immutable.txt(实际上这是一个 Character Device,即 Whiteout 标记)
OverlayFS 不会真正删除下层文件,而是在上层创建一个名为 immutable.txt 的白屏文件(Whiteout)。它是一种特殊的字符设备,作用就是告诉内核:“在合并视图里,把这个名字给我屏蔽掉”。
五、进阶:多层级联(Lowerdir 的多层堆叠)
现代 Linux 支持 多个 Lowerdir,用冒号(:)分隔。这在 Docker 镜像分层中极其关键。
mount -t overlay overlay \
-o lowerdir=/layer3:/layer2:/layer1,\
upperdir=/upper,workdir=/work /merged
在 Docker 中,镜像就是由几十个只读的 Lower 层堆叠而成。当你要启动一个容器时,Docker 会把这些只读层和你容器的可写层(Upper)合并挂载。最底层的 Lower 是基础操作系统,最上面的 Lower 是你刚写的代码层。 如果上层和下层有同名文件,上层的文件完全覆盖下层(类似于 CSS 的层叠样式表)。
六、OverlayFS 的两代演进:Overlay 与 Overlay2
如果你在 Docker 中配置存储驱动,会发现有 overlay 和 overlay2 两种。它们有什么区别?
- Overlay(第一代):只支持一个 Lowerdir,且底层文件系统必须为 d_type 支持(如 ext4),硬链接支持不完善,容易触发
too many levels of symbolic links错误。 - Overlay2(第二代,当前默认):支持多个 Lowerdir,使用了更稳定的硬链接处理机制,且显著降低了 inode 消耗。除非是极老的 Linux 内核(< 4.0),否则请务必使用 overlay2。
七、在生产环境中的三大应用场景
1. Docker/Kubernetes 容器的镜像分层存储
这是 OverlayFS 最伟大的应用。当你拉取一个 Nginx 镜像时,你没有真正下载“一个大包”,而是下载了多个只读层(如 Debian 底层、Nginx 主程序层、配置文件层)。启动 100 个 Nginx 容器,它们共享这些只读层,只有每个容器的 Upper 层是独立且极其轻量的。这带来了极致的磁盘空间节省和秒级的容器启动速度。
2. 嵌入式 Linux 的只读根文件系统(Squashfs + Overlay)
在路由器、智能摄像头等嵌入式设备中,为了固件安全和防篡改,根文件系统通常做成只读的 Squashfs 镜像。系统启动时,用 OverlayFS 将 Squashfs 作为 Lowerdir,将内存文件系统(tmpfs)作为 Upperdir。用户任何配置修改均保存在内存中,断电即失,但固件永远安全、永不损坏。
3. 系统还原卡/无盘工作站
在学校的计算机教室或网吧,管理员利用 OverlayFS 保护 C 盘系统分区。每次开机,系统分区作为 Lowerdir,内存作为 Upperdir。学生任意折腾、删文件、装软件都只影响 Upper。重启后,丢弃 Upper,系统瞬间恢复如初,无需使用 Ghost 还原。
八、必须警惕的坑与限制(高能警告)
- rename 系统调用的限制:OverlayFS 对跨层重命名(
rename)操作支持不完美。如果你在merged里将文件从一个目录移动到另一个目录,可能会遇到Invalid cross-device link错误,因为这对内核来说意味着复杂的文件复制与删除操作。 - 文件句柄(File Handle)稳定性:当底层 Lowerdir 发生变化(如升级 Docker 镜像的某一层)时,依赖该层启动的长期运行进程可能会因为文件句柄失效而报错
Stale file handle。这是容器环境需要优雅重启 Pod 的重要原因之一。 - 并发写同一个文件:如果有多个进程同时通过
merged写入同一个文件,OverlayFS 的 CoW 机制可能导致数据竞争,需依赖上层应用(如数据库)自身的事务锁机制。 - 磁盘空间满的误判:由于数据实际写在 Upperdir,当你
df -h查看merged时,显示的是整个文件系统的可用空间,但你写文件只会消耗upperdir所在分区的空间。若upperdir所在分区已满,即使merged显示有空间,写入也会报错。
九、总结:一张图厘清所有关系
| 你想做什么? | 数据实际去哪了? | 下层(Lower)受影响吗? |
|---|---|---|
| 新建文件 | 直接写入 upperdir | 完全无影响 |
| 修改下层文件 | 复制到 upperdir 后再修改(CoW) | 完全无影响 |
| 删除下层文件 | 在 upperdir 创建一个 Whiteout 遮罩 | 原文件依旧存在,只是被隐藏 |
| 修改上层文件 | 直接在 upperdir 中修改 | 完全无影响 |
| 删除容器/重启系统 | 直接删除 upperdir 和 workdir | 下层数据纹丝不动,可复用 |
最后的忠告
当你在 Linux 下再遇到 Overlay,请在心里默念三遍:它不是网络隧道,它是一个文件叠层器;它不是把文件搬走,它是在文件上盖了一层玻璃。
如果你是在排障时遇到了 No space left on device 但 df -h 显示没满,请去检查 upperdir 所在分区的 inode(df -i),大概率是 OverlayFS 的 workdir 或 upperdir 产生了过多的小文件占满了 inode。如果你是在配置 Docker 时遇到 overlay2: backing fs not supported,请确保你存放 /var/lib/docker 的磁盘格式是 ext4 或 xfs,千万不要是 aufs 或 nfs。
希望这篇文章能让你对 Linux OverlayFS 有一个全面且扎实的认知。如果你正在某个具体的场景(比如搭建 PXE 无盘系统、调试容器存储问题)中使用它,遇到了卡壳的地方,欢迎随时追问具体的操作细节。