容器镜像是分层的——基础层 + 应用层 +
可写层。docker run
时把多层叠加成一个统一文件系统视图,这就是
OverlayFS 的工作。
一、先看图
flowchart TB
MERGED[merged(统一视图)]
MERGED --> UPPER[upper(可写层)]
MERGED --> LOWER1[lower-1(只读层)]
MERGED --> LOWER2[lower-2(只读层)]
MERGED --> WORK[work(内部临时)]
subgraph 写操作
WRITE[写文件 X] --> CHECK{X 在 upper?}
CHECK -- 是 --> DIRECT[直接写]
CHECK -- 否 --> COPYUP[copy_up 到 upper<br/>再写]
end
classDef rw fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef ro fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef op fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class UPPER,DIRECT rw
class LOWER1,LOWER2 ro
class WORK,COPYUP,WRITE,CHECK op
class MERGED rw
二、基本原理
mount -t overlay overlay \
-o lowerdir=/lower2:/lower1,upperdir=/upper,workdir=/work \
/merged- lowerdir:只读层,可以多层(冒号分隔),左边优先
- upperdir:可写层
- workdir:OverlayFS 内部原子操作用
- merged:挂载点,用户看到的统一视图
2.1 读操作
先查 upper,没有 → 查 lower(从左到右)。
2.2 写操作(copy_up)
文件不在 upper → 整个文件 copy 到 upper → 再修改。
开销:
- 大文件首次写时延迟高
- 文件元数据和数据都要复制
三、whiteout 与 opaque
3.1 删除文件
删 lower 层的文件 → 不能真删(lower 只读)。OverlayFS 在
upper 创建一个 whiteout
字符设备节点(mknod ... c 0 0)。
ls -la /upper/
# c--------- 1 root root 0, 0 ... deleted-file3.2 删除目录
删除整个目录 → 在 upper 创建同名目录 + 设置
trusted.overlay.opaque=y xattr → 遮盖 lower
层整个目录。
四、metacopy
问题:修改文件 元数据(chmod、chown)也触发整个文件 copy_up → 浪费。
metacopy
优化(5.8+):只复制元数据,不复制数据。上层文件标记
trusted.overlay.metacopy xattr。后续读数据时 →
按需 copy_up 数据。
mount -t overlay overlay -o metacopy=on,...五、redirect_dir
重命名 lower 层目录 → 不能直接 rename(只读)。
redirect_dir 特性(4.10+):在 upper 创建新目录,设置
trusted.overlay.redirect xattr 指向原 lower
路径。
mount -t overlay overlay -o redirect_dir=on,...六、多层 lower
Docker overlay2 驱动利用多层 lower(最多 128 层):
lowerdir=/var/lib/docker/overlay2/<layer-n>/diff:...:/var/lib/docker/overlay2/<layer-1>/diff
每个镜像层是一个 lower。容器层是 upper。
层数限制:
- OverlayFS 本身限制 500 层
- Docker 默认 128 层
- 层越多 → lookup 越慢(每次 path lookup 要查所有层)
七、性能特征
| 操作 | 开销 |
|---|---|
| 读(upper 已有) | 直接,零额外 |
| 读(lower) | 多一次 lookup |
| 首次写(copy_up) | O(文件大小) |
| 后续写 | 直接,零额外 |
| 删除 lower 文件 | mknod whiteout |
| 修改元数据 | 全 copy_up(无 metacopy) |
7.1 inode 消耗
每个 overlay 文件有独立的 overlay inode → 内存开销。大量小文件的容器会消耗可观内存。
7.2 与页缓存
upper 和 lower 的文件是不同 inode → 同一个底层文件在 copy_up 前后占两份页缓存。
八、Docker overlay2 存储驱动
docker info | grep "Storage Driver"
# Storage Driver: overlay2/var/lib/docker/overlay2/<id>/
├── diff/ # 本层新增/修改的文件
├── link # 短 ID 符号链接
├── lower # lower 层引用
├── merged/ # 挂载点(容器运行时)
└── work/
l/ 目录下的短符号链接避免 mount
命令行过长(参数有长度限制)。
九、与其他联合文件系统对比
- AUFS:Docker 早期默认,未入主线
- OverlayFS:3.18 入主线,Docker 默认
- devicemapper thin:块级,不是联合 FS
- ZFS / btrfs snapshot:利用 COW 语义,不是联合
OverlayFS 胜出因素:简单 + 主线内核 + 广泛文件系统支持。
十、小结
- OverlayFS = lower(只读)+ upper(可写)联合视图
- 写触发 copy_up → 大文件首次写有延迟
- whiteout 和 opaque 处理删除
- metacopy / redirect_dir 优化元数据和 rename
- 容器生态的标准存储驱动
参考文献
Documentation/filesystems/overlayfs.rst- Miklos Szeredi, “overlayfs: initial implementation.” v3.18 merge
fs/overlayfs/- Docker documentation, “Use the OverlayFS storage driver.”
工具
mount -t overlaydocker inspect --format '{{.GraphDriver.Data}}' <container>findmnt -t overlayxattr -l(查看 overlay xattr)
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】namespace:容器的内核根基
namespace 把全局内核资源(pid、mount、net、uts、ipc、user、cgroup、time)切成多个互不可见的视图。本文讲 7 种 namespace 的语义、unshare/setns/clone3 API、user ns 的 uid 映射、rootless 容器、namespace 与 cgroup 的分工、以及 pid ns init 的特殊待遇。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。