Loading

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,不能每人创建一份浪费空间),但又不能互相干扰

解决方案如下:

  1. rootfs 以只读方式关联给每个容器:容器 1 → rootfs(只读),容器 2 → rootfs(只读)
  2. 为每个容器各创建一个独立的可写目录:容器 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 根目录结构。在打包时,可能还在某些目录下放了运行应用程序所需的依赖库、命令等。

镜像的使用流程

  1. 导入镜像:本质就是把 tar 包解压出来,解压到某个文件夹下,就能看到 /bin/home/opt/etc 等目录
  2. 利用 UnionFS:将导入的镜像(rootfs)作为只读目录,再新建一个可写目录,把这两个目录联合挂载到容器中
  3. 多容器共享:如果多个容器使用相同的文件系统,就共享同一个镜像(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 解压出来的就是 binetcdevhomemedialib64mnt 等目录——就是一个 rootfs

所以不要觉得容器镜像是什么高大上的东西,它本质就是一个 rootfs


十一、总结与下一步

核心概念 要点
容器镜像 本质是压缩包,里面只包含 rootfs(一套 Linux 目录结构),不包含 bootfs
UnionFS 联合文件系统,能把多个目录联合挂载成一个大目录,实现只读层 + 可写层的分离
OverlayFS UnionFS 的一种具体实现,是我们后续重点研究的对象
关键设计 多个容器共享同一个只读 rootfs(镜像),各自拥有独立的可写目录,通过 UnionFS 联合挂载
内核共享 同一宿主机上的多个容器共享该宿主机的内核,宿主机内核有漏洞则影响所有容器

下一步:深入研究 UnionFS(具体是 OverlayFS)到底是怎么实现多个目录联合挂载成一个大目录给容器使用的。至于 cgroup 技术,等容器基本操作学完之后再深入了解。

posted @ 2026-06-19 09:46  知杏  阅读(19)  评论(0)    收藏  举报