Docker用户指南(6) – Btrfs存储驱动实践

Btrfs是下一代支持许多高级存储技术以使其更适合Docker的写时拷贝文件系统。Btrfs包含在Linux内核主线中以及现在稳定的硬盘格式(on-disk-format)。不过许多功能仍然在开发中,用户应该意识到此驱动目前正在快速发展中。
Docker的btrfs存储驱动利用Btrfs的许多功能来管理镜像和容器。这些功能是精简置备(thin provisioning),写时拷贝和快照。
本文把Docker的Btrfs存储驱动简称为btrfs,将整个Btrfs文件系统称为Btrfs。

Btrfs的未来

Btrfs一直被誉为是一个Linux的未来文件系统。在Linux内核主线的完全支持下,一个稳定的硬盘格式(on-disk-format),以及活跃的专注稳定性开发,离变成现实越来越近了。
就Linux平台上的Docker而言,许多人将btrfs存储驱动视为潜在的长期替代devicemapper存储驱动。不过,到目前为止,devicemapper存储驱动仍然比btrfs更安全,更稳定和更适合用于生产环境。如果你对btrfs非常熟悉且已经有Btrfs的使用经验,这时你才应该考虑把btrfs部署到生产环境中。

镜像分层与Btrfs共享

Docker利用Btrfs subvolumes和快照来管理镜像和容器数据层的硬盘组件(on-disk components)。Btrfs subvolumes看起来像一个正常的Unix文件系统。因此,他们可以有自己的内部目录结构,挂钩到更广泛的Unix文件系统。
subvolumes更新文件时涉及写时拷贝操作,写入新文件时涉及从一个底层存储池来按需分配空间的操作。它们既能嵌套也能做快照。下图显示了4个subvolumes。“subvolume 2″和”subvloume 3″是嵌套的,而“subvolume 4”显示它自己的内部目录树。

快照是在一个时间点对整一个subvolume做的一个副本。它们直接存在于从其创建的subvolume下面。你可以像下图显示的那样创建快照的快照。

Btfs从一个底层存储池给subvolumes和快照(snapshots)按需分配空间。分配单元称为块(chunk),并且块通常大小为〜1GB。
快照看起来和操作跟常规的subvolumes类似。创建快照所需的技术由于Btrfs本地的写时拷贝设计而构建进Btrfs文件系统中。这意味着Btrfs快照空间利用率高且很少或没有性能开销。下图显示一个subvolume与它的快照共享同样的数据。

Docker的btrfs存储驱动把每个镜像和容器数据层存储到Btrfs subvolume或快照。镜像的基础数据层(最底层)作为一个subvolume存储,而镜像子数据层和容器作为快照存储。如下图所示。

