企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 第三节 # Docker五种存储驱动原理及应用场景和性能测试对比 Docker最开始采用AUFS作为文件系统,也得益于AUFS分层的概念,实现了多个Container可以共享同一个image。但由于AUFS未并入Linux内核,且只支持Ubuntu,考虑到兼容性问题,在Docker 0.7版本中引入了存储驱动, 目前,Docker支持AUFS、Btrfs、Device mapper、OverlayFS、ZFS五种存储驱动。就如Docker官网上说的,没有单一的驱动适合所有的应用场景,要根据不同的场景选择合适的存储驱动,才能有效的提高Docker的性能。如何选择适合的存储驱动,要先了解存储驱动原理才能更好的判断,本文介绍一下Docker五种存储驱动原理详解及应用场景及IO性能测试的对比。在讲原理前,先讲一下写时复制和写时分配两个技术。 ## 原理说明 ### 写时复制(CoW) 所有驱动都用到的技术——写时复制(CoW)。CoW就是copy-on-write,表示只在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果为每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image,所做的写操作都是对从image中复制到自己的文件系统中的复本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个复本,每个容器修改的都是自己的复本,相互隔离,相互不影响。使用CoW可以有效的提高磁盘的利用率。 ### 用时分配(allocate-on-demand) 而写时分配是用在原本没有这个文件的场景,只有在要新写入一个文件时才分配空间,这样可以提高存储资源的利用率。比如启动一个容器,并不会为这个容器预分配一些磁盘空间,而是当有新文件写入时,才按需分配新空间。 ### AUFS AUFS(AnotherUnionFS)是一种Union FS,是文件级的存储驱动。AUFS能透明覆盖一或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统。这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS创建该文件的一个副本,使用CoW将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中,底下的只读层就是image,可写层就是Container。结构如下图所示: ![aufs](https://box.kancloud.cn/8b2e5e37935733bb0a520d15a102e770_732x403.jpg) ### 分析 - 虽然AUFS是Docker 第一版支持的存储方式,但到现在还没有加入内核主线( centos 无法直接使用) - 从原理分析看,AUFS mount()方法很快,所以创建容器很快;读写访问都具有本机效率;顺序读写和随机读写的性能大于kvm;并且Docker的AUFS可以有效的使用存储和内存 。 - AUFS性能稳定,并且有大量生产部署及丰富的社区支持 - 不支持rename系统调用,执行“copy”和“unlink”时,会导致失败。 - 当写入大文件的时候(比如日志或者数据库等)动态mount多目录路径的问题,导致branch越多,查找文件的性能也就越慢。(解决办法:重要数据直接使用 -v 参数挂载。) ### OverlayFS Overlay是Linux内核3.18后支持的,也是一种Union FS,和AUFS的多层不同的是Overlay只有两层:一个upper文件系统和一个lower文件系统,分别代表Docker的镜像层和容器层。当需要修改一个文件时,使用CoW将文件从只读的lower复制到可写的upper进行修改,结果也保存在upper层。在Docker中,底下的只读层就是image,可写层就是Container。结构如下图所示: ![overlay](https://box.kancloud.cn/96bc6a12c62b5fdc9ad21eee2d269cbd_754x193.jpg) ### 分析 - 从kernel3.18进入主流Linux内核。设计简单,速度快,比AUFS和Device mapper速度快。在某些情况下,也比Btrfs速度快。是Docker存储方式选择的未来。因为OverlayFS只有两层,不是多层,所以OverlayFS “copy-up”操作快于AUFS。以此可以减少操作延时。 - OverlayFS支持页缓存共享,多个容器访问同一个文件能共享一个页缓存,以此提高内存使用率。 - OverlayFS消耗inode,随着镜像和容器增加,inode会遇到瓶颈。Overlay2能解决这个问题。在Overlay下,为了解决inode问题,可以考虑将/var/lib/docker挂在单独的文件系统上,或者增加系统inode设置。 - 有兼容性问题。open(2)只完成部分POSIX标准,OverlayFS的某些操作不符合POSIX标准。例如: 调用fd1=open("foo", O_RDONLY) ,然后调用fd2=open("foo", O_RDWR) 应用期望fd1 和fd2是同一个文件。然后由于复制操作发生在第一个open(2)操作后,所以认为是两个不同的文件。 - 不支持rename系统调用,执行“copy”和“unlink”时,将导致失败。 ### Device mapper Device mapper是Linux内核2.6.9后支持的,提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己的需要制定实现存储资源的管理策略。Docker的Device mapper利用 Thin provisioning snapshot管理镜像和容器。 前面讲的AUFS和OverlayFS都是文件级存储,而Device mapper是块级存储,所有的操作都是直接对块进行操作,而不是文件。Device mapper驱动会先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器里看到文件系统是资源池上基本设备的文件系统的快照,并不有为容器分配空间。当要写入一个新文件时,在容器的镜像内为其分配新的块并写入数据,这个叫用时分配。当要修改已有文件时,再使用CoW为容器快照分配块空间,将要修改的数据复制到在容器快照中新的块里再进行修改。Device mapper 驱动默认会创建一个100G的文件包含镜像和容器。每一个容器被限制在10G大小的卷内,可以自己配置调整。结构如下图所示: ![image](http://dockerone.com/uploads/article/20160707/be6f5867d9e5e0be9ac464449b4fc461.png) #### Thin-provisioning Snapshot Snapshot是Lvm提供的一种特性,它可以在不中断服务运行的情况下为the origin(original device)创建一个虚拟快照(Snapshot)。Thin-Provisioning是一项利用虚拟化方法减少物理存储部署的技术。Thin-provisioning Snapshot是结合Thin-Provisioning和Snapshoting两种技术,允许多个虚拟设备同时挂载到一个数据卷以达到数据共享的目的。Thin-Provisioning Snapshot的特点如下: - 可以将不同的snaptshot挂载到同一个the origin上,节省了磁盘空间。 - 当多个Snapshot挂载到了同一个the origin上,并在the origin上发生写操作时,将会触发COW操作。这样不会降低效率。 - Thin-Provisioning Snapshot支持递归操作,即一个Snapshot可以作为另一个Snapshot的the origin,且没有深度限制。 - 在Snapshot上可以创建一个逻辑卷,这个逻辑卷在实际写操作(COW,Snapshot写操作)发生之前是不占用磁盘空间的。 ![snapshort](https://box.kancloud.cn/048fdc1768882476f674fd0b3b67f286_716x369.jpg) 可以通过"docker info"或通过dmsetup ls获取想要的更多信息。查看docker的Device mapper的信息: ### 分析 - Device mapper文件系统兼容性比较好,并且存储为一个文件,减少了inode消耗。 - 每次一个容器写数据都是一个新块,块必须从池中分配,真正写的时候是稀松文件,虽然它的利用率很高,但性能不好,因为额外增加了vfs开销。 - 每个容器都有自己的块设备时,它们是真正的磁盘存储,所以当启动N个容器时,它都会从磁盘加载N次到内存中,消耗内存大。 - Docker的Device mapper默认模式是loop-lvm,性能达不到生产要求。在生产环境推荐direct-lvm模式直接写原块设备,性能好。 ==此处有坑== ### btfs Btrfs被称为下一代写时复制文件系统,并入Linux内核,也是文件级级存储,但可以像Device mapper一直接操作底层设备。Btrfs利用 subvolumes和snapshots管理镜像容器分层。Btrfs把文件系统的一部分配置为一个完整的子文件系统,称之为subvolume ,snapshot是subvolumn的实时读写拷贝,chunk是分配单位,通常是1GB。那么采用 subvolume,一个大的文件系统可以被划分为多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,类似应用程序调用 malloc()分配内存一样。为了灵活利用设备空间,Btrfs 将磁盘空间划分为多个chunk 。每个chunk可以使用不同的磁盘空间分配策略。比如某些chunk只存放metadata,某些chunk只存放数据。这种模型有很多优点,比如Btrfs支持动态添加设备。用户在系统中增加新的磁盘之后,可以使用Btrfs的命令将该设备添加到文件系统中。Btrfs把一个大的文件系统当成一个资源池,配置成多个完整的子文件系统,还可以往资源池里加新的子文件系统,而基础镜像则是子文件系统的快照,每个子镜像和容器都有自己的快照,这些快照则都是subvolume的快照。 ### 分析 - Btrfs是替换Device mapper的下一代文件系统, 很多功能还在开发阶段,还没有发布正式版本,相比EXT4或其它更成熟的文件系统,它在技术方面的优势包括丰富的特征,如:支持子卷、快照、文件系统内置压缩和内置RAID支持等。 - 不支持页缓存共享,N个容器访问相同的文件需要缓存N次。不适合高密度容器场景。 - 当前Btrfs版本使用“small writes”,导致性能问题。并且需要使用Btrfs原生命令btrfs filesys show替代df - Btrfs使用“journaling”写数据到磁盘,这将影响顺序写的性能。 - Btrfs文件系统会有碎片,导致性能问题。当前Btrfs版本,能通过mount时指定autodefrag 做检测随机写和碎片整理。 - ### zfs ZFS 文件系统是一个革命性的全新的文件系统,它从根本上改变了文件系统的管理方式,ZFS 完全抛弃了“卷管理”,不再创建虚拟的卷,而是把所有设备集中到一个存储池中来进行管理,用“存储池”的概念来管理物理存储空间。过去,文件系统都是构建在物理设备之上的。为了管理这些物理设备,并为数据提供冗余,“卷管理”的概念提供了一个单设备的映像。而ZFS创建在虚拟的,被称为“zpools”的存储池之上。每个存储池由若干虚拟设备(virtual devices,vdevs)组成。这些虚拟设备可以是原始磁盘,也可能是一个RAID1镜像设备,或是非标准RAID等级的多磁盘组。于是zpool上的文件系统可以使用这些虚拟设备的总存储容量。Docker的ZFS利用snapshots和clones,它们是ZFS的实时拷贝,snapshots是只读的,clones是读写的,clones从snapshot创建。 下面看一下在Docker里ZFS的使用。首先从zpool里分配一个ZFS文件系统给镜像的基础层,而其他镜像层则是这个ZFS文件系统快照的克隆,快照是只读的,而克隆是可写的,当容器启动时则在镜像的最顶层生成一个可写层。如下图所示: ### 分析 - ZFS同 Btrfs类似是下一代文件系统。ZFS在Linux(ZoL)port是成熟的,但不推荐在生产环境上使用Docker的 ZFS存储方式,除非你有ZFS文件系统的经验。 - 警惕ZFS内存问题,因为,ZFS最初是为了有大量内存的Sun Solaris服务器而设计 。 - ZFS的“deduplication”特性,因为占用大量内存,推荐关掉。但如果使用SAN,NAS或者其他硬盘RAID技术,可以继续使用此特性。 - ZFS caching特性适合高密度场景。 - ZFS的128K块写,intent log及延迟写可以减少碎片产生。 - 和ZFS FUSE实现对比,推荐使用Linux原生ZFS驱动。 ![zfs](https://box.kancloud.cn/a83e12a7d956da7ad42d34d0112bd76f_649x347.jpg) ### 总结 ![](https://box.kancloud.cn/f3cc1d35cea24fd7a786911e875df59a_800x363.png) 以上是五种Docker存储方式的介绍及分析,以此为理论依据,选择自己的Docker存储方式。同时可以做一些验证测试:如IO性能测试,以此确定适合自己应用场景的存储方式。同时,有两点值得提出: - 使用SSD(Solid State Devices)存储,提高性能。 - 考虑使用数据卷挂载提高性能