Skip to main content
每个容器看起来都有完整的文件系统,但磁盘上并没有为每个容器存一份完整拷贝。这背后是**联合文件系统(UnionFS)**在起作用。理解它的分层结构,你才能明白为什么镜像能复用、为什么容器删了数据就没了。

联合文件系统与 overlay2

UnionFS 的核心思想:把多个目录分层叠加,对外呈现为一个统一的文件系统视图。 Docker 默认的存储驱动是 overlay2,它基于 Linux 内核的 OverlayFS 实现。查看你的驱动:

容器的四层目录结构

容器运行时,overlay2 为它准备四个目录。用 docker inspect 可以看到:
输出类似:

镜像层(lowerdir)

镜像层是只读的,来自 docker build 的每条指令。同一台宿主机上,多个镜像共享相同的基础层,磁盘上只存一份。 查看一个镜像的分层:

init 层

LowerDir 里那个带 -init 后缀的目录就是 init 层。它是容器启动时 Docker 在镜像层之上注入的一个特殊只读层,内容只有几个容器专属的配置文件:
  • /etc/hostname:容器的主机名
  • /etc/hosts:容器的 hosts 记录
  • /etc/resolv.conf:DNS 解析配置
这些文件每个容器都不同,不能打进镜像,但又必须出现在容器的文件系统里。Docker 用 init 层把它们”垫”在镜像层之上,既不污染镜像,又让容器启动即拥有正确配置。

容器层(upperdir)

容器层是唯一可写的一层。容器内新建、修改、删除文件,全部落在这里:
  • 新建文件:直接写进容器层
  • 修改镜像层文件:先把文件从只读层复制上来(copy-up),再修改
  • 删除镜像层文件:在容器层写一个 whiteout 标记,把下层文件”遮住”,而不是真的删除只读层

merge 层(mergeddir)

merge 层把只读层和容器层叠加后的统一视图。容器进程看到的 / 就是它。 同名文件的覆盖规则:上层优先。容器层(upperdir)的文件会覆盖镜像层(lowerdir)的同名文件;whiteout 标记则让下层的文件”消失”。

写时复制(Copy-on-Write)

修改只读层文件的完整过程:
1

发起写入

容器内进程修改一个来自镜像层的文件,比如 sed -i 改配置。
2

copy-up

OverlayFS 把该文件从 lowerdir 复制到 upperdir。文件越大,这一步越慢,但只在首次修改时发生
3

写入容器层

修改落在 upperdir 的副本上,镜像层原件保持不变。之后对该文件的读写都直接走容器层。
写时复制保证了镜像层的不可变性:一百个容器共用一个镜像层,谁也改不到它。代价是首次修改大文件有复制开销。

实地查看一个容器的分层

1

启动一个实验容器

2

找到它的分层目录

3

在容器里制造一个改动

4

在宿主机上验证

macOS 用户注意:Docker Desktop 把 /var/lib/docker 放在一个 Linux 虚拟机里,需要用 docker run -it --rm --privileged --pid=host justincormack/nsenter1 进入 VM 后才能访问。

与数据持久化的关系

容器层(upperdir)的生命周期和容器绑定——docker rm 之后,这一层连同里面的数据一起删除。这就是为什么生产数据必须放到 Volume 里,见 Docker 存储

延伸阅读