最近在官网上看了下devicemapper的说明文档,这里简要总结一下。

1 诞生的背景

期初docker是运行在Debian和Ubuntu系统上的,使用的是AUFS方式的存储。后来很多企业希望在Redhat系linux上运行docker,并决定基于现有的Device Mapper技术开发一个新的存储方案,这就是devicemapper。

2 镜像的分层和共享

Device Mapper技术不是按照文件级别(file level),而是按块级别(block level)工作的,所以devicemapper也属于块级存储方案,有着thin provisioning和copy-on-write的特点。

  • thin provisioning
    这个不知道该怎么翻译,网上叫自动精简配置的比较多(与此相对的传统的存储配置模型被称为厚存储配置FP),简单的说就是在需要的时候分配所需的最小空间(与此相对,传统的存储空间的分配都是超过目前的需求的,从而导致的一个问题就是存储利用率低下)。
  • copy-on-write
    简单的理解就是,在内容发生变化的时候才进行内容复制。

devicemapper存储方式下的镜像

使用devicemapper存储生成镜像大致按照下面的流程:
首先,devicemapper驱动从块设备创建一个小的存储池(a thin pool)
然后,创建一个带有文件系统,如extfs等,的基础设备(base device)
之后,每个新的镜像(或镜像层)都是base device的一个快照(snapshot)

devicemapper存储方式下的容器

devicemapper存储方式下,容器层都是从镜像生成的快照,快照里存储着所有对容器的更新。当数据被写入容器的时候,devicemapper按需从池中分配空间。

下面是官方文档中的一个说明图:
这里写图片描述
从上图可以看出,每个镜像层都是它下面一层的快照。每个镜像的最下面一层的镜像则是池中base device的快照。需要注意的是,base device属于Device Mapper的一部分,并不是docker的镜像层。

3 devicemapper下的读写

关于读

官网上读操作的说明图如下:
这里写图片描述
1)应用请求读取容器中的0x44f块区
因为容器是镜像的一个简单快照,并没有数据只有一个指针,指向镜像层存储数据的地方。
2)存储驱动根据指针,到镜像快照的a005e镜像层寻找0xf33块区
3)devicemapper从镜像快照拷贝0xf33的内容到容器的内存中
4)存储驱动最后将数据返回给请求的应用

关于写

当对容器中的大文件做一个小的改动的时候,devicemapper不会复制这整个文件,而是只拷贝被修改的块区。每个块区的大小为64KB。

  • 写新数据的情况
    例如,写56KB大小的新数据到一个容器:
    1)应用发出一个要写56KB的新数据到容器的请求
    2)根据按需分配,将分配一个新的64KB的块区给容器的快照
    如果写操作的数据大于64KB的话,将分配多个块区
    3)之后,数据将被写入到新分配的块区中

  • 覆写已有的数据
    每当容器第一次更新已有的数据时:
    1)应用发出一个修改容器中数据的请求
    2)copy-on-write操作将定位到需要更新的块区
    3)然后分配新的空块区给容器快照,并复制数据到新分配的块区
    4)接着,在有复制数据的新块区中进行数据修改
    容器中的应用对发生的allocate-on-demand操作和copy-on-write操作是无感的

4 关于devicemapper的配置

Redhat系的linux发行版都采用devicemapper作为docker的默认存储驱动。目前,Debian,Ubuntu和Arch Linux也支持devicemapper。

devicemapper默认使用的是loop-lvm模式,这个模式使用sparse files来创建供镜像和容器快照使用的thin pool。然而,生产环境不要使用loop-lvm模式,官方建议使用direct-lvm模式。direct-lvm模式使用块设备(block devices)来创建thin pool。

假设/dev/mapper/docker-thinpool是已建好的lvm的逻辑卷,可以配置docker daemon的运行选项如下:

--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

官方说设置dm.use_deferred_removal=true和dm.use_deferred_deletion=true选项可以防止unintentionally leaking mount points(没太明白什么意思,总之配了比不配好吧)。

devicemapper在主机上的结构

使用lsblk命令可以查看设备文件和devicemapper在设备文件上创建的pool:
这里写图片描述
对应上面接口的层次图如下:
这里写图片描述
可以看出,名为Docker-202:1-1032-pool的pool横跨在data和metadata设备之上。pool的命名规则为:

Docker-主设备号:二级设备号-inode号-pool

docker 1.10和以后的版本,在/var/lib/docker目录下不在采用镜像层ID来关联目录名了,有另外两个比较重要的目录:
/var/lib/docker/devicemapper/mnt 包含镜像和容器层的挂载目录;
/var/lib/docker/devicemapper/metadata 目录包含每个镜像层和容器快照的json格式的文件。

另外,当数据的逻辑卷要满的时候,可以给pool进行扩容,具体操作看官网。

5 关于devicemapper的性能

allocate-on-demand的性能影响

每当应用有新数据要写入容器时,都要从pool中去定位空的块区并映射给容器。因为所有块区都是64KB的,小于64KB的数据也会分配一个块区;大于64B的数据则会分配多个块区。所以,特别是当发生很多小的写操作时,就会比较影响容器的性能。

copy-on-write的性能影响

每当容器第一次更新已有的数据时,devicemapper存储驱动都要执行copy-on-write操作。这个操作是从镜像快照复制数据到容器快照,这对容器性能还是有比较明显的性能影响的。当容器发生很多小64KB的写操作时,devicemapper的性能会比AUFS要差。

其它方面

1)所使用的mode
默认情况下,devicemapper使用的是loop-lvm模式,这种模式使用的是sparse files,性能比较低。生产环境建议使用direct-lvm模式,这种模式存储驱动直接写数据到块设备。
2)使用高速存储
如果希望更好的性能,可以将Data file和Metadata file放到SSD这样的高速存储上。
3)内存使用
devicemapper并不是一个有效使用内存的存储驱动。当一个容器运行n个时,它的文件也会被拷贝n份到内存中,这对docker宿主机的内存使用会造成明显影响。因此,devicemapper存储驱动可能并不是PaaS和其它高密度使用型的最好选择。

对于写操作较大的,可以采用挂载data volumes。使用data volumes可以绕过存储驱动,从而不受thin provisioning和copy-on-write产生的负责影响。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