![自己动手写Docker](https://wfqqreader-1252317822.image.myqcloud.com/cover/589/35537589/b_35537589.jpg)
2.3 Union File System
2.3.1 什么是Union File System
Union File System,简称UnionFS,是一种为Linux、FreeBSD和NetBSD操作系统设计的,把其他文件系统联合到一个联合挂载点的文件系统服务。它使用branch把不同文件系统的文件和目录“透明地”覆盖,形成一个单一一致的文件系统。这些branch或者是read-only的,或者是read-write的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为unionfs用到了一个重要的资源管理技术,叫写时复制。
写时复制(copy-on-write,下文简称CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何修改,这时并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改时增加小部分的开销。
用一个经典的例子来解释一下。Knoppix,一个用于Linux演示、光盘教学和商业产品演示的Linux发行版,就是将一个CD-ROM或DVD和一个存在于可读写设备(比如,U盘)上的叫作knoppix.img的文件系统联合起来的系统。这样,任何对CD/DVD上文件的改动都会被应用在U盘上,而不改变原来的CD/DVD上的内容。
2.3.2 AUFS
AUFS,英文全称是Advanced Multi-Layered Unification Filesystem,曾经也叫Acronym Multi-Layered Unification Filesystem、Another Multi-Layered Unification Filesystem。AUFS完全重写了早期的UnionFS 1.x,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。AUFS的一些实现已经被纳入UnionFS 2.x版本。
2.3.3 Docker是如何使用AUFS的
AUFS是Docker选用的第一种存储驱动。AUFS具有快速启动容器、高效利用存储和内存的优点。直到现在,AUFS仍然是Docker支持的一种存储驱动类型。接下来,介绍一下Docker是如何利用AUFS存储image和container的。
image layer和AUFS
每一个Docker image都是由一系列read-only layer组成的。image layer的内容都存储在Docker hosts filesystem的/var/lib/docker/aufs/diff目录下。而/var/lib/docker/aufs/layers目录,则存储着image layer如何堆栈这些layer的metadata。
准备一台安装了Docker 1.11.2的机器。在没有拉取任何镜像、启动任何容器的情况下,执行如下命令。
ls/var/lib/docker/aufs/diff
可以发现,目录没有存储任何内容。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/45_1.jpg?sign=1739276953-IGlak9bP4LV4KlNbYUqnd3Jf1ZTejPzw-0-e7b6105c5d7460f2d61a95a56340fa68)
拉取镜像后,可以看到在docker pull中的结果显示ubuntu:15.04镜像一共有4个layer,在执行命令的结果中也有4个对应的存储文件目录。
ls/var/lib/docker/aufs/diff
这里有一点需要说明的是,自从Docker 1.10之后,diff目录下的存储镜像layer文件夹不再与镜像ID相同。最后,命令列出来的是堆栈里位于6bb19cb345da470e015ba3f1ca049a1c27d 2c57ebc205ec165d2ad8a44e148ea layer下方的layer。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/45_2.jpg?sign=1739276953-mmnzXwoxtZfmGpDetI9TtHQHBzOjRNgd-0-ba768da0f177e2fa246db74643576c7a)
接下来,以ubuntu:15.04镜像为基础镜像,创建一个名为changed-ubuntu的镜像。这个镜像只是在镜像的/tmp文件夹中添加一个写了“Hello world”的文件。可以使用下面的Dockerfile来实现。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/46_1.jpg?sign=1739276953-zBgdDdEPGhlI2eahTeU5IPOAvp4Y0SNy-0-c42117a3f869db1ee446702c1edca4ea)
在terminal中使用cd命令进入到上面Dockerfile所在的位置,执行docker build-t changed-ubuntu.命令来build镜像。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/46_2.jpg?sign=1739276953-4NDV6lRJ0uEtP1SZBJ0NGWWVZwkVxPqX-0-d8ef9f188decee820cf30aac7793bf8c)
然后,执行docker images查看现在的镜像,可以看到新生成的changed-ubuntu镜像。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/46_3.jpg?sign=1739276953-W2tvwO0iN1Eby7bZ90nacBurniMTnMry-0-634a7d32f445653c61b557b122ff3083)
使用如下命令,可以清楚地查看到changed-ubuntu镜像使用了哪些image layer。
docker history changed-ubuntu
changed-ubuntu镜像使用的image layer如下。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/46_4.jpg?sign=1739276953-GP8xOJD1PLaWPI59iuh9WUdlaSZAkZxr-0-f063fe8097222c3fbb96317b38416e21)
从输出中可以看到9d8602c9aee1 image layer位于最上层,只有12B的大小,由如下命令创建。
/bin/sh-c echo "Hello world" >/tmp/newfile
也就是说,changed-ubuntu镜像只占用了12B的磁盘空间,这也证明了AUFS是如何高效使用磁盘空间的。而下面的四层image layer,则是共享地构成ubuntu:15.04镜像的4个image layer。“missing”标记的layer,是自Docker 1.10之后,一个镜像的image layer的image history数据都存储在一个文件中导致的,这是Docker官方认为的正常行为。
接下来,继续查看layer的存储信息。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/47_1.jpg?sign=1739276953-FsDGupWIgh3UiHHoA5G9PFgc8Uoi0Esj-0-31b8d67c63feb475257780183e181827)
从输出中可以看到,/var/lib/docker/aufs/diff目录和/var/lib/docker/aufs/mnt目录均多了一个文件夹9f122dbaa103338f27bac146326af38a2bcb52f98ebb3530cac828573faa3c4e。当使用如下命令来查看它的metadata时,可以看到,它前面的layer就是ubuntu:15.04镜像所使用的4个image layer。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/47_2.jpg?sign=1739276953-8lqgKrVAOBywq1yG2kAHX3DfMYpaaW6p-0-884383c8de96102f83d8cd83e07d101a)
进一步探查/var/lib/docker/aufs/diff/9f122dbaa103338f27bac146326af38a2bcb52f98ebb3530 cac828573faa3c4e文件夹,发现其中存储了一个/tmp/newfile文件,文件中只有一行“Hello world”。至此,我们完整地分析出了image layer和AUFS是如何通过共享文件和文件夹来实现镜像存储的。
container layer和AUFS
Docker使用AUFS的CoW技术来实现image layer共享和减少磁盘空间占用。CoW意味着一旦某个文件只有很小的部分有改动,AUFS也需要复制整个文件。这种设计会对容器性能产生一定的影响,尤其是在待复制的文件很大,或者位于很多image layer下方,又或者AUFS需要深度搜索目录结构树的时候。不过也不用过度担心,对于一个容器而言,每个image layer最多只需要复制一次。后续的改动都会在第一次拷贝的container layer上进行。
启动一个container的时候,Docker会为其创建一个read-only的init layer,用来存储与这个容器内环境相关的内容;Docker还会为其创建一个read-write的layer来执行所有写操作。
container layer的mount目录也是/var/lib/docker/aufs/mnt。container的metadata和配置文件都存放在/var/lib/docker/containers/<container-id>目录中。container的read-write layer存储在/var/lib/docker/aufs/diff/目录下。即使容器停止,这个可读写层仍然存在,因而重启容器不会丢失数据,只有当一个容器被删除的时候,这个可读写层才会一起删除。
接下来,仍然用实验来证明上面的结论。首先查询到现有的容器数目为0,而且在/var/lib/docker/containers目录下也没有查到任何数据。最后,查看一下系统的aufs mount情况,发现只有一个config文件。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/48_1.jpg?sign=1739276953-dzHha6Ogt3W8KitjxhAEMgHFeKOUl521-0-481ababcffc518e9a950f990b08dbc5b)
启动一个changed-ubuntu的容器。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/48_2.jpg?sign=1739276953-AEzcUIVFSUjQbo2ZC63IhBUQpkOPwXhj-0-6936222dc6d1b586d0bf97ddbba09efa)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/49_1.jpg?sign=1739276953-5PkdharWPt4XrJ2hxiBHFknM0ZmmBODh-0-acbbd873db3c6f8cd0900d0abfc54456)
如下,查看/var/lib/docker/aufs/diff目录发现,下面多了2个文件夹,f9ccf5caa9b7324f0ef-112750caa14203b557d276ca08c78c23a42a949e2bfc8-init是Docker为容器创建的read-only的init layer,而f9ccf5caa9b7324f0ef112750caa14203b557d276ca08c78c23a42a949e2bfc8则是Docker为容器创建的read-write layer。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/49_2.jpg?sign=1739276953-5BTTTRDyWkJ91oBpOKlj5xc8ulWTfNfh-0-e0274ae4a96f48314e716018352608b1)
/var/lib/docker/aufs/mnt目录的变化和/var/lib/docker/aufs/diff一致。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/49_3.jpg?sign=1739276953-MLP7EQ8oMiP5yRapye7jtSJi8pfi2wc7-0-e5fafd958701a5a60175dcb61233da6a)
/var/lib/docker/aufs/layers/目录下多了与上文两个文件目录同名的文件,用cat命令可以清楚地看到其依赖layer的记录。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/49_4.jpg?sign=1739276953-tt5tUSGYFGIvwcc6wj044b0WIuTMDHpK-0-71656d1533b4f3afad7be344e8e056b8)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/50_1.jpg?sign=1739276953-VUuJbHhTOXq4XxeCiQGKpUpSEnDkBLKr-0-eb21ac7f53691fdca9796b5e5aa5dcb2)
在/var/lib/docker/containers/目录下多了一个与containerid相同的文件夹,存放着容器的metadata和config文件。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/50_2.jpg?sign=1739276953-dNLOHKlKk7CP0PXCC9nfjlRu1rbV2u0j-0-28070b9e3b3d88532fda5f09d3b2235d)
接下来从系统AUFS来看mount的情况,在/sys/fs/aufs/目录下多了一个si_fe6d5733e85e4904文件夹,执行如下命令。
cat/sys/fs/aufs/si_fe6d5733e85e4904/*
下面,可以清楚地看到这就是刚刚创建的容器的layer权限,只有最上面的f9ccf5caa9b7 324f0ef112750caa14203b557d276ca08c78c23a42a949e2bfc8 layer是read-write权限。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/50_3.jpg?sign=1739276953-060BVbxxz1PT6drN9BHPa3KpwOZmforr-0-9dcafa4760ab18b8a35a1258507eb40e)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/51_1.jpg?sign=1739276953-YGAj99ZTgNpciTxuLrEILHJSdgz7AYJm-0-8600e2016db1f8998636b05904ecbb69)
最后,讲一下AUFS如何为container删除一个文件。如果要删除file1,AUFS会在container的read-write层生成一个.wh.file1的文件来隐藏所有read-only层的file1文件。至此,我们已清楚地描述和验证了Docker是如何使用AUFS来管理container layer的。
2.3.4 自己动手写AUFS
下面,开始自己动手用简单的命令来创建一个AUFS文件系统,感受下如何使用AUFS和CoW实现文件管理。
首先,在实验目录下创建一个aufs的文件夹,然后在aufs目录下创建一个mnt的文件夹作挂载点。接着,在aufs目录下创建一个名为container-layer的文件夹,里面有一个名为container-layer.txt的文件,文件内容为“I am container layer”。同样地,继续在aufs目录下创建4个名为image-layern的文件夹(n取值分别为1和4),里面有一个名为image-layer{n}.txt的文件,文件内容为“I am image layer${n}”。使用如下命令检查文件内容。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/51_2.jpg?sign=1739276953-09BjXsfHwNLFVlpXfI75CKRRSBk3dfxw-0-90038d1db8bf51d72ed6cf6e778f0dbf)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/52_1.jpg?sign=1739276953-eXYpAE3jWv3CnRuKbj2iZ3Lk40YDKSLM-0-365f0b868fcef6bc0577951826c4f944)
要联合的文件目录都已经准备好了。接下来,把container-layer和4个名为image-layer${n}的文件夹用AUFS的方式挂载到刚刚创建的mnt目录下。在mount aufs的命令中,没有指定待挂载的5个文件夹的权限,默认的行为是,dirs指定的左边起第一个目录是read-write权限,后续的都是read-only权限。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/52_2.jpg?sign=1739276953-VoI2WeHm9yiHbcR2MWIVWftlnRcixqcV-0-d28a902c5a39472983e9fa9c0e98d2bf)
大家还记得上一小节曾经在系统aufs目录下,查看文件读写权限的做法吗?这里依然使用如下命令来确认新mount的文件系统中每个目录的权限。(注意,si_fe6d5733e85e5904应该是系统为这个mnt挂载点新创建的,而非在介绍Docker和AUFS里面提到的那个文件夹。)
cat/sys/fs/aufs/si_fe6d5733e85e5904/*
根据输出,可以清楚地看到,只有container-layer文件夹是read-write的,其余的都是read-only权限。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/52_3.jpg?sign=1739276953-bd7l2jIAvi8jlGH58IyiibI9NgiRiJM6-0-612839a8c773c82539bb5205311108a5)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_1.jpg?sign=1739276953-hr16lry91vH5wH1GvY4KsrKneUvzr0nE-0-38744e5e5fa30ddbd4e8a7440f13a28b)
接下来,执行一个有意思的操作,往mnt/image-layer1.txt文件末尾添加一行文字“write to mnt's image-layer1.txt”。根据上面介绍的CoW技术,大家猜想一下会产生什么样的行为。
$ echo-e "\nwrite to mnt's image-layer1.txt" >>./mnt/image-layer4.txt
用cat命令去查看mnt/image-layer4.txt文件的内容,发现内容确实从“I am image layer 4”变成了如下的样子。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_2.jpg?sign=1739276953-UuKwAqI6jWJALeDD6A1TRHUNaHRQKMO6-0-5e25a9ec8ba2266b1542795c59bc1524)
此处,mnt只是一个虚拟挂载点,因此,接下来还需要继续去寻找文件修改到底在什么位置。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_3.jpg?sign=1739276953-f89DFfZdYPo4EFl9ZYR1KFNKG2suePfm-0-6032e6fc70d0b2fc72fcdf028f647dd3)
查看image-layer4/image-layer4.txt文件的内容,发现其并未改变。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_4.jpg?sign=1739276953-dDV72hugQDaaLN6ILs7WM3hEaVDbEyFz-0-5215de9fdfde3252e20718ac85394350)
而在检查container-layer文件夹的时候,发现多了一个名为image-layer4.txt的文件,文件的内容如下。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_5.jpg?sign=1739276953-wMwxNqwabce0oJ6H2NIzCDi6f1t8Na9I-0-2e33f77fbc726e4e3f1691becdc62a6a)
也就是说,当尝试向mnt/image-layer4.txt文件进行写操作的时候,系统首先在mnt目录下查找名为image-layer4.txt的文件,将其拷贝到read-write层的container-layer目录中,接着对container-layer目录中的image-layer4.txt文件进行写操作。至此,我们成功地完成了一个小小的demo,实现了自己的AUFS文件系统。
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/53_6.jpg?sign=1739276953-sUK69bN0QzGapGvCbls8kd8XYaSQAD0Z-0-aefca6f6c683ca6a22071e1b36ba8b94)
![](https://epubservercos.yuewen.com/5CFC20/18978713008548006/epubprivate/OEBPS/Images/54_1.jpg?sign=1739276953-Xek4EcnuJvuhKhUM5QRKaMR4VJuIsPPT-0-4b0f37de0616db9b0c1df03e1386d51a)