第二部分-Docker核心原理——09. 联合文件系统(UnionFS)
09. 联合文件系统(UnionFS)
1. UnionFS 概述
联合文件系统(Union File System)是一种将多个目录(分支)合并成单个视图的文件系统。它是 Docker 镜像分层和容器高效运行的核心技术。
┌─────────────────────────────────────────────────────────────┐ │ UnionFS 工作原理 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 统一视图 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ merged/ │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ │ │ a │ │ b │ │ c │ │ d │ │ e │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ 合并 │ │ ┌─────────────────┼─────────────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Layer 3 │ │ Layer 2 │ │ Layer 1 │ │ │ │ (容器层) │ │ (镜像层) │ │ (镜像层) │ │ │ │ 可读写 │ │ 只读 │ │ 只读 │ │ │ │ │ │ c, e │ │ a, b │ │ │ │ d │ │ │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘2. 常见的 UnionFS 实现
| 文件系统 | 特点 | 适用场景 |
|---|---|---|
| OverlayFS | 内核原生,性能好,稳定 | Linux 内核 3.18+,Docker 默认 |
| Overlay2 | OverlayFS 改进版,支持更多层 | Linux 内核 4.0+,推荐 |
| AUFS | 功能丰富,但未进内核主线 | Ubuntu/Debian 旧版本 |
| devicemapper | 基于块设备,性能较差 | CentOS/RHEL 旧版本 |
| btrfs | 子卷快照,功能强大 | 特殊需求 |
| zfs | 功能强大,适合大型存储 | 特殊需求 |
3. OverlayFS 架构
3.1 OverlayFS 组成部分
┌─────────────────────────────────────────────────────────────┐ │ OverlayFS 组成 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ mount point │ │ │ │ (merged) │ │ │ └─────────────────────────────────────────────────────┘ │ │ ▲ │ │ │ │ │ ┌───────────────────────┼───────────────────────┐ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ lowerdir │ │ lowerdir │ ... │ upperdir │ │ │ │ (只读) │ │ (只读) │ │ (可写) │ │ │ │ 层 1 │ │ 层 2 │ │ 容器层 │ │ │ └───────────┘ └───────────┘ └───────────┘ │ │ │ │ workdir: 用于原子操作的工作目录 │ │ │ └─────────────────────────────────────────────────────────────┘3.2 OverlayFS 目录结构
# Docker OverlayFS 目录/var/lib/docker/overlay2/# 典型目录结构├──<layer-id-1>/# 镜像层 1│ ├── diff/# 该层的实际文件│ ├──link# 指向自身的短链接│ └── lower# 指向父层的链接├──<layer-id-2>/# 镜像层 2│ ├── diff/ │ ├──link│ └── lower ├──<container-id>/# 容器层│ ├── diff/# 容器修改的文件│ ├──link│ ├── lower# 指向所有父层│ ├── merged/# 合并后的视图(挂载点)│ └── work/# 工作目录└── l/# 短链接目录├──<short-id-1>->../<layer-id-1>/diff └──<short-id-2>->../<layer-id-2>/diff4. OverlayFS 实践
4.1 手动创建 OverlayFS
# 创建目录结构mkdir-p/tmp/overlay/{lower,upper,work,merged}# 创建测试文件echo"Hello from lower layer">/tmp/overlay/lower/hello.txtecho"Lower file">/tmp/overlay/lower/lower.txt# 挂载 OverlayFSsudomount-toverlay overlay\-olowerdir=/tmp/overlay/lower,\upperdir=/tmp/overlay/upper,\workdir=/tmp/overlay/work\/tmp/overlay/merged# 查看合并视图ls-la/tmp/overlay/merged/cat/tmp/overlay/merged/hello.txt# 修改文件(会写入 upperdir)echo"Modified in container">>/tmp/overlay/merged/hello.txt# 查看 upperdir 内容ls-la/tmp/overlay/upper/# 多层 lowerdir(多个只读层)mkdir-p/tmp/overlay/{lower1,lower2,upper2,work2,merged2}echo"Layer 1">/tmp/overlay/lower1/file1.txtecho"Layer 2">/tmp/overlay/lower2/file2.txtsudomount-toverlay overlay\-olowerdir=/tmp/overlay/lower2:/tmp/overlay/lower1,\upperdir=/tmp/overlay/upper2,\workdir=/tmp/overlay/work2\/tmp/overlay/merged2ls/tmp/overlay/merged2/# file1.txt file2.txt# 卸载sudoumount/tmp/overlay/merged4.2 查看 Docker OverlayFS
# 查看存储驱动dockerinfo|grep-A3"Storage Driver"# 查看镜像层dockerhistoryubuntu:20.04# 查看镜像层目录IMAGE_ID=$(dockerinspect--format='{{.Id}}'ubuntu:20.04)ls-la/var/lib/docker/overlay2/# 查看镜像层关系find/var/lib/docker/overlay2-name"lower"-execcat{}\;# 查看容器层CONTAINER_ID=$(dockerrun-dubuntu:20.04sleep3600)ls-la/var/lib/docker/overlay2/$(dockerinspect-f'{{.GraphDriver.Data.MergedDir}}'$CONTAINER_ID)# 查看容器修改的文件find/var/lib/docker/overlay2/$(dockerinspect-f'{{.GraphDriver.Data.UpperDir}}'$CONTAINER_ID)-typef5. 写时复制(Copy-on-Write)
5.1 CoW 原理
┌─────────────────────────────────────────────────────────────┐ │ 写时复制(CoW)原理 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 读取文件 /etc/hosts │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 容器层 (upper) 查找 → 未找到 │ │ │ │ 镜像层 (lower) 查找 → 找到,直接返回 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 修改文件 /etc/hosts │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 1. 从镜像层读取原文件 │ │ │ │ 2. 复制到容器层(upperdir) │ │ │ │ 3. 在容器层修改 │ │ │ │ 4. 后续读取从容器层读取 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 删除文件 │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 在容器层创建 .wh. 文件标记删除 │ │ │ │ 白色文件(whiteout)遮蔽下层文件 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘5.2 CoW 演示
# 启动容器dockerrun-it--nametestubuntu:20.04bash# 容器内操作root@container:/# cat /etc/hostsroot@container:/# echo "test" > /test.txtroot@container:/# rm /etc/hostsroot@container:/# exit# 查看容器层变化CONTAINER_ID=$(dockerps-a-q--filtername=test)UPPER_DIR=$(dockerinspect-f'{{.GraphDriver.Data.UpperDir}}'$CONTAINER_ID)# 查看新增的文件ls-la$UPPER_DIR/# 会看到 test.txt 和 .wh.hosts(删除标记)# 查看镜像层和容器层差异dockerdiff$CONTAINER_ID# C /etc/hosts # 修改# A /test.txt # 新增6. Docker 镜像分层
6.1 镜像层结构
# Dockerfile 示例 FROM ubuntu:20.04 # 第1层:基础镜像 RUN apt-get update # 第2层:更新软件源 RUN apt-get install -y nginx # 第3层:安装 nginx COPY index.html /var/www/html/ # 第4层:复制文件 CMD ["nginx", "-g", "daemon off;"] # 第5层:启动命令# 查看构建过程中的层dockerbuild --no-cache-tmyapp.# 查看镜像层dockerhistorymyapp# 查看每层大小dockerhistory--no-trunc myapp|awk'{print $1, $2, $5}'# 查看镜像的 rootfs 信息dockerinspect myapp|grep-A20"RootFS"6.2 层共享与复用
# 多个镜像共享基础层REPO1=$(dockerpull ubuntu:20.04|grepDigest)REPO2=$(dockerpull ubuntu:20.04|grepDigest)# 两个镜像共享相同的层,不会重复存储# 查看共享的层dockerimage inspect ubuntu:20.04|grep-A10"GraphDriver"# 删除镜像时,只有没有被其他镜像引用的层才会被删除dockerrmi ubuntu:20.047. 层缓存优化
7.1 优化 Dockerfile
# ❌ 不好:每次都会重新执行 FROM ubuntu:20.04 RUN apt-get update RUN apt-get install -y python3 nginx COPY . /app # ✅ 好:利用层缓存 FROM ubuntu:20.04 # 先复制依赖文件 COPY requirements.txt /app/ # 安装依赖(只有 requirements.txt 变化才重新安装) RUN apt-get update && apt-get install -y python3 nginx # 最后复制代码 COPY . /app # ✅ 更好:合并命令减少层数 FROM ubuntu:20.04 RUN apt-get update && \ apt-get install -y python3 nginx && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* COPY . /app7.2 查看层缓存
# 构建时查看缓存使用dockerbuild --no-cache-tmyapp.dockerbuild-tmyapp.# 第二次构建会使用缓存# 强制使用缓存dockerbuild --cache-from myapp-tmyapp:v2.# 查看构建历史dockerhistory--no-trunc myapp8. Docker 存储驱动配置
# 查看当前存储驱动dockerinfo|grep"Storage Driver"# 配置存储驱动 /etc/docker/daemon.json{"storage-driver":"overlay2","storage-opts":["overlay2.override_kernel_check=true","overlay2.size=20G"]}# 重启 Dockersudosystemctl restartdocker# 查看存储驱动详情dockersystem info|grep-A10"Storage Driver"9. 常用命令总结
| 操作 | 命令 |
|---|---|
| 查看存储驱动 | docker info |
| 查看历史 | docker history |
| 查看层变化 | docker diff |
| 查看镜像文件 | docker inspect |
| 清理未使用层 | docker system prune |
| 查看层大小 | docker system df |
10. 实战演练
# 1. 创建测试 Dockerfilecat>Dockerfile<<EOF FROM alpine:latest RUN echo "Layer 1" > /layer1.txt RUN echo "Layer 2" > /layer2.txt RUN echo "Layer 3" > /layer3.txt EOF# 2. 构建镜像dockerbuild-ttest-layers.# 3. 查看层信息dockerhistorytest-layers# 4. 查看每个层的大小dockerhistory--no-trunc test-layers|awk'{print $1, $2, $5}'# 5. 运行容器并修改dockerrun-d--nametest-container test-layerssleep3600# 6. 查看容器修改dockerexectest-containertouch/container-file.txtdockerdifftest-container# 7. 查看存储目录CONTAINER_ID=$(dockerinspect-f'{{.Id}}'test-container)ls-la/var/lib/docker/overlay2/$CONTAINER_ID/diff/# 8. 清理dockerrm-ftest-containerdockerrmi test-layers11. 常见问题
Q1: 为什么 OverlayFS 层数有限制?
OverlayFS 内核限制最多 128 层,Docker 默认最多 128 层镜像。
Q2: 如何查看层是否被共享?
# 使用 docker image inspect 查看 layer iddockerimage inspect--format='{{.RootFS.Layers}}'image1dockerimage inspect--format='{{.RootFS.Layers}}'image2Q3: 容器修改的文件在哪里?
在容器的 upperdir 目录中:/var/lib/docker/overlay2/<container-id>/diff/
12. 小结
- UnionFS实现镜像分层和写时复制
- Overlay2是 Docker 默认存储驱动
- 镜像由多层只读层组成
- 容器添加一层可写层(upperdir)
- 写时复制(CoW)节省存储空间
- 层共享减少镜像存储和传输
- 合理组织 Dockerfile 利用层缓存
- 注意层数限制(128层)
