Docker用户指南(7) – Device Mapper存储驱动实践

Device Mapper是一个基于内核的框架,支持Linux上的许多高级卷管理技术。Docker的devicemapper存储驱动利用这个框架的精简置备和快照功能来管理镜像和容器。本文简称Device Mapper存储驱动为devicemapper,内核框架为Device Mapper。

AUFS的一个替代品

Docker刚开始是运行在Ubuntu和Debian Linux系统,使用AUFS作为存储后端。随着Docker变得流行,许多公司想在Red Hat Enterprise Linux上使用Docker。不过因为Linux内核上游主线没有包括AUFS,RHEL也没有用AUFS。
要解决这个问题,Red Hat开发人员着手调查让AUFS进内核主线。最终,他们决定开发一个新的存储后端。此外,它们基于现有的Device Mapper技术来开发新的存储后端。
Red Hat与Docker公司合作开发这个新的驱动。由于这次合作,Docker公司把Engine的存储后端重新设计成可插拔的。所以devicemapper成为Docker支持的第二个存储驱动。
从Linux内核版本2.6.9起,Device Mapper已经包含在Linux内核主线中。它是RHEL系列Linux发行版的核心部分。这意味着devicemapper存储驱动基于稳定的代码,具有大量实际生产环境部署和强大的社区支持。

镜像分层和共享

