Docker原生提供了多种存储驱动,用户需要做的是结合自己的场景选用其中之一。没有一种存储驱动是适合所有场景的。本文先简单介绍如下几种存储驱动:AUFS、DeviceMapper、Btrfs、overlayFs、ZFS,之后说明PPTV在构建DCOS的选型过程。
用户决定使用哪款驱动,要在Docker守护进程启动时指定,当然指定的驱动必须被底层操作系统支持。如更换存储驱动,需重启守护进程。指定存储驱动的方法可参考Docker官网,操作简单方便。各存储驱动都封装了统一接口供守护进程调用,一旦完成初始化,存储驱动的差异对守护进程来说是透明的。
想要有效的使用存储驱动,需要了解Docker如何存储镜像以及容器如何使用镜像。Docker镜像含有启动Docker容器所需的文件系统结构及其内容。在传统的Linux操作系统内核启动时,首先挂载一个只读的rootfs,当系统检测其完整性后,再将其切换为读写模式。而在Docker架构中,当Docker daemon为Docker容器挂载rootfs时,沿用了Linux内核启动时的方法,即将rootfs设为只读模式,在挂载完毕之后,在已有的只读rootfs上再挂载一个读写层。这样,可读写层处于Docker容器文件系统的最顶层,其下可能挂载多个只读层,只有在Docker容器运行过程中文件系统发生变化时,才会把文件拷贝到读写层再修改,这样的技术被称为写时复制(Copy On Write)。存储驱动提供了接口支持镜像分层与写时复制机制。
Docker团队最开始在dotCloud内部便使用AUFS来运行容器,因此AUFS有最长的历史,也就拥有较为丰富的运维经验。AUFS层层堆叠多个文件系统层,然后每一层都对外公开一个单独的挂载点供访问。它每一次的查找总是先从最顶层开始,当某个文件需要作读写操作时该文件将会被复制到最顶层,因此在性能方面的瓶颈主要在于需要写入大文件的场景。不过AUFS存储驱动最主要的问题还是AUFS文件系统本身没有被纳入主流的Linux内核版本。
图 1 AUFS镜像层容器层
DeviceMapper使用预分配技术来实现镜像分层,DeviceMapper的写时复制技术是基于块级别的,不同于AUFS基于文件级别。当Docker守护进程启动时,会创建预分配机制所必须的两个块设备:用作存储池的数据设备和维护元数据的设备,守护进程在预分配的存储池上创建一个基础设备,所有新的镜像层都是基础设备的一个快照,基础设备的大小默认为10GB,可以在守护进程启动时通过dm.basesize参数来修改这一配置。DeviceMapper有两种模式,默认为Loop模式,生产环境建议使用direct-lvm模式,原因为Loop模式是基于两个稀疏文件创建虚拟的块设备的,影响性能。
图 2 DeviceMapper镜像层容器层
Btrfs以块为单位存储数据,一个块就是一段原始存储。Docker利用Btrfs的子卷特性,每一个新创建的容器都会被分配一个新的Btrfs子卷,如果存在父镜像层,那么它会以父镜像层子卷的一个快照的形式创建。
图 3 Btrfs镜像层容器层
OverlayFs是一个联合文件系统,与AUFS的做法有些类似,由于合并目录,OverlayFs在查找效率方面作了不小的改进,不同于AUFS镜像层越多则查找时间越长。基于OverlayFs技术有两种存储驱动:Overlay与Overlay 2,根据官方文档,Overlay要求内核版本3.18以上,Overlay 2要求内核版本4.0以上。但是我们经过尝试,可以在3.10内核使用Overlay。
图 4 Overlay镜像层容器层
ZFS是Sun公司(现在的Oracle)发明的并在CDDL条款下开源。而由于CDDL与GPL开源条款的不兼容,ZFS无法并入Linux内核主线。不过ZFS On Linux(ZoL)项目提供了一个可以单独安装的内核模块和用户空间工具。除非你在ZoL项目上已经有大量的实践经验,现阶段并不推荐在生产环境中使用ZFS作为Docker的存储驱动。
如下图所示,镜像的最底层(base layer)是一个ZFS文件系统。每一个镜像子层都是一个基于下层ZFS快照的ZFS克隆实体。一个容器的文件系统是一个ZFS克隆,下层是从镜像层创建的快照。所有数据实体都从ZPOOL分配空间。
图 5 ZFS镜像层容器层
在过去的一段时间,我们使用Loop模式的DeviceMapper作为存储驱动,是Docker默认的,在使用过程中遇到一些问题。
[root@oak-qa-04 Deploy]# docker stop
b977235038bce3176279bdd27cf84b12755eff0ed08d6a11b09e5e8e97480f78
b977235038bce3176279bdd27cf84b12755eff0ed08d6a11b09e5e8e97480f78
[root@oak-qa-04 Deploy]# docker rm
b977235038bce3176279bdd27cf84b12755eff0ed08d6a11b09e5e8e97480f78
Error response from daemon: Driver devicemapperfailed to remove root filesystem
b977235038bce3176279bdd27cf84b12755eff0ed08d6a11b09e5e8e97480f78
: Device is Busy
如上面所示问题,不只一次出现,目前都没有完全解决,最终只能用重启Docker的方法,如果在生产环境中遇到这样的问题,应该是大家不希望看到的。
我们分析产生该问题的原因还是在于Loop模式的的DeviceMapper所用的数据设备和元数据设备都只是绑定到回环设备上的稀疏文件,如不做特别的配置,这些文件大小一般看上去是100GB(如下图所示loop0)和2GB(如下图所示loop1),由于使用预分配机制,在实际写入数据之前,并不会真正占用100GB和2GB的磁盘空间。这样的机制让我们想到两个问题:1、稀疏文件性能不太好,且文件本身可能会损坏;2、由于预分配机制,待需要占用的时候,如果实际磁盘空间已经没有那么多了,就会发生异常。
[root@oak-qa-05]#lsblk
NAME MAJ:MINRM SIZE RO TYPE MOUNTPOINT
fd0 8:0 0 4k 0 disk
sda1
sda2
loop0 7:0 0 100G 0 loop
loop1 7:1 0 2G 0 loop
用户可根据自己的实际需要,考虑以下几个方面:容器密度、文件大小、是否IO密集等,以及自己在使用方面的喜好,来选择一种。
由于AUFS没有并入Linux内核主线,Overlay没有完全支持POSIX标准,ZFS也存在兼容性问题。鉴于DeviceMapper(Loop模式)之前遇到种种问题,所以我们计划在Btrfs与DeviceMapper(direct-lvm模式)两者中选一种,下面对他们进行一些IO性能的测试,之后在现有环境中部分部署Btrfs,部分部署DeviceMapper,对稳定性进行比较。
大家对于打算使用的存储驱动,可以关注官方网站的bug列表,也是一个可参考的因素。
使用IOzone测试工具,该工具参数多样,可灵活使用,测试各种读写,我们结合自己的场景,主要测试读的速度,主要关注文件大小在几百KB到几MB之间的数据,仅比较Btrfs与DeviceMapper。
宿主机配置如下:
CPU(s) | 40 |
MemTotal | 65746032 KB |
Model name | Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz |
测试结果中横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s。
测试 1
Read(读测试),文件大小为4K到128K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 6 测试 1
测试 2
Read(读测试),文件大小为128K到8192K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 7 测试 2
测试 3
Read(读测试),文件大小为256k到16384k,以record 256k来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 8 测试 3
测试 4
Reread(对刚读过的文件重读),文件大小为4K到128K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 9 测试 4
测试 5
Reread(对刚读过的文件重读),文件大小为128K到8192K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 10 测试 5
测试 6
Reread(对刚读过的文件重读),文件大小为256K到16384K,以record 256K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 11 测试 6
测试 7
Random read(随机读),文件大小为4K到128K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 12 测试 7
测试 8
Random read(随机读),文件大小为128K到8192K,以record 4K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 13 测试 8
测试 9
Random read(随机读),文件大小为256K到16384K,以record 256K来传输,测试结果如下图所示(红色为Btrfs,蓝色为DeviceMapper):
图 14 测试 9
对于我们目前打算用Docker上线的PP云等服务,主要关注从几百KB到几MB大小的文件,只需考虑读,写的操作是采用挂载卷的方式,不会直接写在容器里。在读的方面,DeviceMapper比Btrfs性能略好。在稳定性方面的比较,由于线下的试验并不能完全模仿线上的场景,初步打算上线时一部分容器运行在DeviceMapper存储驱动的环境下,一部分容器运行在Btrfs存储驱动的环境下,进行观察、比较。
作者介绍:张正宜,现PPTV DCOS组开发工程师,目前主要研究方向是Docker存储方案,容器内MySQL集群、Redis集群、MongoDB集群、Cassandra、RabbitMQ、ZooKeeper、ELK等组件的性能与稳定性分析。爱交流,爱分享,喜欢新技术,关注用户体验。