章节索引 :

UnionFS 与 Docker

我们自己在上一节留下了不少问题,在自制 rootfs 有很多不完善的地方,在 Docker 产品的方面,就对这些部分进行了补足, 解决的方案,基本都是围绕 UnionFS (联合文件系统)展开的,本节我们会讲讲 UnionFS 和它在 Docker中 的应用。

1. 什么是文件系统

计算机的文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据不必关心数据实际保存在硬盘(或者光盘)的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中。

一句话总结,文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型。

2. 什么是 UnionFS

联合文件系统(Union File System):2004 年由纽约州立大学开发,它可以把多个目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFS可以把只读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以保存到可写文件系统当中。

目前有多种文件系统可以被当作联合文件系统,CentOS8 使用的是 overlay2,下面,我们尝试着挂载一个 overlay2 文件系统。

/root/test 目录下,建立一个unionfs 目录,它的目录结构如下(a,b,c是文件,其他都是目录):

unionfs
├── lower1
│   ├── a
│   └── b
├── lower2
│   └── a
├── merged
├── upper
│   └── c
└── work

进入 unionfs 目录,使用 mount 命令挂载:

cd unionfs
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged

如上,挂载了一个名为 overlay 的 overlay2 类型的文件系统,挂载点为 merged 目录。

查看 merged 目录的层次:

[root@centos8 unionfs]# tree merged
merged
├── a
├── b
└── c

查看这些文件的内容:

[root@centos8 unionfs]# for i in `ls merged`;do echo $i: `cat merged/$i`;done
a: in lower1
b: in lower1
c: in upper

可以看到,从 merged 视角,位于 lower2 的 a 文件被 lower1 的 a 文件覆盖;b 文件位于 lower1,c 文件位于 upper,符合从高到低 upper->lower1->lower2 的层次结构。

我们按照如下操作来验证 unionfs 的分层特性:

[root@centos8 unionfs]# touch merged/d
[root@centos8 unionfs]# ls merged/
a  b  c  d
[root@centos8 unionfs]# ls upper/
c  d
[root@centos8 unionfs]# ls lower1
a  b
[root@centos8 unionfs]# ls lower2
a

可以看到对 merged 目录的改动同步至 upper 目录中,并不会影响到 lower 目录。

3. Docker 如何使用 UnionFS?

Docker 的官方文档中有一张图片,很好地展示了 Docker 使用 UnionFS 搭建的分层结构的状态。
图片描述

使用 UnionFS 搭建的分层结构

图中的容器是运行在 debian 容器环境中的 apache 网页应用,这个环境还提供了 emacs 编辑器功能。

将之前我们自己构建的 rootfs 与上面这张图片对比,会发现我们将所有系统文件、运行库文件和上层应用,都放到了一个 rootfs 里面,这样做缺乏灵活性,增大了维护的复杂度。而 Docker 引入了层(layer)的概念,将 rootfs 的内容进行了分层管理,有系统层,运行库依赖层等等,可以一层接一层进行增量式挂载叠加。启动容器的时候通过 UnionFS 把相关的层挂载到一个目录,作为容器的根 rootfs。

借助于 UnionFS,容器内部的更改都被保存到了最上面的读写层,而其他层都是只读的,这样中间的只读 rootfs 是可以被多个容器复用的。UnionFS 将文件的更新挂载到老的文件之上,而不去修改那些不更新的内容,这就意味着即使虚拟的文件系统被反复修改,也能保证宿主机空间占用保持一个较低水平。

4. 衍生出的 Docker 概念

4.1 Docker 镜像

我们将中间只读的 rootfs 的集合称为 Docker 镜像,我们在后面的部分会讲到,Docker 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。UnionFS 使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

4.2 Docker 容器

Docker 容器与我们之前的容器在本质上没有区别,我们之前的容器更偏向抽象的技术概念,而受到在 Docker 管理约束的容器就是 Docker 容器,它会带有 Docker 产品的一些特征和功能。

Docker镜像 和 Docker容器 的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。
从文件系统来看,Docker容器比Docker镜像多一层可读写的文件系统挂载层,从生命周期来看,Docker容器可以被创建、启动、停止、删除、暂停等。

5. 小结

在 rootfs 的基础上,Docker 公司创新性地提出了使用 UnionFS,多个增量 rootfs 联合挂载一个完整 rootfs 的方案,通过“分层镜像”的设计,围绕 Docker 镜像,大家甚至可以协同工作,再加上 Docker 官方提供的镜像仓库,进一步减少了共享镜像的成本,这大大提高了开发部署的效率。