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创建一个镜像的过程如下:
使用devicemapper驱动时,容器数据层是从其创建的镜像的快照。与镜像一样,容器快照是精简置备写时拷贝快照。容器快照存储着容器的所有更改。当数据写入容器时,devicemapper从存储池按需分配空间。
下图显示一个具有一个base设备和两个镜像的精简池。
如果你仔细查看图表你会发现快照一个连着一个。每一个镜像数据层是它下面数据层的一个快照。每个镜像的最底端数据层是存储池中base设备的快照。此base设备是Device Mapper的工件,而不是Docker镜像数据层。
一个容器是从其创建的镜像的一个快照。下图显示两个容器 – 一个基于Ubuntu镜像和另一个基于Busybox镜像。
使用devicemapper读文件
我们来看下使用devicemapper存储驱动如何进行读和写。下图显示在示例容器中读取一个单独的块[0x44f]的过程。
写示例
使用devicemapper驱动,通过按需分配(allocate-on-demand)操作来实现写入新数据到容器。更新存在的数据使用写时拷贝(copy-on-write)操作。由于Device Mapper是基于块的技术,这些操作发生在块级别上。
例如,当更新容器中一个大文件的一小部分,devicemapper存储驱动不会复制整个文件。它仅复制要更改的数据块。每个数据块是64KB。
写入新数据
要写入56KB的新数据到容器:
覆盖存在的数据
首次更改已存在的数据时:
容器中的应用程序不知道这些按需分配和写时拷贝操作。不过,这些操作可能会增加应用程序的读和写操作延迟。
配置Docker使用devicemapper
在一些Linux发行版本中,devicemapper是Docker的默认存储驱动。包括RHEL和它的大多数分支。目前,支持此驱动的发行版本如下:
Docker主机运行devicemapper存储驱动时,默认的配置模式为loop-lvm。此模式使用空闲的文件来构建用于镜像和容器快照的精简存储池。该模式设计为无需额外配置开箱即用(out-of-the-box)。不过生产部署不应该以loop-lvm模式运行。
你可以使用docker info命令来检查目前使用的模式:
- $ sudo docker info
- Containers: 0
- Images: 0
- Storage Driver: devicemapper
- Pool Name: docker-202:2-25220302-pool
- Pool Blocksize: 65.54 kB
- Backing Filesystem: xfs
- […]
- Data loop file: /var/lib/docker/devicemapper/devicemapper/data
- Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
- Library Version: 1.02.93-RHEL7 (2015-01-28)
- […]
上面的输出显示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已停止。
$ pvcreate /dev/xvdf
- $ vgcreate docker /dev/xvdf
在此示例中,设置池大小为“docker”卷组大小的95%。 其余的空闲空间可以用来自动扩展数据或元数据。
- $ lvcreate –wipesignatures y -n thinpool docker -l 95%VG
- $ lvcreate –wipesignatures y -n thinpoolmeta docker -l 1%VG
- $ lvconvert -y –zero n -c 512K –thinpool docker/thinpool –poolmetadata docker/thinpoolmeta
- $ vi /etc/lvm/profile/docker-thinpool.profile
- thin_pool_autoextend_threshold = 80
该值的设置是增加存储池的空间百分比(100 =禁用)
- thin_pool_autoextend_percent = 20
- activation {
- thin_pool_autoextend_threshold=80
- thin_pool_autoextend_percent=20
- }
- $ lvchange –metadataprofile docker-thinpool docker/thinpool
- $ lvs -o+seg_monitor
- $ mkdir /var/lib/docker.bk
- $ mv /var/lib/docker/* /var/lib/docker.bk
如果你是用dockerd命令行启动docker,使用如下参数:
- –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启动配置文件设置它们,例如:
- {
- "storage-driver": "devicemapper",
- "storage-opts": [
- "dm.thinpooldev=/dev/mapper/docker-thinpool",
- "dm.use_deferred_removal=true",
- "dm.use_deferred_deletion=true"
- ]
- }
- $ systemctl daemon-reload
- $ systemctl start docker
启动Docker daemon后,确保你监控存储池和卷组的可用空间。虽然卷组会自动扩展,它仍然会占满空间。要监控逻辑卷,使用lvs或lvs -a来查看数据和元数据大小。要监控卷组可用空间,使用vgs命令。
当达到阈值时,可以通过日志查看存储池的自动扩展,使用如下命令:
- $ journalctl -fu dm-event.service
检查主机上的devicemapper结构
你可以使用lsblk命令来查看以上创建的设备文件和存储池。
- $ sudo lsblk
- NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
- xvda 202:0 0 8G 0 disk
- └─xvda1 202:1 0 8G 0 part /
- xvdf 202:80 0 10G 0 disk
- ├─vg–docker-data 253:0 0 90G 0 lvm
- │ └─docker-202:1-1032-pool 253:2 0 10G 0 dm
- └─vg–docker-metadata 253:1 0 4G 0 lvm
- └─docker-202:1-1032-pool 253:2 0 10G 0 dm
下图显示由lsblk命令输出的之前镜像的详细信息。
在这个图中,存储池名称为Docker-202:1-1032-pool,包括之前创建的data和metadata设备。devicemapper构造池名称如下:
- 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查看目前的配置:
- $ sudo docker info
- Containers: 0
- Running: 0
- Paused: 0
- Stopped: 0
- Images: 2
- Server Version: 1.11.0
- Storage Driver: devicemapper
- Pool Name: docker-8:1-123141-pool
- Pool Blocksize: 65.54 kB
- Base Device Size: 10.74 GB
- Backing Filesystem: ext4
- Data file: /dev/loop0
- Metadata file: /dev/loop1
- Data Space Used: 1.202 GB
- Data Space Total: 107.4 GB
- Data Space Available: 4.506 GB
- Metadata Space Used: 1.729 MB
- Metadata Space Total: 2.147 GB
- Metadata Space Available: 2.146 GB
- Udev Sync Supported: true
- Deferred Removal Enabled: false
- Deferred Deletion Enabled: false
- Deferred Deleted Device Count: 0
- Data loop file: /var/lib/docker/devicemapper/devicemapper/data
- WARNING: Usage of loopback devices is strongly discouraged for production use. Use `–storage-opt dm.thinpooldev` to specify a custom block storage device.
- Metadata loop file: /var/lib/docker/devicemapper/devicemapper/metadata
- Library Version: 1.02.90 (2014-09-01)
- Logging Driver: json-file
- […]
Data Space值显示存储池总共大小为100GB。此示例扩展存储池到200GB。
1.列出设备的大小。
- $ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
- total 1175492
- -rw——- 1 root root 100G Mar 30 05:22 data
- -rw——- 1 root root 2.0G Mar 31 11:17 metadata
2.扩展data文件大小为200GB。
- $ sudo truncate -s 214748364800 /var/lib/docker/devicemapper/devicemapper/data
3.验证更改的大小。
- $ sudo ls -lh /var/lib/docker/devicemapper/devicemapper/
- total 1.2G
- -rw——- 1 root root 200G Apr 14 08:47 data
- -rw——- 1 root root 2.0G Apr 19 13:27 metadata
4.重载数据loop设备
- $ sudo blockdev –getsize64 /dev/loop0
- 107374182400
- $ sudo losetup -c /dev/loop0
- $ sudo blockdev –getsize64 /dev/loop0
- 214748364800
5.重载devicemapper存储池。
1) 先获取存储池名称。
- $ sudo dmsetup status | grep pool
- docker-8:1-123141-pool: 0 209715200 thin-pool 91
- 422/524288 18338/1638400 – rw discard_passdown queue_if_no_space –
冒号前面部分是名称。
2) 导出device mapper表:
- $ sudo dmsetup table docker-8:1-123141-pool
- 0 209715200 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing
3) 现在计算存储池的实际总扇区。
更改表信息的第二个数字(即磁盘结束扇区),以反映磁盘中512字节扇区的新数。 例如,当新loop大小为200GB时,将第二个数字更改为419430400。
4) 使用新扇区号重新加载存储池
- $ sudo dmsetup suspend docker-8:1-123141-pool
- && sudo dmsetup reload docker-8:1-123141-pool –table ‘0 419430400 thin-pool 7:1 7:0 128 32768 1 skip_block_zeroing’
- && sudo dmsetup resume docker-8:1-123141-pool
device_tool
Docker项目的contrib目录不是核心分发的一部分。 这些工具通常很有用,但也可能已过期。这个目录的device_tool.go可以调整loop-lvm精简池的大小。
要使用该工具,首先编译它。 然后,执行以下操作调整池大小:
- $ ./device_tool resize 200GB
对于direct-lvm模式的配置
在此示例中,你将扩展使用direct-lvm模式设备的容量。此示例假设你使用的是/dev/sdh1磁盘分区。
1.扩展卷组(VG)vg-docker。
- $ sudo vgextend vg-docker /dev/sdh1
- Volume group "vg-docker" successfully extended
2.扩展数据逻辑卷(LV)vg-docker/data
- $ sudo lvextend -l+100%FREE -n vg-docker/data
- Extending logical volume data to 200 GiB
- Logical volume data successfully resized
3.重载devicemapper存储池。
1) 获取存储池名称。
- $ sudo dmsetup status | grep pool
- 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表。
- $ sudo dmsetup table docker-253:17-1835016-pool
- 0 96460800 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing
3)现在计算存储池的实际总扇区。 我们可以使用blockdev来获取数据lv的实际大小。
更改表信息的第二个数(即扇区数)以反映磁盘中512个字节扇区的新数。 例如,由于新数据lv大小为264132100096字节,请将第二个数字更改为515883008。
- $ sudo blockdev –getsize64 /dev/vg-docker/data
- 264132100096
4)然后使用新的扇区号重新加载存储池。
- $ sudo dmsetup suspend docker-253:17-1835016-pool
- && sudo dmsetup reload docker-253:17-1835016-pool
- –table ‘0 515883008 thin-pool 252:0 252:1 128 32768 1 skip_block_zeroing’
- && sudo dmsetup resume docker-253:17-1835016-pool
Device Mapper及Docker性能
了解按需分配和写时拷贝操作对整体容器性能的影响很重要。
按需分配对性能的影响
devicemapper存储驱动通过按需分配操作给容器分配新的数据块。这意味着每次应用程序写入容器内的某处时,一个或多个空数据块从存储池中分配并映射到容器中。
所有数据块为64KB。 写小于64KB的数据仍然分配一个64KB数据块。写入超过64KB的数据分配多个64KB数据块。这可能会影响容器性能,特别是在执行大量小写的容器中。不过一旦数据块分配给容器,后续的读和写可以直接在该数据块上操作。
写时拷贝对性能的影响
每当容器首次更新现有数据时,devicemapper存储驱动必须执行写时拷贝操作。这会从镜像快照复制数据到容器快照。此过程对容器性能产生显着影响。因此,更新一个1GB文件的32KB数据只复制一个64KB数据块到容器快照。这比在文件级别操作需要复制整个1GB文件到容器数据层有明显的性能优势。
不过在实践中,使用devicemapper执行大量小块写入(<64KB)的容器比使用AUFS执行得更慢。
device mapper其它性能注意事项
还有其他一些影响devicemapper存储驱动性能的因素。
最后一点,数据卷提供了最好的和最可预测的性能。这是因为他们绕过存储驱动,并且没有精简置备和写时拷贝引入的潜在开销。