ZFS存储驱动程序

ZFS 是下一代文件系统,支持许多高级存储技术,例如卷管理、快照、校验和、压缩和重复数据删除、复制等等。

它由 Sun Microsystems(现为 Oracle Corporation)创建,并在 CDDL 许可下开源。由于 CDDL 和 GPL 之间的许可证不兼容,ZFS 无法作为主线 Linux 内核的一部分进行发布。但是,ZFS On Linux (ZoL) 项目提供了一个独立于树外的内核模块和用户空间工具,可以单独安装。

Linux 上的 ZFS (ZoL) 移植版运行良好且日趋成熟。但是,目前除非您拥有丰富的 Linux 上 ZFS 使用经验,否则不建议将zfs Docker 存储驱动程序用于生产环境。

注意

Linux 平台上也存在 ZFS 的 FUSE 实现。这不推荐。原生 ZFS 驱动程序 (ZoL) 经过更多测试,性能更好,并且使用更广泛。本文档的其余部分指的是原生的 ZoL 移植版。

先决条件

  • ZFS 需要一个或多个专用块设备,最好是固态驱动器 (SSD)。
  • /var/lib/docker/ 目录必须安装在 ZFS 格式化的文件系统上。
  • 更改存储驱动程序会使您已创建的任何容器在本地系统上无法访问。使用docker save保存容器,并将现有镜像推送到 Docker Hub 或私有仓库,以便您以后无需重新创建它们。

注意

无需使用MountFlags=slave,因为dockerdcontainerd位于不同的挂载命名空间中。

