ZFS是下一代文件系统,它支持许多高级存储特性,诸如卷(volume)管理,快照(snapshots),校验,压缩,去冗余(deduplication),主从复制(replication)等。
ZFS是Sun公司(现在的Oracle)发明的并在CDDL条款下开源。而由于CDDL与GPL开源条款的不兼容,ZFS无法并入Linux内核主线。不过ZFS On Linux(ZoL)项目提供了一个可以单独安装的内核模块和用户空间工具。
虽然ZoL已经是健康和成熟的技术,除非你在ZoL项目上已经有大量的实践经验,现阶段并不推荐在生产环境中使用zfs
作为Docker的存储驱动。
注意: Linux平台上还有一个FUSE实现版的ZFS。它应该能用在Docker上,但是不推荐使用。我们更推荐使用原生的ZFS驱动(ZoL),它经历了更多的测试,性能也更好。文章的剩余部分也是关于原生的ZoL接口。
ZFS下的镜像分层和共享
Docker的zfs
存储驱动大量使用了下面三种ZFS数据集:
* 文件系统(filesystems)
* 快照(snapshots)
* 克隆(clones)
ZFS文件系统是自动精简配置的,它只在正真需要时才会从ZFS的资源池(zpool)中分配空间。快照和克隆是一种ZFS文件系统在某个时间点的拷贝,占用很少的空间。不同的是快照是只读的,而克隆是读写的,克隆只能从一个快照创建。它们的关系可以通过下图表示:
图中的实线表示创建一个克隆的处理流程。步骤1从文件系统创建了快照,步骤2从快照创建了克隆。虚线则表示了克隆和文件系统的关系,是通过中间快照实现的。所有三种数据实体都是从同一个底层zpool分配空间。
在Docker宿主机上使用zfs
存储驱动,镜像的最底层(base layer)是一个ZFS文件系统。每一个镜像子层都是一个基于下层ZFS快照的ZFS克隆实体。一个容器的文件系统是一个ZFS克隆,下层是从镜像层创建的快照。所有ZFS数据实体都是从一个共用的zpool分配空间。下图表示了如何将一个容器和两层的镜像关联起来:
下面的步骤阐释了镜像如何分层以及容器的文件系统如何创建。流程可以参考上图。
1. 镜像的base层是Docker宿主机上的一个ZFS文件系统。
该文件系统消耗zpool中的存储空间。
2. 其他上层镜像层是其下层装有镜像层的克隆。
图中,“Layer 1”是同通过创建base层的一个ZFS快照再创建该快照的克隆。克隆是可写的,并根据需要在zpool中分配物理空间。而快照是只读的,让base层成为一个不变的实体。
3. 启动容器时,再将一个读写层加入到镜像的最上层。
ZFS下容器的读写
容器通过zfs
存储驱动读操作非常简单。一个新启动的容器基于一个ZFS克隆,该克隆初始共享所有来自被克隆数据实体的数据。这意味着用zfs
存储驱动进行读操作非常快,即使读取的实际数据还没有被拷贝到容器。数据块的共享如下图所示:
往容器中写入新数据是通过按需求分配操作完成的。每当容器有新数据需要写入,就会从zpool里新分配一个块。这表示只有新数据要写入才会使容器占用额外的空间。新空间将从底层zpool里面分配给容器。
更新容器中已经有的数据通过分配新的块给克隆层并将变更后的数据存储在那些新分配的块中。原始的数据没有改变,允许底层镜像数据实体保持不变。这和写入一个普通的ZFS文件系统并没有什么不同,却实现了一个写时拷贝(COW)语义。
在Docker中配置ZFS存储驱动
zfs
存储驱动只有在Docker宿主机的/var/lib/docker
挂载为ZFS文件系统才可以使用。这部分主要说明如何在Ubuntu14.04操作系统下面安装和配置ZoL。
环境要求
如果你已经在你的Docker宿主机上运行了Docker daemon并且有你想要使用的镜像,在尝试下面过程前先将它们push
到Dcoker Hub或者你的个人Docker信任的镜像库中。
停止运行Docker daemon,然后确保你又一块闲置的块设备,如/dev/xvdb
。在你的环境中,设备标识符可能会不一样,在下面的步骤中你应该替换为你自己的设备标识符。
在Ubuntu 14.04 LTS上安装ZFS
- 如果Docker daemon还在运行,将其停止。
- 安装
software-properties-common
包。
- 它是
add-apt-repository
命令依赖的包 `$ sudo apt-get install software-properties-common
Reading package lists... Done
Building dependency tree
<output truncated>
- 它是
- 添加
zfs-native
源
$ sudo add-apt-repository ppa:zfs-native/stable
The native ZFS filesystem for Linux. Install the ubuntu-zfs package.
<output truncated>
gpg: key F6B0FC61: public key "Launchpad PPA for Native ZFS for Linux" imported
gpg: Total number processed: 1
gpg: imported: 1 (RSA: 1)
OK
- 更新所有源
$ sudo apt-get update
Ign http://us-west-2.ec2.archive.ubuntu.com trusty InRelease
Get:1 http://us-west-2.ec2.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
<output truncated>
Fetched 10.3 MB in 4s (2,370 kB/s)
Reading package lists... Done
- 安装
ubuntu-zfs
包
$ sudo apt-get install -y ubuntu-zfs
Reading package lists... Done
Building dependency tree
<output truncated>
- 加载
zfs
模块
$ sudo modprobe zfs
- 验证模块已正确加载
$ lsmod | grep zfs
zfs 2768247 0
zunicode 331170 1 zfs
zcommon 55411 1 zfs
znvpair 89086 2 zfs,zcommon
spl 96378 3 zfs,zcommon,znvpair
zavl 15236 1 zfs
给Docker配置ZFS
等待ZFS安装并加载完成,你就可以准备给Docker配置ZFS了。
1. 新建一个zpool
* $ sudo zpool create -f zpool-docker /dev/xvdb
* 该命令创建了一个zpool
,并命名为“zpool-docker”,名字也可以是其他。
2. 检查zpool
是否存在
$ sudo zfs list
NAME USED AVAIL REFER MOUNTPOINT
zpool-docker 55K 3.84G 19K /zpool-docker
- 创建并挂在一个新的ZFS文件系统到
/var/lib/docker
。
$ sudo zfs create -o mountpoint=/var/lib/docker zpool-docker/docker
- 检查上一步是否成功
$ sudo zfs list -t all
NAME USED AVAIL REFER MOUNTPOINT
zpool-docker 93.5K 3.84G 19K /zpool-docker
zpool-docker/docker 19K 3.84G 19K /var/lib/docker- 现在,你已经有了一个挂载在
/var/lib/docker
下面的ZFS文件系统,daemon应该能自动加载zfs
存储模块了。
- 启动Docker daemon
$ sudo service docker start
docker start/running, process 2315
- 不同的Linux发行版启动Docker daemon的流程可能会不同。你也可以通过给
docker daemon
命令或者Docker配置文件的DOCKER_OPTS
选项附加--storage-driver=zfs
flag强制Docker daemon使用zfs
存储驱动。
- 验证daemon正在使用
zfs
存储驱动
$ sudo docker info
Containers: 0
Images: 0
Storage Driver: zfs
Zpool: zpool-docker
Zpool Health: ONLINE
Parent Dataset: zpool-docker/docker
Space Used By Parent: 27648
Space Available: 4128139776
Parent Quota: no
Compression: off
Execution Driver: native-0.2
[...]- 上面命令的输出表示Docker daemon正在使用
zfs
存储驱动,并且其父数据集是前面创建的文件系统zpool-docker/docker
你的Docker宿主机正在使用ZFS存储并管理镜像和容器。
ZFS和Docker性能
如果使用zfs
存储驱动,下面几个因素会影响Docker的性能:
- 内存 内存是影响ZFS性能的主要因素。这归因于ZFS最初是设计给拥有大量内存的大型Sun Solaris服务器的。当你定制Docker宿主机的时候别忘了这个。
- ZFS特性 使用ZFS特性,如去冗余(deduplication),会够显著增加ZFS的内存占用量。考虑到内存消耗和性能,我们推荐关闭ZFS去冗余功能。然而,技术栈其他层(如SAN或者NAS阵列)的去冗余仍然可以使用,因为他们不会影响ZFS的内存占用和性能。如果与ZFS一块使用SAN、NAS或者其他硬RAID技术,你应该继续遵从那些现有的最佳实践。
- ZFS缓存 ZFS将磁盘块缓存在内存中使用的是适应性替换缓存技术(ARC)。ZFS的单拷贝ARC特性允许一个块的单个缓存拷贝被一个文件系统的多个克隆共享。这意味着多个运行的容器可以共享一个缓存块。也表示ZFS是PAAS和其他高密度应用场景下一个比较好的选择。
- 磁盘碎片 磁盘碎片是ZFS这类写时拷贝文件系统一个自然的副作用。不过,ZFS通过每次写入128K大小的块并分配slabs(多个128K的块)给COW操作的方式来减少磁盘碎片。ZFS 的intent log(ZIL)和合并写入(延迟写入)技术也能减少磁盘碎片。
- 在Linux平台上使用原生ZFS驱动 虽然Docker的
zfs
驱动也支持ZFS的FUSE实现,我们并不推荐在有性能要求的场景下使用。Linux平台上原生的ZFS驱动比FUSE实现版本性能表现更好。
下面两个性能方面通用的最佳实践也适用于ZFS:
- 使用SSD 为了更好的性能,可以使用如SSD这类高速存储设备。不过,如果你只有少量SSD可以使用,我们推荐你将ZIL存储在SSD上。
- 使用数据卷(Data Volumes) Data Volumes提供了最好和最可预测的性能。这是因为它绕开了存储驱动(Docker的),也不会招致由于引入自动精简配置和COW技术而带来的性能开销。出于这个原因,你应该会想把一些频繁写入的负载放在data volumes上。