UnionFS 联合文件系统与容器镜像
一、回顾:容器文件系统隔离的基本思路
在上节课中,我们了解到,容器要实现文件系统层面的隔离——即容器内的目录读写操作只影响当前容器,不影响其他容器和宿主机。为了实现这种隔离,我们的做法是:在宿主机上的某个路径下,为每个容器定制一套专属的 Linux 系统目录结构(rootfs),然后在容器启动后,通过 chdir 切换到该目录,再用 chroot 将该目录设为容器内的根目录。
用图来描述就是:通过 clone 系统调用创建一个子进程,然后在该进程外围初始化一个 namespace(名称空间),这个名称空间就是我们的容器。容器 1 和容器 2 各自拥有自己的进程和名称空间。
二、没有文件系统隔离会怎样?
如果两个容器仅仅做到了 UTS、IPC、PID、网络、用户空间等维度的隔离,而没有做到挂载目录(Mount)的隔离,那意味着容器 1 和容器 2 直接共享的就是宿主机的文件系统。
宿主机的 rootfs 就是根目录下的那一堆目录结构:
/
├── bin
├── home
├── opt
├── etc
├── mnt
├── lib
└── ...
在这种情况下,容器 1 对 /mnt 目录做了改动,容器 2 直接就能看到;容器 2 的改动,容器 1 也能看到。两个容器完全没有隔离,可以互相影响。
三、为每个容器定制专属 rootfs
为了做到隔离,首先需要引入 CLONE_NEWNS 参数(关于挂载点的隔离),在此基础上,还需要为每个容器定制一个专属的挂载目录。
具体做法是:在宿主机的某个目录下(路径和名字随意),创建一套完整的 Linux 系统目录结构。例如:
- 为容器 1 创建:
/opt/a/b/c/d_rootfs111/ - 为容器 2 创建:
/opt/a/b/c/d_rootfs222/
每个 rootfs 下面都仿照一个操作系统的根目录结构,完整地创建一份:
rootfs111/
├── bin
├── home
├── opt
├── etc
├── mnt
├── lib
└── ...
容器启动后,chdir 切换到对应的 rootfs 目录,再 chroot 将其设为根目录。这样,进入容器后看到的就是 /bin、/home、/opt 等目录,容器里的进程就会以为自己运行在一个完全独立的操作系统之上。
因为容器里跑的就是一系列进程,进程只跟目录结构打交道——跟某些目录下的文件进行读写操作。进程看到自己有一套完整独立的目录结构,就感觉自己拥有了一个独立的操作系统。相当于把容器里所有的进程都"欺骗"了。
四、容器只需要 rootfs,不需要 bootfs
这里要补充一个关键概念:一个完整的操作系统分为两部分:
| 组成部分 | 内容 |
|---|---|
| bootfs | 内核(kernel)以及内核相关的启动文件 |
| rootfs | 根目录下的那一套目录结构(/bin、/home、/opt、/etc 等) |
容器不需要 bootfs,只需要 rootfs 即可。 至于内核,所有容器都是共享宿主机内核的。因此,如果宿主机的内核有安全漏洞,那肯定会影响所有容器。
容器的精髓是隔离。 容器跟虚拟机很像,但它并不是虚拟机——它没有虚拟硬件,也不需要有完整的操作系统,只需要有一套自己的目录结构就可以了。容器里面本质运行的就是一个一个的进程,进程只需要跟目录和文件打交道,不需要内核相关的东西。至于要调用内核功能,用宿主机的就可以了。
五、定制多套 rootfs 的问题——空间浪费
上面的思路大方向是对的,但具体操作手法存在问题:每启动一个容器,就给它定制一套专属的 rootfs,这会产生冗余操作和空间浪费。
原因很简单:容器 1 和容器 2 可能对操作系统的要求是一样的(比如都需要一个 Linux 系统),那大家的目录结构其实是一模一样的。你在 rootfs111 下创建了一套,又在 rootfs222 下创建了一套,这就是在做重复操作。
更关键的是:这些目录结构里面不是空的,里面是要放实际数据的。如果宿主机上运行了 20 个容器,都要求运行 Linux 操作系统,那你就为 20 个容器各创建一份一模一样的 rootfs,相当于放了 20 份一模一样的数据,造成了极大的空间浪费。
六、共享 rootfs 的方案与新问题
既然多个容器的文件系统是一样的,那就不要重复创建多套一模一样的 rootfs 了,只留一份,大家共享,问题不就解决了吗?
于是变成:容器 1 → 指向 rootfs,容器 2 → 也指向同一个 rootfs,共享一份,不浪费空间。
但是,只做到这种程度又有新问题: 如果容器 1 和容器 2 都 chdir + chroot 到同一个 rootfs 下,那大家又共享同一套东西了——容器 1 对某个目录的改动又会影响容器 2,又失去了隔离。
七、只读共享 + 可写目录的设计
那怎么解决这个问题呢?一定要共享(因为大家用的是一模一样的 rootfs,不能每人创建一份浪费空间),但又不能互相干扰。
解决方案如下:
- rootfs 以只读方式关联给每个容器:容器 1 → rootfs(只读),容器 2 → rootfs(只读)
- 为每个容器各创建一个独立的可写目录:容器 1 → 写目录 1(可写),容器 2 → 写目录 2(可写)
这样,每个容器的完整目录结构由两部分构成:
容器的文件系统 = 基础 rootfs(只读) + 可写目录(可写)
工作机制:
- 如果容器发生了文件的增删改操作 → 都往可写目录里写
- 如果是读操作 → 先看可写目录里有没有,有就以可写目录里的为准(因为它代表了最新的操作);没有就去只读的 rootfs 里找
这个设计非常巧妙:
- 大量的读操作,大家共享同一个 rootfs,不浪费空间
- 少量的写操作,各自写到自己的可写目录里,互不干扰
八、UnionFS 联合文件系统
上面的设计有一个关键问题:怎么把多个目录(rootfs 目录 + 可写目录)联合到一起,拼成一个大目录,挂载给容器?
因为在容器内部,进程看到的应该是一整套完整的 Linux 目录结构,而不需要去区分哪个是写目录、哪个是只读目录。
这就用到了 UnionFS(联合文件系统)。
UnionFS 的精髓
UnionFS 可以把多个目录联合到一起,挂载成一个大的文件夹。这个大文件夹包含了:
- 可写目录中的内容
- 只读 rootfs 中的内容
然后将这个联合后的大文件夹挂载给容器。容器内看到的就是一个完整的文件系统。
当容器发生写操作时,UnionFS 负责将写操作分发到可写目录中;当容器要读取可写目录中不存在的内容时,UnionFS 负责去只读 rootfs 中获取。
对于容器来说,它根本不需要区分谁是写目录、谁是读目录。它就看到一套完整的 Linux 目录结构就可以了。底层的读写分发,都是 UnionFS 在负责。
UnionFS 是一个统称
UnionFS 有很多种实现,我们主要研究的是一种叫 OverlayFS(Overlay 联合文件系统) 的实现。Overlay 是 UnionFS 的一种。
九、容器镜像
上面提到的那个只读的 rootfs,其实就是我们所说的容器镜像。
什么是镜像?
镜像本质就是一个压缩包,这个压缩包里面包含着操作系统相关的一些文件。
类比来理解:你平时装操作系统的时候,首先要下载一个镜像文件(可能是 .iso 格式)。ISO 格式本质就是一种压缩包,里面压缩了操作系统的文件。然后你把镜像写到 U 盘里做成启动盘,带着 U 盘到目标主机上安装系统。安装系统的本质就是把镜像中的文件解压释放到目标主机的硬盘里——就像搬家一样。
完整系统镜像 vs 容器镜像
| 完整系统镜像(物理机/虚拟机) | 容器镜像 | |
|---|---|---|
| 内容 | bootfs + rootfs | 仅 rootfs |
| 格式 | 通常是 .iso 格式 |
通常是 .tar 包格式 |
| 用途 | 安装完整操作系统 | 为容器提供文件系统 |
容器镜像中只包含 rootfs,里面打包的就是一堆 Linux 根目录结构。在打包时,可能还在某些目录下放了运行应用程序所需的依赖库、命令等。
镜像的使用流程
- 导入镜像:本质就是把 tar 包解压出来,解压到某个文件夹下,就能看到
/bin、/home、/opt、/etc等目录 - 利用 UnionFS:将导入的镜像(rootfs)作为只读目录,再新建一个可写目录,把这两个目录联合挂载到容器中
- 多容器共享:如果多个容器使用相同的文件系统,就共享同一个镜像(rootfs),但各自拥有不同的可写目录
十、实际验证:容器镜像到底长什么样?
以 Docker 的 CentOS 7 镜像为例,实际验证容器镜像的内容:
# 创建一个目录并进入
mkdir /tmp/test_image && cd /tmp/test_image
# 将已导入的 centos:7 镜像导出为 tar 包
docker save centos:7 -o centos_7.tar
# 解压 tar 包
tar xf centos_7.tar
# 进入解压后的目录(会看到一些元数据文件和一个子目录)
cd <解压出的子目录>
# 解压 layer.tar(这才是真正的 rootfs 内容)
tar -xf layer.tar
# 查看解压结果
ls
# bin dev etc home lib lib64 media mnt opt ...
可以看到,layer.tar 解压出来的就是 bin、etc、dev、home、media、lib64、mnt 等目录——就是一个 rootfs。
所以不要觉得容器镜像是什么高大上的东西,它本质就是一个 rootfs。
十一、总结与下一步
| 核心概念 | 要点 |
|---|---|
| 容器镜像 | 本质是压缩包,里面只包含 rootfs(一套 Linux 目录结构),不包含 bootfs |
| UnionFS | 联合文件系统,能把多个目录联合挂载成一个大目录,实现只读层 + 可写层的分离 |
| OverlayFS | UnionFS 的一种具体实现,是我们后续重点研究的对象 |
| 关键设计 | 多个容器共享同一个只读 rootfs(镜像),各自拥有独立的可写目录,通过 UnionFS 联合挂载 |
| 内核共享 | 同一宿主机上的多个容器共享该宿主机的内核,宿主机内核有漏洞则影响所有容器 |
下一步:深入研究 UnionFS(具体是 OverlayFS)到底是怎么实现多个目录联合挂载成一个大目录给容器使用的。至于 cgroup 技术,等容器基本操作学完之后再深入了解。

浙公网安备 33010602011771号