使用zfs存储驱动程序配置 Docker

  1. 停止 Docker。

  2. /var/lib/docker/的内容复制到/var/lib/docker.bk,并删除/var/lib/docker/的内容。

    $ sudo cp -au /var/lib/docker /var/lib/docker.bk
    
    $ sudo rm -rf /var/lib/docker/*
    
  3. 在您的专用块设备上创建一个新的zpool,并将其安装到/var/lib/docker/。确保您已指定正确的设备,因为这是一个破坏性操作。此示例将两个设备添加到池中。

    $ sudo zpool create -f zpool-docker -m /var/lib/docker /dev/xvdf /dev/xvdg
    

    该命令创建zpool并将其命名为zpool-docker。此名称仅用于显示,您可以使用其他名称。使用zfs list检查池是否已正确创建和安装。

    $ sudo zfs list
    
    NAME           USED  AVAIL  REFER  MOUNTPOINT
    zpool-docker    55K  96.4G    19K  /var/lib/docker
    
  4. 配置 Docker 以使用zfs。编辑/etc/docker/daemon.json并将storage-driver设置为zfs。如果该文件之前为空,则现在应如下所示

    {
      "storage-driver": "zfs"
    }

    保存并关闭文件。

  5. 启动 Docker。使用docker info验证存储驱动程序是否为zfs

    $ sudo docker info
      Containers: 0
       Running: 0
       Paused: 0
       Stopped: 0
      Images: 0
      Server Version: 17.03.1-ce
      Storage Driver: zfs
       Zpool: zpool-docker
       Zpool Health: ONLINE
       Parent Dataset: zpool-docker
       Space Used By Parent: 249856
       Space Available: 103498395648
       Parent Quota: no
       Compression: off
    <...>
    

管理zfs

增加正在运行的设备的容量

要增加zpool的大小,您需要向 Docker 主机添加一个专用块设备,然后使用zpool add命令将其添加到zpool

$ sudo zpool add zpool-docker /dev/xvdh

限制容器的可写存储配额

如果您想在每个镜像/数据集的基础上实现配额,您可以设置size存储选项以限制单个容器可用于其可写层空间量。

编辑/etc/docker/daemon.json并添加以下内容

{
  "storage-driver": "zfs",
  "storage-opts": ["size=256M"]
}

守护程序参考文档中查看每个存储驱动程序的所有存储选项

保存并关闭文件,然后重新启动 Docker。

zfs存储驱动程序的工作原理

ZFS 使用以下对象

  • 文件系统:精简配置,根据需要从zpool分配空间。
  • 快照:文件系统的空间效率高的只读时间点副本。
  • 克隆:快照的读写副本。用于存储与上一层的差异。

创建克隆的过程

ZFS snapshots and clones
  1. 从文件系统创建只读快照。
  2. 从快照中创建一个可写的克隆。它包含与父层的所有差异。

文件系统、快照和克隆都从底层的zpool分配空间。

磁盘上的镜像和容器层

每个运行容器的统一文件系统都挂载在/var/lib/docker/zfs/graph/中的挂载点上。请继续阅读以了解统一文件系统的组成方式。

镜像分层和共享

镜像的基础层是一个ZFS文件系统。每个子层都是基于其下方层的ZFS快照的ZFS克隆。容器是基于其创建来源镜像的顶层的ZFS快照的ZFS克隆。

下图显示了基于两层镜像的运行容器是如何组装在一起的。

ZFS pool for Docker container

启动容器时,会按顺序执行以下步骤

  1. 镜像的基础层作为ZFS文件系统存在于Docker主机上。

  2. 附加的镜像层是托管其正下方镜像层的dataset的克隆。

    在图中,“层1”是通过对基础层进行ZFS快照,然后从该快照创建克隆来添加的。克隆是可写的,并根据需要从zpool消耗空间。快照是只读的,将基础层保持为不可变对象。

  3. 启动容器时,会在镜像上方添加一个可写层。

    在图中,容器的读写层是通过对镜像的顶层(层1)进行快照,然后从该快照创建克隆来创建的。

  4. 当容器修改其可写层的内容时,会为更改的块分配空间。默认情况下,这些块为128k。

zfs如何处理容器的读写操作

读取文件

每个容器的可写层都是一个ZFS克隆,它与其创建来源的dataset(其父层的快照)共享所有数据。即使读取的数据来自较深的层,读取操作也非常快。此图说明了块共享的工作方式

ZFS block sharing

写入文件

写入新文件:根据需要从底层的zpool分配空间,并将块直接写入容器的可写层。

修改现有文件:仅为更改的块分配空间,并使用写时复制 (CoW) 策略将这些块写入容器的可写层。这最大限度地减小了层的尺寸并提高了写入性能。

删除文件或目录:

  • 删除下层中存在的的文件或目录时,即使文件或目录仍存在于下层的只读层中,ZFS驱动程序也会屏蔽容器的可写层中文件或目录的存在。
  • 如果在容器的可写层中创建然后删除文件或目录,则zpool会回收这些块。

ZFS 和 Docker 性能

使用zfs存储驱动程序的Docker性能受多种因素影响。

  • 内存:内存对ZFS性能有重大影响。ZFS最初是为具有大量内存的大型企业级服务器设计的。

  • ZFS功能:ZFS包含一个重复数据删除功能。使用此功能可以节省磁盘空间,但会使用大量内存。建议您为与Docker一起使用的zpool禁用此功能,除非您使用SAN、NAS或其他硬件RAID技术。

  • ZFS缓存:ZFS将磁盘块缓存到称为自适应替换缓存 (ARC) 的内存结构中。ZFS的单副本ARC功能允许多个克隆共享单个缓存块的副本。此功能使ZFS成为PaaS和其他高密度用例的良好选择。

  • 碎片:碎片是像ZFS这样的写时复制文件系统的自然副产品。ZFS通过使用128k的小块大小来减轻这种情况。ZFS意图日志(ZIL)和写入合并(延迟写入)也有助于减少碎片。您可以使用zpool status监控碎片。但是,没有办法在不重新格式化和恢复文件系统的情况下整理ZFS。

  • 使用Linux的原生ZFS驱动程序:由于性能较差,不推荐使用ZFS FUSE实现。

性能最佳实践

  • 使用快速存储:固态硬盘 (SSD) 比旋转磁盘提供更快的读写速度。

  • 对于写入密集型工作负载,使用卷:卷为写入密集型工作负载提供最佳且最可预测的性能。这是因为它们绕过了存储驱动程序,并且不会产生精简配置和写时复制带来的任何潜在开销。卷还有其他好处,例如允许您在容器之间共享数据,即使没有运行的容器正在使用它们也能持久保存。