使用btrfs驱动的Docker主机创建镜像和容器的过程如下:

  • 1.镜像的基础数据层存储在/var/lib/docker/btrfs/subvolumes的Btrfs subvloume中。
  • 2.后续的镜像数据层存储为subvolume或快照的父级数据层的一个Btrfs快照中。
  • 下图显示一个3个数据层的镜像。base layer是一个subvolume。layer 1是base layer的subvolume的一个快照。layer 2是layer1快照的快照。

    从docker 1.10开始,镜像数据层ID不再与在/var/lib/docker的目录名称相关。

    镜像和容器在硬盘的结构

    镜像数据层和容器在docker主机的文件系统的/var/lib/docker/btrfs/subvolumes/目录可见。不过,如之前所说的,目录名不再与镜像数据层ID相关。那就是说,容器的目录即使容器已经停止都存在。这是因为btrfs存储驱动在/var/lib/docker/subvolumes/下挂载一个默认的,顶层的subvolume。所有的其它subvolumes和快照为作Btrfs文件系统对象存在,而不是作为单独挂载存在。
    因为Btrfs工作在文件系统级别上而不是块级别,所以可以使用常规的Unix命令来浏览器每个镜像和容器数据层。下面的示例显示使用ls -l命令浏览一个镜像数据层:

    1. $ ls -l /var/lib/docker/btrfs/subvolumes/0a17decee4139b0de68478f149cc16346f5e711c5ae3bb969895f22dd6723751/
    2.  
    3. total 0
    4. drwxr-xr-x 1 root root 1372 Oct  9 08:39 bin
    5. drwxr-xr-x 1 root root    0 Apr 10  2014 boot
    6. drwxr-xr-x 1 root root  882 Oct  9 08:38 dev
    7. drwxr-xr-x 1 root root 2040 Oct 12 17:27 etc
    8. drwxr-xr-x 1 root root    0 Apr 10  2014 home
    9. ...output truncated...

    使用Btrfs进行容器读和写

    一个容器是一个镜像的空间高效利用的快照。快照的元数据指向存储池的实际数据块。这与subvolume相同。因此,对快照执行的读取与对subvolume执行的读取本质相同。所以Btrfs驱动不会产生任何性能开销。
    写入一个新文件到容器涉及到一个按需分配(allocate-on-demand)操作来为容器快照分配一个新的数据块。然后文件写入到这个新空间。按需分配操作对于使用Btrfs的所有写入都是原生的,写入新数据到subvolume一样涉及到到此操作。因此,将新文件写入到容器快照的速度与本地Btrfs写入速度一样。
    在容器中更新一个存在的文件将产生一个写时拷贝操作(技术上称为redirect-on-write)。驱动保留原始数据并为快照分配新空间。更新的数据写入到这个新空间。然后驱动更新快照文件系统元数据指向新的数据。原始数据仍然保留在subvolumes中。
    使用Btfs写入和更新大量小文件可能会导致性能降低。

    配置Docker使用Btrfs

    btrfs存储驱动只在Docker主机的/var/lib/docker挂载为Btrfs文件系统上操作。下面介绍如何在Ubuntu 14.04 LTS上配置Btrfs。

    先决条件

    如果你已经在Docker主机上使用Docker daemon,并且你想保持现有的镜像,在进行下面的配置前先把镜像push到Docker Hub或者其它地方。
    停止Docker daemon,然后确保你在/dev/xvdb有一个闲置的块设备。设备标识符在你的环境中可能不同,你应该在整个过程中替换你自己的值。
    下面的步骤也假设你的内核已经正确加载了Btrfs模块。要验证它,使用如下命令:

    1. $ cat /proc/filesystems | grep btrfs
    2.  
    3.         btrfs

    在Ubuntu 14.04 LTS配置Btrfs

    假设你系统已经满足了前面提到的条件,执行如下:
    1.安装btrfs-tools包。

    1. $ sudo apt-get install btrfs-tools
    2.  
    3.  Reading package lists... Done
    4.  Building dependency tree
    5.  <output truncated>

    2.创建Btrfs存储池。
    Btrfs存储池使用mkfs.btrfs命令创建。传递多个设备给mkfs.btrfs命令将在所有这些设备之间创建一个存储池。
    这里我们使用在/dev/xvdb的单个设备创建一个存储池。

    1. $ sudo mkfs.btrfs -f /dev/xvdb
    2.  
    3.  WARNING! - Btrfs v3.12 IS EXPERIMENTAL
    4.  WARNING! - see http://btrfs.wiki.kernel.org before using
    5.  
    6.  Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
    7.  fs created label (null) on /dev/xvdb
    8.      nodesize 16384 leafsize 16384 sectorsize 4096 size 4.00GiB
    9.  Btrfs v3.12

    3.如果Docker主机本地存储区域不存在,先创建。

    1. $ sudo mkdir /var/lib/docker

    4.配置系统重启时自动挂载Btrfs文件系统。
    a.获取Btrfs文件系统的UUID。

    1. $ sudo blkid /dev/xvdb
    2.  
    3.  /dev/xvdb: UUID_SUB="c3927a64-4454-4eef-95c2-a7d44ac0cf27"

    b.在/etc/fstab添加一行来让系统每次启动时自动挂载。

    1. /dev/xvdb /var/lib/docker btrfs defaults 0 0
    2. /var/lib/docker btrfs defaults 0 0

    5.挂载新的文件系统并验证

    1. $ sudo mount -a
    2.  
    3.  $ mount
    4.  
    5.  /dev/xvda1 on / type ext4 (rw,discard)
    6.  <output truncated>
    7.  /dev/xvdb on /var/lib/docker type btrfs (rw)

    上面输出的最后一行显示/dev/xvdb作为Btrfs挂载在/var/lib/docker。
    现在你已经有一个挂载在/var/lib/docker的Btrfs文件系统,docker daemon应该能自动加载btrfs存储驱动了。
    1.启动docker daemoon。

    1. $ sudo service docker start
    2.  docker start/running, process 2315

    启动之后Docker会自动加载Btrfs,不过你可以指定–storage-driver=btrfs或添加DOCKER_OPTS行来强制Docker使用btrfs。
    2.使用docker info命令验证

    1. $ sudo docker info
    2.  
    3.  Containers: 0
    4.  Images: 0
    5.  Storage Driver: btrfs
    6.  [...]

    现在docker已经配置使用btrfs存储驱动了。

    Btrfs及Docker性能

  • 页缓存(page caching)。Btrfs不支持页缓存共享。意味着n个容器访问同一个文件需要n个副本缓存。因此,Btrfs可能不是PaaS和类似使用场景的最好选择。
  • 小文件写。容器执行大量的小文件写操作(包括Docker主机启动和停止大量容器)会导致Btrfs块利用率低的问题。这可能最终导致docker主机空间很快不足的情况,致命影响docker运行。这是目前使用当前版本的Btrfs的主要缺点。如果你使用btrfs存储驱动,需要使用btrfs filesys来密切监控空间使用情况。不要使用常规的Unix命令如df来查看空间使用情况,因为有可能会显示不正确;推荐始终使用Btrfs原生的命令。
  • 顺序写。Btrfs通过日志技术(journaling technique)将数据写入硬盘。其性能可以提升一半。
  • 碎片(Fragmentation)。碎片是写时拷贝文件系统(如Btrfs)的自然副产品。 许多小的随机写入可以导致这个问题。 它表现为使用SSD硬盘的Docker主机上的CPU峰值和使用机械硬盘的Docker主机上的抖动。 这两者都导致差的性能。
    最新版本的Btrfs允许你将autodefrag指定为mount选项。 此模式尝试检测随机写入和碎片整理。 在Docker主机上启用此选项之前,你应该先自己测试下。 一些测试显示此选项对执行大量小型写入的Docker主机(包括启动和停止许多容器的系统)具有负面性能影响。
  • 固态设备(SSD)。 Btrfs具有SSD介质的本机优化。 要启用这些选项,请使用-o ssd mount选项进行挂载。 这些优化包括通过避免诸如在SSD介质上没有用的查找优化等增强的SSD写性能。
    Btfs还支持原生的TRIM / Discard。 但是,使用-o discard mount选项进行挂载可能会导致性能问题。 因此,建议你在使用此选项之前先测试下。
  • 使用数据卷(data volumes)。 数据卷提供最佳和最可预测的性能。这是因为他们绕过存储驱动,并且不承担任何通过精简置备和写时拷贝引入的潜在开销。
  • 标签:Docker 发布于:2019-11-20 04:19:35