关于容器技术的原理,我在很早之前翻译过命名空间相关的文章,但这还远远不够,需要切入的还有cgroup、文件系统和网络相关方面的细节。

到了招聘季,稍微有点时间整理这方面的资料,索性先从文件系统入手,本文的目标仅仅是“知其然”。

0x00 对于分层的需求

在LiveCD的场景下,有这么一种需求:要求在发行版启动后,在逻辑上得到一个统一的文件系统,用户能够对其进行读写,而该文件系统中的基础部分来自一个只读文件系统,无法对其进行写操作。

容器镜像的需求也是类似,用户需要能够根据基础镜像构建新镜像,出于共享基础镜像的目的,整个过程并不能影响基础镜像本身,也就是说最终得到的是一个逻辑意义上的新镜像。

通俗而言,就是要求文件系统提供“层次”的概念,目前可选的方案有AUFS,OverlayFS,DeviceMapper等。

作为UnionFS之一的OverlayFS就是这么一种文件系统,它于2014年被合并到3.18内核,顾名思义其主要特性就是“覆盖”,可以结合下图进行理解(图非原创)。

2962248062.png

0x01 特性介绍

正如上图所示,在OverlayFS中,存在Lower和Upper的概念,指定的Lower和Upper文件系统共同组成了新的文件系统。

其效果就如同上图所示,我们从Upper的正上方向下观察,存在文件和目录的位置是“不透明”的,因此最终得到的俯视图应该就如同Overlay那一层所示。

对于文件系统而言,“不透明”的另一种表达方式即为覆盖。也就是说Upper中存在的文件会覆盖Lower中的同名文件,但这对于目录而言稍有不同,同名文件会被覆盖,而不同名的则是合并。

对于文件/目录的修改,处理策略如下:若指定文件存在于Upper,则直接修改该文件。

若指定文件仅存在于Lower,则会先从Lower拷贝该文件到Upper(copy_up操作),然后进行修改。

对于文件/目录的创建,效果等同于在Upper层直接进行创建。

对于文件的删除,处理策略如下:若指定文件仅存在于Upper,则直接删除该文件。

若指定文件存在于Lower,则在Upper层创建同名的字符设备。

对于目录的删除,处理策略如下:若指定目录仅存在于Upper,则直接删除该目录。

若指定目录存在于Lower,则在Upper层创建同名的字符设备。

若在删除后再次创建同名目录,且同名目录存在于Lower,此时新的目录成为opaque目录,通过将xattr的“trusted.overlay.opaque”设置为“y”实现标记,此时Upper层的该目录就会完全覆盖Lower中的目录,而非原先的合并。

通过上述策略,使得在OverlayFS中,Lower可以是只读的,而Upper则需要是可写的文件系统。

0x02 copy_up操作

在上一节,我们已经提及了copy_up操作,简单阐述,就是指当涉及需要修改Lower中的数据时,将对应数据从Lower拷贝到Upper的操作。

触发copy_up操作有2个条件:涉及到对文件的写操作,无论是meta信息还是文件内容

文件仅存在于Lower文件系统中

创建硬链接也会导致copy_up操作,而符号链接则不会。

copy_up操作在解决一些问题的同事,也带来了一些问题,毕竟它不是万能的。

如果你在copy_up前,对该文件加了文件锁,copy_up后,文件锁的目标并不会改变。对于拥有多个硬链接的文件,亦是如此。

0x03 实战

接下来我们通过实际操作来感受一下OverlayFS的功能。具体的命令如下:sudo mount -t overlay overlay -olowerdir=LLL,upperdir=UUU,workdir=WWW MMM

这里允许存在多个lowerdir,使用“:”分隔即可,upperdir和workerdir是可选的,且它们必须位于同一个文件系统上,workdir似乎是用来存储一些操作的中间产物的。

LLL,UUU,WWW,MMM均为目录,且WWW必须为空。

假设我们有如下目录:|- lower

| |- a

| |- b

| |- dir/

| | |- c

| | |- d

| |- test/

|- upper/

| |- e

| |- f

| |- dir/

| |- g

|- work/

|- merged/

我们可以通过如下命令将lower和upper“合并”,并挂载到merged目录。sudo mount -t overlay overlay -olowerdir=lower,upperdir=upper,workdir=work merged

通过查看merged目录和merged/dir目录,可以得到如下结果:[codesun@lucode overlay]$ ls -l merged/

总用量 8

-rw-r--r-- 1 codesun users 0 8月 1 00:01 a

-rw-r--r-- 1 codesun users 0 8月 1 00:01 b

drwxr-xr-x 1 codesun users 4096 8月 1 00:02 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 e

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

drwxr-xr-x 2 codesun users 4096 8月 1 00:01 test

[codesun@lucode overlay]$ ls -l merged/dir

总用量 0

-rw-r--r-- 1 codesun users 0 8月 1 00:01 c

-rw-r--r-- 1 codesun users 0 8月 1 00:01 d

-rw-r--r-- 1 codesun users 0 8月 1 00:02 g

