Docker的存储是怎么工作的
我们都知道docker支持多种存储驱动,默认在ubuntu上使用AUFS,其他Linux系统上使用devicemapper。这篇文章从零开始,用一些Linux的命令来使用这些不同的存储,包括AUFS、Device Mapper、Btrfs和Overlay。
背景知识
Docker最早只是运行在Ubuntu和Debian上,使用的存储驱动是AUFS。随着Docker越来越流行,很多人都希望能把它运行在RHEL系列上。可是Linux内核和RHEL并不支持AUFS,最后红帽公司和Docker公司一起合作开发了基于Device Mapper技术的devicemapper存储驱动,这也成为Docker支持的第二款存储。由于Linux内核2.6.9就已经包含Device Mapper技术了,所以它也非常的稳定,代价是比较慢。ZFS是被Oracle收购的Sun公司为Solaris 10开发的新一代文件系统,支持快照,克隆,写时复制(CoW)等。ZFS的“Z”是最后一个字母,表示终极文件系统,不需要开发其它的文件系统了。虽然ZFS各种好,但是毕竟它的Linux版本是移植过来的,Docker官方并不推荐在生产环境上使用,除非你对ZFS相当熟悉。而且由于软件许可证不同的关系,它也无法被合并进Linux内核里。这么NB的文件系统出来后,Linux社区也有所回应。Btrfs就是和ZFS比较类似的Linux原生存储系统,在Linux内核2.6.29里就包含它了。虽然Btrfs未来是要替换devicemapper的,但是目前devicemapper更安全,更稳定,更适合生产环境。所以如果不是有很经验的话,也不那么推荐在生产环境使用。OverlayFS是类似AUFS的联合文件系统,但是轻量级一些,而且还能快一点儿。更重要的是,它已经被合并到Linux内核3.18版了。虽然OverlayFS发展得很快,但是它还非常年轻,如果要上生产系统,还是要记得小心为上。Docker还支持一个VFS驱动,它是一个中间层的抽象,底层支持ext系列,ntfs,nfs等等,对上层提供一个标准的文件操作接口,很早就被包含到Linux内核里了。但是由于它不支持写时复制,所以比较占磁盘空间,速度也慢,同样并不推荐上生产环境。
说到这里,好几个存储驱动都上Linux内核了,怎么AUFS一直被拒于门外呢?AUFS是一个日本人岡島順治郎开发的,他也曾希望能把这个存储驱动提交到内核中。但是据说Linus Torvalds有点儿嫌弃AUFS的代码写得烂……
准备工作
我们需要先安装virtualBox和vagrant。通过vagrant来驱动virtualBox搭建一个虚拟测试环境。首先在本地任意路径新建一个空文件夹比如test
,运行以下命令启动并连接虚拟机:
AUFS
AUFS是一个联合文件系统,也就是说,它是一层层垒上去的文件系统。最上层能看到的就是下层的所有系统合并后的结果。我们创建几个文件夹,layer1是最底层,result用来挂载,再搞几个文件:
现在文件夹的层级结构看起来是酱紫的:
└── aufs
├── layer1
│ ├── file1 # file1 in layer1
│ └── file2 # file2 in layer1
├── layer2
│ └── file1 # file1 in layer2
└── result
然后一层层地挂载到result文件夹去(none的意思是挂载的不是设备文件),就能看到result现在有两个文件,以及它们的内容:
|
|
file1是由layer2提供的,file2是由layer1提供的,因为layer2里没有file2。如果我们在挂载后的目录写入file1~3:
就会看到这些文件都是写入到layer2的。测试完成,清场~
想要了解更细致点的话可以参考Docker基础技术:AUFS这篇文章。
回头来看Docker官方的这幅图:
虽然是删除文件的示例,但是也能清楚看到AUFS是怎么工作的。然后再结合docker一起看:
一切就都很清楚明了:一层层地累加所有的文件,最终加载到镜像里。
Device Mapper
Device Mapper是块设备的驱动,它的写时复制是基于块而非文件的。它包含3个概念:原设备,快照和映射表,它们的关系是:原设备通过映射表映射到快照去。一个快照只能有一个原设备,而一个原设备可以映射成多个快照。快照还能作为原设备映射到其他快照中,理论上可以无限迭代。
Device Mapper还提供了一种Thin-Provisioning技术。它实际上就是允许存储的超卖,用以提升空间利用率。当它和快照结合起来的时候,就可以做到许多快照挂载在一个原设备上,除非某个快照发生写操作,不然不会真正给快照们分配空间。这样的原设备叫做Thin Volume,它和快照都会由thin-pool来分配,超卖就发生在thin-pool之上。它需要两个设备用来存放实际数据和元数据。下面我们来创建两个文件,用来充当实际数据文件和元数据文件:
文件建好了之后,用Loop device把它们模拟成块设备:
然后创建thin-pool(参数的含义可以参考这篇文章):
之后创建Thin Volume并格式化:
加载这个Thin Volume并往里写个文件。我的测试机器上需80秒左右才能把这个文件同步回thin-pool去。如果不等待,可能接下来的快照里就不会有这个文件;如果等待时间不足(小于30秒),可能快照里会有这个文件,但是内容为空。这个时间跟thin-pool的参数,尤其是先前创建的实际数据和元数据文件有关。
睡饱后,给thin这个原设备添加一份快照snap1:
加载这个快照,能看见先前写的file1文件被同步过来了。再往里写个新文件。还是要保证睡眠充足:
快照是能作为原设备映射成其他快照的,下面从snap1映射一份snap11:
加载完后就能看到file1和file2都被同步过来了:
一份原设备是可以映射成多个快照的,下面从snap1再映射一份snap12:
测试完成,清场~
在RHEL,CentOS系列上,Docker默认使用loop-lvm,类似上文的机制(配的是稀疏文件),虽然性能比本文要好,但也是堪忧。官方推荐使用direct-lvm,也就是直接使用raw分区。这篇文章介绍了如何在CentOS 7上使用direct-lvm。另外,剖析Docker文件系统对AUFS和Device Mapper有很详细的讲解。
回头来看Docker官方的这幅图:
一切就都很清楚明了:最底层是两个文件:数据和元数据文件。这两个文件上面是一个pool,再上面是一个原设备,然后就是一层层的快照叠加上去,直至镜像,充分共享了存储空间。
Btrfs
Btrfs的Btr是B-tree的意思,元数据用B树管理,比较高效。它也支持块级别的写时复制,性能也不错,对SSD有优化,但是不支持SELinux。它支持把文件系统的一部分配置为Subvolume子文件系统,父文件系统就像一个pool一样给这些子文件系统们提供底层的存储空间。这就意味着子文件系统无需关心设置各自的大小,反正背后有父文件系统撑腰。Btrfs还支持对子文件系统的快照,速度非常快,起码比Device Mapper快多了。快照在Btrfs里也是一等公民,同样也可以像Subvolume那样再快照、被加载,享受写时复制技术。
要使用Btrfs,得先安装工具包:
下面我们来创建一个文件,用Loop device把它模拟成块设备:
把这个块设备格式化成btrfs:
新建一个subvolumn并往里写个文件:
给result/origin这个subvolumn添加一份快照snap1,能看见先前写的file1文件被同步过来了。再往里写个新文件:
快照也像Device Mapper那样能生成其他的快照,下面从snap1生成一份snap11:
也是可以生成多个快照的,下面从snap1再生成一份snap12:
可以使用这个命令来查看所有快照:
可以使用这个命令来查看这个文件系统:
测试完成,清场~
我们看到它比Device Mapper更简单一些,并且速度很快,不需要sleep以待同步完成。这篇文章虽然有点儿旧了,但是对Btrfs的原理讲得挺清楚的。
回头来看Docker官方的这幅图:
一切就都很清楚明了:最底层是个subvolume,再它之上层层累加快照,镜像也不例外。
Overlay
最初它叫做OverlayFS,后来被合并进Linux内核的时候被改名为Overlay。它和AUFS一样都是联合文件系统。Overlay由两层文件系统组成:upper(上层)和lower(下层)。下层可以是只读的任意的Linux支持的文件系统,甚至可以是另一个Overlay,而上层一般是可读写的。所以模型上比AUFS要简单一些,这就是为什么我们会认为它更轻量级一些。
用uname -r
可以看到我们现在这个vagrant虚拟机的Linux内核版本是3.13,而内核3.18之后才支持Overlay,所以我们得先升级一下内核,否则在mount的时候会出错:mount: wrong fs type, bad option, bad superblock on overlay
。运行以下命令来升级ubuntu 14.04的内核:
等待重启之后,重新连接进vagrant虚拟机:
完成之后再用uname -r
看一下,现在应该已经是3.18了。下面我们开搞吧:
现在的文件层级结构看起来是酱紫的:
├── lower
│ ├── file1 # file1 in lower
│ └── file2 # file2 in lower
├── merged
├── upper
│ └── file1 # file1 in upper
└── work
然后我们加载merged,让它的下层是lower,上层是upper。除此之外还需要一个workdir,据说是用来做一些内部文件原子性操作的,必须是空文件夹:
|
|
所以我们最终得到了类似AUFS一样的结果。测试完成,清场~
在Linux内核3.19之后,overlay还能够支持多层lower(Multiple lower layers),这样就能更好地支持docker镜像的模型了。多层的mount命令是酱紫的:mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged
,有兴趣的朋友可以再次升级Linux内核试试。
回头来看Docker官方的这幅图:
很好地说明了OverlayFS驱动下容器和镜像的存储是怎么工作的,lower、upper和merged各自的关系。然后看看docker镜像:
因为目前docker支持的还不是多层存储,所以在镜像里只是用硬链接来在较低层之间共享数据。今后docker应该会利用overlay的多层技术来改善镜像各层的存储。
总结
ZFS和VFS由于官方都不推荐上生产我们就不试了,虽然OverlayFS也不推荐,但是它毕竟代表着未来的趋势,还是值得我们看一看的。下表列出了docker所支持的存储驱动特性对比:
驱动 | 联合文件系统 | 写时复制 | 内核 | SELinux | 上生产环境 | 速度 | 存储空间占用 |
---|---|---|---|---|---|---|---|
AUFS | 是 | 文件级别 | 不支持 | 支持 | 推荐 | 快 | 小 |
Device Mapper | 不是 | 块级别 | 2.6.9 | 支持 | 有限推荐 | 慢 | 较大 |
Btrfs | 不是 | 块级别 | 2.6.29 | 不支持 | 有限推荐 | 较快 | 较小 |
OverlayFS | 是 | 文件级别 | 3.18 | 不支持 | 不推荐 | 快 | 小 |
ZFS | 不是 | 块级别 | 不支持 | 不支持 | 不推荐 | 较快 | 较小 |
VFS | 不是 | 不支持 | 2.4 | 支持 | 不推荐 | 很慢 | 大 |