devicemapper驱动存储每个镜像和容器到它自己的虚拟设备上。这些设备是精简置备写时拷贝快照设备。Device Mapper技术工作在块级别而不是文件级别。意味着devicemapper存储驱动的精简置备和写时拷贝操作的是块而不是整个文件。
使用devicemapper创建一个镜像的过程如下:

  • 1.devicemapper存储驱动创建一个精简池(thin pool)。这个池是从块设备或循环挂载的文件
  • 2.下一步是创建一个base设备。一个base设备是具有文件系统的精简设备。你可以通过运行docker info命令检查Backing filesystem来查看使用的是哪个文件系统。
  • 3.每一个新镜像(和镜像数据层)是这个base设备的一个快照。这些是精简置备写时拷贝快照。这意味着它们初始为空,只在往它们写入数据时才消耗池中的空间。
  • 使用devicemapper驱动时,容器数据层是从其创建的镜像的快照。与镜像一样,容器快照是精简置备写时拷贝快照。容器快照存储着容器的所有更改。当数据写入容器时,devicemapper从存储池按需分配空间。
    下图显示一个具有一个base设备和两个镜像的精简池。

    如果你仔细查看图表你会发现快照一个连着一个。每一个镜像数据层是它下面数据层的一个快照。每个镜像的最底端数据层是存储池中base设备的快照。此base设备是Device Mapper的工件,而不是Docker镜像数据层。
    一个容器是从其创建的镜像的一个快照。下图显示两个容器 – 一个基于Ubuntu镜像和另一个基于Busybox镜像。

    使用devicemapper读文件

    我们来看下使用devicemapper存储驱动如何进行读和写。下图显示在示例容器中读取一个单独的块[0x44f]的过程。

  • 1.一个应用程序请求读取容器中0x44f数据块。由于容器是一个镜像的一个精简快照,它没有那数据。不过有存储在镜像堆栈下的镜像快照的数据的指针。
  • 2.存储驱动跟随指针找到与镜像数据层a005关联的快照数据块0xf33 …
  • 3.devicemapper从镜像快照复制数据块0xf33的内容到容器内存中。
  • 4.存储驱动返回数据给请求数据的应用程序。
  • 写示例

    使用devicemapper驱动,通过按需分配(allocate-on-demand)操作来实现写入新数据到容器。更新存在的数据使用写时拷贝(copy-on-write)操作。由于Device Mapper是基于块的技术,这些操作发生在块级别上。
    例如,当更新容器中一个大文件的一小部分,devicemapper存储驱动不会复制整个文件。它仅复制要更改的数据块。每个数据块是64KB。

    写入新数据

    要写入56KB的新数据到容器:

  • 1.一个应用程序请求写入56KB的新数据到容器。
  • 2.按需分配操作给容器快照分配一个新的64KB数据块。如果写操作大于64KB,就分配多个新数据块给容器快照。
  • 3.新的数据写入到新分配的数据块。
  • 覆盖存在的数据

    首次更改已存在的数据时:

  • 1.一个应用程序请求更新容器中的一些数据。
  • 2.写时拷贝操作定位需要更新的数据块。
  • 3.分配新的空白数据块给容器快照并复制数据到这些数据块。
  • 4.更新好的数据写入到新分配的数据块。
  • 容器中的应用程序不知道这些按需分配和写时拷贝操作。不过,这些操作可能会增加应用程序的读和写操作延迟。

    配置Docker使用devicemapper

    在一些Linux发行版本中,devicemapper是Docker的默认存储驱动。包括RHEL和它的大多数分支。目前,支持此驱动的发行版本如下:

  • RHEL/CentOS/Fedora
  • Ubuntu 12.04
  • Ubuntu 14.04
  • Debian
  • Arch Linux
  • Docker主机运行devicemapper存储驱动时,默认的配置模式为loop-lvm。此模式使用空闲的文件来构建用于镜像和容器快照的精简存储池。该模式设计为无需额外配置开箱即用(out-of-the-box)。不过生产部署不应该以loop-lvm模式运行。
    你可以使用docker info命令来检查目前使用的模式:

    1. $ sudo docker info
    2.  
    3. Containers: 0
    4. Images: 0
    5. Storage Driver: devicemapper
    6.  Pool Name: docker-202:2-25220302-pool
    7.  Pool Blocksize: 65.54 kB
    8.  Backing Filesystem: xfs
    9.  [...]
    10.  Data loop file: /var/lib/docker/devicemapper/devicemapper/data
    11.  Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
    12.  Library Version: 1.02.93-RHEL7 (2015-01-28)
    13.  [...]

    上面的输出显示Docker主机运行的devicemapper存储驱动的模式为loop-lvm。因为Data loop file和Metadata loop file指向/var/lib/docker/devicemapper/devicemapper下的文件。这些文件是环回挂载(loopback mounted)文件。

    生产环境配置direct-lvm模式

    生产部署首选配置是direct-lvm。这个模式使用块设备来创建存储池。下面展示使用配置在使用devicemapper存储驱动的Docker主机上配置使用direct-lvm模式。
    下面的步骤创建一个逻辑卷,配置用作存储池的后端。我们假设你有在/dev/xvdf的充足空闲空间的块设备。也假设你的Docker daemon已停止。

  • 1.登录你要配置的Docker主机并停止Docker daemon。
  • 2.安装LVM2软件包。LVM2软件包含管理Linux上逻辑卷的用户空间工具集。
  • 3.创建一个物理卷
    $ pvcreate /dev/xvdf
  • 4.创建一个“docker”卷组
    1. $ vgcreate docker /dev/xvdf
  • 5.创建一个名为thinpool的存储池。
    在此示例中,设置池大小为“docker”卷组大小的95%。 其余的空闲空间可以用来自动扩展数据或元数据。
    1. $ lvcreate --wipesignatures y -n thinpool docker -l 95%VG
    2. $ lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG
  • 6.转换存储池
    1. $ lvconvert -y --zero n -c 512K --thinpool docker/thinpool --poolmetadata docker/thinpoolmeta
  • 7.通过lvm profile配置存储池autoextension
    1. $ vi /etc/lvm/profile/docker-thinpool.profile
  • 8.设置thin_pool_autoextend_threshold值。这个值应该是之前设置存储池余下空间的百分比(100 = disabled)。
    1. thin_pool_autoextend_threshold = 80
  • 9.为当存储池autroextension发生时更改thin_pool_autoextend_percent值。
    该值的设置是增加存储池的空间百分比(100 =禁用)
    1. thin_pool_autoextend_percent = 20
  • 10.检查你的docker-thinpool.profile的设置。一个示例/etc/lvm/profile/docker-thinpool.profile应该类似如下:
    1. activation {
    2.     thin_pool_autoextend_threshold=80
    3.     thin_pool_autoextend_percent=20
    4. }
  • 11.应用新lvm配置
    1. $ lvchange --metadataprofile docker-thinpool docker/thinpool
  • 12.验证lv是否受监控。
    1. $ lvs -o+seg_monitor
  • 13.如果Docker daemon之前已经启动过,移动你的驱动数据目录到其它地方。
    1. $ mkdir /var/lib/docker.bk
    2. $ mv /var/lib/docker/* /var/lib/docker.bk
  • 14.配置一些特定的devicemapper选项。
    如果你是用dockerd命令行启动docker,使用如下参数:
    1. --storage-driver=devicemapper --storage-opt=dm.thinpooldev=/dev/mapper/docker-thinpool --storage-opt=dm.use_deferred_removal=true --storage-opt=dm.use_deferred_deletion=true

    你也可以在daemon.json启动配置文件设置它们,例如:

    1. {
    2.   "storage-driver": "devicemapper",
    3.    "storage-opts": [
    4.      "dm.thinpooldev=/dev/mapper/docker-thinpool",
    5.      "dm.use_deferred_removal=true",
    6.      "dm.use_deferred_deletion=true"
    7.    ]
    8. }
  • 15.如果你用的是systemd及通过unit或drop-in文件更新daemon配置文件,重载systemd来扫描配置更改。
    1. $ systemctl daemon-reload
  • 16.启动docker daemon
    1. $ systemctl start docker
  • 启动Docker daemon后,确保你监控存储池和卷组的可用空间。虽然卷组会自动扩展,它仍然会占满空间。要监控逻辑卷,使用lvs或lvs -a来查看数据和元数据大小。要监控卷组可用空间,使用vgs命令。
    当达到阈值时,可以通过日志查看存储池的自动扩展,使用如下命令:

    1. $ journalctl -fu dm-event.service

    检查主机上的devicemapper结构

    你可以使用lsblk命令来查看以上创建的设备文件和存储池。

    1. $ sudo lsblk
    2. NAME               MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    3. xvda               202:0    0    8G  0 disk
    4. └─xvda1            202:1    0    8G  0 part /
    5. xvdf               202:80   0   10G  0 disk
    6. ├─vg--docker-data          253:0    0   90G  0 lvm
    7. │ └─docker-202:1-1032-pool 253:2    0   10G  0 dm
    8. └─vg--docker-metadata      253:1    0    4G  0 lvm
    9.   └─docker-202:1-1032-pool 253:2    0   10G  0 dm

    下图显示由lsblk命令输出的之前镜像的详细信息。

    在这个图中,存储池名称为Docker-202:1-1032-pool,包括之前创建的data和metadata设备。devicemapper构造池名称如下:

    1. Docker-MAJ:MIN-INO-pool

    MAJ,MIN和INO指主设备号和次设备号和inode。
    因为Device Mapper在块级别操作,所以更难以看到镜像层和容器之间的差异。Docker 1.10和更高版本不再将镜像层ID与/var/lib/docker中的目录名称匹配。 但是,有两个关键目录。/var/lib/docker/devicemapper/mnt目录包含镜像层和容器层的挂载点。/var/lib/docker/devicemapper/metadata目录包含每个镜像层和容器快照对应的一个文件。这个文件包含每个快照的JSON格式元数据。

    增加正在使用设备的容器

    你可以增加使用中的存储池的容量。如果你的数据逻辑卷已满这会有所帮助。

    对于loop-lvm模式的配置

    在这个场景下,存储池配置为使用loop-lvm模式。使用docker info查看目前的配置:

    1. $ sudo docker info
    2.  
    3. Containers: 0
    4.  Running: 0
    5.  Paused: 0
    6.  Stopped: 0
    7. Images: 2
    8. Server Version: 1.11.0
    9. Storage Driver: devicemapper
    10.  Pool Name: docker-8:1-123141-pool
    11.  Pool Blocksize: 65.54 kB
    12.  Base Device Size: 10.74 GB
    13.  Backing Filesystem: ext4
    14.  Data file: /dev/loop0
    15.  Metadata file: /dev/loop1
    16.  Data Space Used: 1.202 GB
    17.  Data Space Total: 107.4 GB
    18.  Data Space Available: 4.506 GB
    19.  Metadata Space Used: 1.729 MB
    20.  Metadata Space Total: 2.147 GB
    21.  Metadata Space Available: 2.146 GB
    22.  Udev Sync Supported: true
    23.  Deferred Removal Enabled: false
    24.  Deferred Deletion Enabled: false
    25.  Deferred Deleted Device Count: 0
    26.  Data loop file: /var/lib/docker/devicemapper/devicemapper/data
    27.  WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.
    28.  Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
    29.  Library Version: 1.02.90 (2014-09-01)
    30. Logging Driver: json-file
    31. [...]

    Data Space值显示存储池总共大小为100GB。此示例扩展存储池到200GB。
    1.列出设备的大小。

    1. $ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
    2.  
    3. total 1175492
    4. -rw------- 1 root root 100G Mar 30 05:22 data
    5. -rw------- 1 root root 2.0G Mar 31 11:17 metadata

    2.扩展data文件大小为200GB。

    1. $ sudo truncate -s 214748364800 /var/lib/docker/devicemapper/devicemapper/data

    3.验证更改的大小。

    1. $ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
    2.  
    3. total 1.2G
    4. -rw------- 1 root root 200G Apr 14 08:47 data
    5. -rw------- 1 root root 2.0G Apr 19 13:27 metadata

    4.重载数据loop设备

    1. $ sudo blockdev --getsize64 /dev/loop0
    2.  
    3. 107374182400
    4.  
    5. $ sudo losetup -c /dev/loop0
    6.  
    7. $ sudo blockdev --getsize64 /dev/loop0
    8.  
    9. 214748364800

    5.重载devicemapper存储池。
    1) 先获取存储池名称。

    1. $ sudo dmsetup status | grep pool
    2.  
    3. docker-8:1-123141-pool: 0 209715200 thin-pool 91
    4. 422/524288 18338/1638400 - rw discard_passdown queue_if_no_space -

    冒号前面部分是名称。
    2) 导出device mapper表:

    1. $ sudo dmsetup table docker-8:1-123141-pool
    2.  
    3. 0 209715200 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing

    3) 现在计算存储池的实际总扇区。
    更改表信息的第二个数字(即磁盘结束扇区),以反映磁盘中512字节扇区的新数。 例如,当新loop大小为200GB时,将第二个数字更改为419430400。
    4) 使用新扇区号重新加载存储池

    1. $ sudo dmsetup suspend docker-8:1-123141-pool \
    2.     && sudo dmsetup reload docker-8:1-123141-pool --table '0 419430400 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing' \
    3.     && sudo dmsetup resume docker-8:1-123141-pool

    device_tool

    Docker项目的contrib目录不是核心分发的一部分。 这些工具通常很有用,但也可能已过期。这个目录的device_tool.go可以调整loop-lvm精简池的大小。
    要使用该工具,首先编译它。 然后,执行以下操作调整池大小:

    1. $ ./device_tool resize 200GB

    对于direct-lvm模式的配置

    在此示例中,你将扩展使用direct-lvm模式设备的容量。此示例假设你使用的是/dev/sdh1磁盘分区。
    1.扩展卷组(VG)vg-docker。

    1. $ sudo vgextend vg-docker /dev/sdh1
    2.  
    3. Volume group "vg-docker" successfully extended

    2.扩展数据逻辑卷(LV)vg-docker/data

    1. $ sudo lvextend  -l+100%FREE -n vg-docker/data
    2.  
    3. Extending logical volume data to 200 GiB
    4. Logical volume data successfully resized

    3.重载devicemapper存储池。
    1) 获取存储池名称。

    1. $ sudo dmsetup status | grep pool
    2.  
    3. docker-253:17-1835016-pool: 0 96460800 thin-pool 51593 6270/1048576 701943/753600 - rw no_discard_passdown queue_if_no_space

    2)导出device mapper表。

    1. $ sudo dmsetup table docker-253:17-1835016-pool
    2.  
    3. 0 96460800 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing

    3)现在计算存储池的实际总扇区。 我们可以使用blockdev来获取数据lv的实际大小。
    更改表信息的第二个数(即扇区数)以反映磁盘中512个字节扇区的新数。 例如,由于新数据lv大小为264132100096字节,请将第二个数字更改为515883008。

    1. $ sudo blockdev --getsize64 /dev/vg-docker/data
    2.  
    3. 264132100096

    4)然后使用新的扇区号重新加载存储池。

    1. $ sudo dmsetup suspend docker-253:17-1835016-pool \
    2.     && sudo dmsetup reload docker-253:17-1835016-pool \
    3.       --table  '0 515883008 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing' \
    4.     && sudo dmsetup resume docker-253:17-1835016-pool

    Device Mapper及Docker性能

    了解按需分配和写时拷贝操作对整体容器性能的影响很重要。

    按需分配对性能的影响

    devicemapper存储驱动通过按需分配操作给容器分配新的数据块。这意味着每次应用程序写入容器内的某处时,一个或多个空数据块从存储池中分配并映射到容器中。
    所有数据块为64KB。 写小于64KB的数据仍然分配一个64KB数据块。写入超过64KB的数据分配多个64KB数据块。这可能会影响容器性能,特别是在执行大量小写的容器中。不过一旦数据块分配给容器,后续的读和写可以直接在该数据块上操作。

    写时拷贝对性能的影响

    每当容器首次更新现有数据时,devicemapper存储驱动必须执行写时拷贝操作。这会从镜像快照复制数据到容器快照。此过程对容器性能产生显着影响。因此,更新一个1GB文件的32KB数据只复制一个64KB数据块到容器快照。这比在文件级别操作需要复制整个1GB文件到容器数据层有明显的性能优势。
    不过在实践中,使用devicemapper执行大量小块写入(device mapper其它性能注意事项

    还有其他一些影响devicemapper存储驱动性能的因素。

  • 模式。Docker使用的devicemapper存储驱动的默认模式是loop-lvm。这个模式使用空闲文件来构建存储池,性能非常低。不建议用到生产环境。推荐用在生产环境的模式是direct-lvm。
  • 高速存储。为获得最佳性能,应将数据文件和元数据文件放在高速存储(如SSD)上。
  • 内存使用。devicemapper在内存使用方面不是最有效率的。启动同一容器的n个副本会把n个副本加载到内存中。这可能影响你Docker主机的内存使用。因此,dervicemapper存储驱动可能不是PaaS和其他类似用例的最佳选择。
  • 最后一点,数据卷提供了最好的和最可预测的性能。这是因为他们绕过存储驱动,并且没有精简置备和写时拷贝引入的潜在开销。

    标签:Docker 发布于:2019-11-20 03:53:24