剖析Docker文件系统:Aufs与Devicemapper,dockeraufs
Docker在启动容器的时候,需要创建文件系统,为rootfs提供挂载点。最初Docker仅能在支持Aufs文件系统的Linux发行版上运行,但是由于Aufs未能加入Linux内核,为了寻求兼容性、扩展性,Docker在内部通过graphdriver机制这种可扩展的方式来实现对不同文件系统的支持。目前,Docker支持Aufs,Devicemapper,Btrfs和Vfs四种文件系统。若想了解更多内容可以点击这里。
本文以Docker 1.4为基础,首先分析Docker镜像的结构,接着介绍Aufs和Devicemapper的应用,最后从代码的角度分析Docker文件系统的实现。
Docker镜像
典型的Linux文件系统由bootfs和rootfs两部分组成,bootfs(boot file system)主要包含 bootloader和kernel,bootloader主要是引导加载kernel,当kernel被加载到内存中后 bootfs就被umount了。 rootfs (root file system) 包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc等标准目录和文件。

Docker容器是建立在Aufs基础上的,Aufs是一种Union FS, 简单来说就是支持将不同的目录挂载到同一个虚拟文件系统下,并实现一种layer的概念。Aufs将挂载到同一虚拟文件系统下的多个目录分别设置成read-only,read-write以及whiteout-able权限,对read-only目录只能读,而写操作只能实施在read-write目录中。重点在于,写操作是在read-only上的一种增量操作,不影响read-only目录。当挂载目录的时候要严格按照各目录之间的这种增量关系,将被增量操作的目录优先于在它基础上增量操作的目录挂载,待所有目录挂载结束了,继续挂载一个read-write目录,如此便形成了一种层次结构。
Docker镜像的典型结构如下图。传统的Linux加载bootfs时会先将rootfs设为read-only,然后在系统自检之后将rootfs从read-only改为read-write,然后我们就可以在rootfs上进行写和读的操作了。但Docker的镜像却不是这样,它在bootfs自检完毕之后并不会把rootfs的read-only改为read-write。而是利用union mount(UnionFS的一种挂载机制)将一个或多个read-only的rootfs加载到之前的read-only的rootfs层之上。在加载了这么多层的rootfs之后,仍然让它看起来只像是一个文件系统,在Docker的体系里把union mount的这些read-only的rootfs叫做Docker的镜像。但是,此时的每一层rootfs都是read-only的,我们此时还不能对其进行操作。当我们创建一个容器,也就是将Docker镜像进行实例化,系统会在一层或是多层read-only的rootfs之上分配一层空的read-write的rootfs。
相关厂商内容
IBM Bluemix产品介绍
快速、灵活的开发体验,无需在意基础设施管理
企业应用云化中的选型策略与技术储备
传统电商技术架构背后的多业务模式演进
证券行业大数据应用的典型场景
相关赞助商
全球架构师峰会,7月17日-18日,深圳大梅沙京基海湾大酒店。马上报名。

为了形象化Docker的镜像结构,我们docker pull一个ubuntu:14.04的镜像,使用docker images -tree查看结果如下:
[root@qingze qingze]# docker images -tree
Warning: '-tree' is deprecated, it will be removed soon. See usage.
└─511136ea3c5a Virtual Size: 0 B
└─3b363fd9d7da Virtual Size: 192.5 MB
└─607c5d1cca71 Virtual Size: 192.7 MB
└─f62feddc05dc Virtual Size: 192.7 MB
└─8eaa4ff06b53 Virtual Size: 192.7 MB Tags: ubuntu:14.04,
可以看到Ubuntu的镜像中有多个长ID的layer,且以一种树状结构继承下来,如下图。其中,第n+1层继承了第n层,并在此基础上有了自己的内容,直观上的表现就是第n+1层占用磁盘空间增大。并且,不同的镜像可能会有相同的父镜像。例如,图中Tomcat和Nginx 继承于同一个Vim 镜像,这种组织方式起到共享的作用,节约了镜像在物理机上占用的空间。

接下来,使用docker save命令将镜像具体化并查看其结构:
[root@qingze qingze]# docker save -o ubuntu.tar ubuntu:14.04 [root@qingze qingze]# tar -tf ubuntu.tar 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a/ 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a/VERSION 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a/json 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a/layer.tar 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/ 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/VERSION 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa/ 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa/VERSION 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa/json 607c5d1cca71dd3b6c04327c3903363079b72ab3e5e4289d74fb00a9ac7ec2aa/layer.tar 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a/ 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a/VERSION 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a/json 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a/layer.tar f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73/ f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73/VERSION f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73/json f62feddc05dc67da9b725361f97d7ae72a32e355ce1585f9a60d090289120f73/layer.tar repositories
我们可以发现在具体化的镜像中对应树型结构中的每一个layer都有一个文件夹存在,且每个文件夹中包含VERSION,json,layer.tar三个文件,另外还有一个repositories文件。长ID为 511136ea3c5a的layer没有继承任何layer,因此被称为base镜像,一般来说镜像都是以这个layer开始的, 其内容也为空。
[root@qingze 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a]# ls
json layer.tar VERSION
[root@qingze 3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a]# cat json | python -m json.tool
{
"Size": 192480945,
"architecture": "amd64",
"checksum": "tarsum.dev+sha256:3679c533275a3074e9ab22c1a98a5c63515df754dacecb36cf03115a6ce7cc44",
"config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": null,
"CpuShares": 0,
"Cpuset": "",
"Domainname": "",
...
},
"container": "8c41fcbc2d072f0158d254ca92f33e10d7cb57af3eec3cd3c7fb308a6f24a3e0",
"container_config": {
...
},
"created": "2015-01-01T01:25:18.368698296Z",
"docker_version": "1.4.1",
"id": "3b363fd9d7dab4db9591058a3f43e806f6fa6f7e2744b63b2df4b84eadb0685a",
"os": "linux",
"parent": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"
}
由于篇幅关系,这里只截取了json文件的一部分内容,可以看出json文件包含了镜像以及其对应的容器的配置信息,其中有docker run时指定的,如CpuShares,Cpuset,也有系统生成的,如parent,正是这个属性记录了继承结构,从而实现了layer的层次关系。
另外,每个文件夹下都有一个layer.tar的文件,这个压缩文件存放的正是rootfs的内容,不过是增量存放的而已。为了证明这一点我们找到Ubuntu:14.04对应的layer是8eaa4ff06b53,进入该目录,查看layer的内容,未发现任何东西。
[root@qingze 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a]# tar -tf layer.tar [root@qingze 8eaa4ff06b53ff7730c4d7a7e21b4426a4b46dee064ca2d5d90d757dc7ea040a]#
然后,我们使用该镜像启动一个容器,创建一个100M的文件并docker commit该容器,具体化为ubuntu:test后重新查看。结果显示,ubuntu:test相比ubuntu:14.04多了一个7e64c2624ce0文件夹,且该文件夹下的layer.tar中有了test文件。读者亦

