本文的目的
Docker原生提供了多种存储驱动,用户需要做的是结合自己的场景选用其中之一。没有一种存储驱动是适合所有场景的。本文先简单介绍如下几种存储驱动:AUFS、DeviceMapper、Btrfs、overlayFs、ZFS,之后说明PPTV在构建DCOS的选型过程。
如何指定存储驱动
用户决定使用哪款驱动,要在Docker守护进程启动时指定,当然指定的驱动必须被底层操作系统支持。如更换存储驱动,需重启守护进程。指定存储驱动的方法可参考Docker官网,操作简单方便。各存储驱动都封装了统一接口供守护进程调用,一旦完成初始化,存储驱动的差异对守护进程来说是透明的。
Copy On Write技术
想要有效的使用存储驱动,需要了解Docker如何存储镜像以及容器如何使用镜像。Docker镜像含有启动Docker容器所需的文件系统结构及其内容。在传统的Linux操作系统内核启动时,首先挂载一个只读的rootfs,当系统检测其完整性后,再将其切换为读写模式。而在Docker架构中,当Docker
daemon为Docker容器挂载rootfs时,沿用了Linux内核启动时的方法,即将rootfs设为只读模式,在挂载完毕之后,在已有的只读rootfs上再挂载一个读写层。这样。可读写层处于Docker容器文件系统的最顶层,其下可能挂载多个只读层,只有在Docker容器运行过程中文件系统发生变化时,才会把文件拷贝到读写层再修改,这样的技术被称为写时复制(Copy
On Write)。存储驱动提供了接口支持镜像分层与写时复制机制。
AUFS
Docker团队最开始在dotCloud内部便使用AUFS来运行容器,因此AUFS有最长的历史,也就拥有较为丰富的运维经验。AUFS层层堆叠多个文件系统层,然后每一层都对外公开一个单独的挂载点供访问。它每一次的查找总是先从最顶层开始,当某个文件需要作读写操作时该文件将会被复制到最顶层,因此在性能方面的瓶颈主要在于需要写入大文件的场景。不过AUFS存储驱动最主要的问题还是AUFS文件系统本身没有被纳入主流的Linux内核版本。
图1 AUFS镜像层容器层
DeviceMapper
DeviceMapper使用预分配技术来实现镜像分层,DeviceMapper的写时复制技术是基于块级别的,不同于AUFS基于文件级别。当Docker守护进程启动时,会创建预分配机制所必须的两个块设备:用作存储池的数据设备和维护元数据的设备,守护进程在预分配的存储池上创建一个基础设备,所有新的镜像层都是基础设备的一个快照,基础设备的大小默认为10GB,可以在守护进程启动时通过dm.basesize参数来修改这一配置。DeviceMapper有两种模式,默认为loop模式,生产环境建议使用direct-lvm模式,原因为loop模式是基于两个稀疏文件创建虚拟的块设备的,影响性能。
图2 DeviceMapper镜像层容器层
Btrfs
Btrfs以块为单位存储数据,一个块就是一段原始存储。Docker利用Btrfs的子卷特性,每一个新创建的容器都会被分配一个新的Btrfs子卷,如果存在父镜像层,那么它会以父镜像层子卷的一个快照的形式创建。
图3 Btrfs镜像层容器层
OverlayFs
OverlayFs是一个联合文件系统,与AUFS的做法有些类似,由于合并目录,overlayFs在查找效率方面作了不小的改进,不同于AUFS镜像层越多则查找时间越长。基于overlayFs技术有两种存储驱动:overlay与overlay2,根据官方文档,overlay要求内核版本3.18以上,overlay2要求内核版本4.0以上。但是我们经过尝试,可以在3.10内核使用overlay。
图4 overlay镜像层容器层
ZFS
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列表,也是一个可参考的因素。
IO性能测试
使用IOzone测试工具,该工具参数多样,可灵活使用,测试各种读写,我们结合自己的场景,主要测试读的速度,主要关注文件大小在几百kKB到几MB之间的数据,仅比较Btrfs与DeviceMapper。
测试环境
宿主机配置如下:
测试1
Read(读测试),文件大小为4k到128k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图6 测试1
测试2
Read(读测试),文件大小为128k到8192k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图7 测试2
测试3
Read(读测试),文件大小为256k到16384k,以record 256k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图8 测试3
测试4
Reread(对刚读过的文件重读),文件大小为4k到128k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图9 测试4
测试5
Reread(对刚读过的文件重读),文件大小为128k到8192k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图10 测试5
测试6
Reread(对刚读过的文件重读),文件大小为256k到16384k,以record 256k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图11 测试6
测试7
Random read(随机读),文件大小为4k到128k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图12 测试7
测试8
Random read(随机读),文件大小为128k到8192k,以record 4k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图13 测试8
测试9
Random read(随机读),文件大小为256k到16384k,以record 256k来传输,测试结果如下图所示:
红色为Btrfs,蓝色为DeviceMapper;
横坐标为文件大小,单位为kBytes,纵坐标为传输速度,单位为kBytes/s;
图14 测试9
小结
对于我们目前打算用docker上线的pp云等服务,主要关注从几百KB到几MB大小的文件,只需考虑读,写的操作是采用挂载卷的方式,不会直接写在容器里。在读的方面,devicemapper比Btrfs性能略好。在稳定性方面的比较,由于线下的试验并不能完全模仿线上的场景,初步打算上线时一部分容器运行在devicemapper存储驱动的环境下,一部分容器运行在Btrfs存储驱动的环境下,进行观察、比较。 |