尝试添加一个新文件h,然后查看upper目录:[codesun@lucode overlay]$ touch merged/h

[codesun@lucode overlay]$ ls -l upper/

总用量 4

drwxr-xr-x 2 codesun users 4096 8月 1 00:02 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 e

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

-rw-r--r-- 1 codesun users 0 8月 1 00:05 h

可以看到,h文件被创建到了upper目录,这个就证明了文件创建的规则。

接下来,我们来测试一下copy_up操作,分为两步:修改文件a的meta信息

修改文件b的内容

然后,再次查看uppder目录:[codesun@lucode overlay]$ touch merged/a

[codesun@lucode overlay]$ echo test >> merged/b

[codesun@lucode overlay]$ ls -l upper/

总用量 8

-rw-r--r-- 1 codesun users 0 8月 1 00:08 a

-rw-r--r-- 1 codesun users 5 8月 1 00:08 b

drwxr-xr-x 2 codesun users 4096 8月 1 00:02 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 e

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

-rw-r--r-- 1 codesun users 0 8月 1 00:05 h

可以看到,touch操作修改了文件a的atime信息,所以被copy_up了,文件b由于内容被修改,亦被copy_up。

接下来,我们验证一下文件的删除和文件夹的删除,分为如下几步:删除upper独有的文件e

删除test目录

删除共有的文件a,测试字符设备机制[codesun@lucode overlay]$ rm merged/e

[codesun@lucode overlay]$ rmdir merged/test

[codesun@lucode overlay]$ rm merged/a

[codesun@lucode overlay]$ ls -l upper/

总用量 8

c--------- 1 root root 0, 0 8月 1 00:12 a

-rw-r--r-- 1 codesun users 5 8月 1 00:08 b

drwxr-xr-x 2 codesun users 4096 8月 1 00:02 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

-rw-r--r-- 1 codesun users 0 8月 1 00:05 h

c--------- 1 root root 0, 0 8月 1 00:12 test

[codesun@lucode overlay]$ ls -l lower/

总用量 8

-rw-r--r-- 1 codesun users 0 8月 1 00:01 a

-rw-r--r-- 1 codesun users 0 8月 1 00:01 b

drwxr-xr-x 2 codesun users 4096 8月 1 00:01 dir

drwxr-xr-x 2 codesun users 4096 8月 1 00:01 test

可以看到文件e删除后,没有任何字符设备生成,因为它是upper目录独有的,文件a和目录test被删除后,则是生成了字符设备,以表示文件已被删除,而lower目录中的文件a和目录test,并没有任何变化。

接下来,我们来测试opaque目录机制,具体的步骤是删除共有目录dir,然后再创建同名目录。[codesun@lucode overlay]$ rm -r merged/dir

[codesun@lucode overlay]$ ls -l upper/

总用量 4

c--------- 1 root root 0, 0 8月 1 00:12 a

-rw-r--r-- 1 codesun users 5 8月 1 00:08 b

c--------- 1 root root 0, 0 8月 1 00:15 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

-rw-r--r-- 1 codesun users 0 8月 1 00:05 h

c--------- 1 root root 0, 0 8月 1 00:12 test

[codesun@lucode overlay]$ ls -l lower/

总用量 8

-rw-r--r-- 1 codesun users 0 8月 1 00:01 a

-rw-r--r-- 1 codesun users 0 8月 1 00:01 b

drwxr-xr-x 2 codesun users 4096 8月 1 00:01 dir

drwxr-xr-x 2 codesun users 4096 8月 1 00:01 test

删除dir目录后,可以看到和目录test被删除后一样的现象,lower中的dir目录则并无变化,那么在重新创建dir目录呢?[codesun@lucode overlay]$ mkdir merged/dir

[codesun@lucode overlay]$ ls -l upper/

总用量 8

c--------- 1 root root 0, 0 8月 1 00:12 a

-rw-r--r-- 1 codesun users 5 8月 1 00:08 b

drwxr-xr-x 2 codesun users 4096 8月 1 00:17 dir

-rw-r--r-- 1 codesun users 0 8月 1 00:02 f

-rw-r--r-- 1 codesun users 0 8月 1 00:05 h

c--------- 1 root root 0, 0 8月 1 00:12 test

[codesun@lucode overlay]$ ls -l merged/dir

总用量 0

可以看到,目录创建后,upper中的同名字符设备不见了,而其内部并没有文件c和文件d,也就是说该目录已经是一个opaque目录了。

需要注意的是,在挂载OverlayFS后,如果修改其Lower和Upper目录中的内容,其行为对于合并后的文件系统是未定义的,所以尽量不要这么做。

0xFF 总结

总体而言,OverlayFS的使用还是很方便的,并且其概念也不难理解,在使用Docker时,我们可以通过配置使daemon使用该驱动,据说相交DeviceMapper更适合生产环境。

从目录结构来看,目前我的系统中,Docker默认使用的似乎是DeviceMapper,所以近期还会摸索一下DeviceMapper,尽请期待。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