Device Mapper简介
DeviceMapper自Linux 2.6被引入。它在内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核和架构,包含三个重要的对象概念,MapperDevice,Mapping Table, Target device。
Mapped Device 是一个逻辑抽象,可以理解成为内核向外提供的逻辑设备,它通过Mapping Table描述的映射关系和Target Device建立映射。Target device表示的是Mapped Device所映射的物理空间段,对Mapped Device所表示逻辑设备来收,就是该逻辑设备映射到的一个物理设备。
Mapping Table里有 Mapped Device 逻辑的起始地址、范围、和表示在 Target Device 所在物理设备的地址偏移量以及Target 类型等信息(注:这些地址和偏移量都是以磁盘的扇区为单位的,即 512 个字节大小,所以,当你看到128的时候,其实表示的是128*512=64K)。
DeviceMapper 中的逻辑设备Mapped Device不但可以映射一个或多个物理设备Target Device,还可以映射另一个Mapped Device,于是,就是构成了一个迭代或递归的情况,就像文件系统中的目录里除了文件还可以有目录,理论上可以无限嵌套下去。
devicemapper驱动将每一个Docker镜像和容器存储在它自身的具有精简置备(thin-provisioned)、写时拷贝(copy-on-write)和快照功能(snapshotting)的虚拟设备上。由于Device Mapper技术是在块(block)层面而非文件层面,所以Docker Engine的devicemapper存储驱动使用的是块设备来存储数据而非文件系统。
Thin Provisioning 精简配置
Docker使用了Thin Provisioning的Snapshot的技术实现分层镜像,
Thin Provisioning Snapshot 演示
首先,我们需要先建两个文件,一个是data.img,一个是meta.data.img:
[root@localhost ~]# dd if=/dev/zero of=/tmp/data.img bs=1K count=1 seek=10M
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.000172451 s, 5.9 MB/s
[root@localhost ~]# dd if=/dev/zero of=/tmp/meta.data.img bs=1K count=1 seek=1G
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.000164882 s, 6.2 MB/s
注意命令中seek
选项,表示略过of
选项指定得输出文件得前10M
个output的blocksize的空间后再写入内容。因为bs是1个字节,所以是10G的大小,但其实在硬盘上没有占用空间的,占有空间只有1k的内容。直到写入内容时,才会在硬盘上分配空间。
用ls命令查看
[root@localhost tmp]# ls -lsh /tmp/data.img
4.0K -rw-r--r--. 1 root root 11G Jan 25 20:27 /tmp/data.img
[root@localhost tmp]# ls -lsh /tmp/meta.data.img
4.0K -rw-r--r--. 1 root root 1.1T Jan 25 20:27 /tmp/meta.data.img
创建loopback设备。
[root@localhost tmp]# losetup /dev/loop2015 /tmp/data.img
[root@localhost tmp]# losetup /dev/loop2016 /tmp/meta.data.img
[root@localhost tmp]# losetup -a
/dev/loop2015: [64768]:16788486 (/tmp/data.img)
/dev/loop2016: [64768]:16788157 (/tmp/meta.data.img)
为这个设备建一个Thin Provisioning的Pool, 用dmsetup命令:
[root@localhost tmp]# dmsetup create test-thin-pool \
--table "0 20971522 thin-pool /dev/loop2016 /dev/loop2015 \
128 65536 1 skip_block_zeroing"
参数解释如下(更多信息参看man pnage):
- dmsetup create 创建thin pool的命令
- test-thin-pool是自定义的一个pool名,不冲突就好。
- table是这个pool的参数设置
- 0代表起的sector位置
- 20971522代码结句的sector号,前面说过,一个sector是512字节,所以,20971522个正好是10GB
- /dev/loop2016是meta文件的设备
- /dev/loop2015是data文件的设备
- 128是最小的可分配的sector数
- 65536是最少可用sector的water mark,也就是一个threshold
- 1 代表有一个附加参数
- skip_block_zeroing是个附加参数,表示略过用0填充的块
然后,就可以看到一个Device Mapper的设备:
[root@localhost tmp]# ll /dev/mapper/test-thin-pool
lrwxrwxrwx. 1 root root 7 Jan 25 20:30 /dev/mapper/test-thin-pool -> ../dm-2
接下来,创建一个Thin Provisioning的Volume:
[root@localhost tmp]# dmsetup message /dev/mapper/test-thin-pool 0 "create_thin 0"
[root@localhost tmp]# dmsetup create test-thin-volumn-001 --table "0 2097152 thin /dev/mapper/test-thin-pool 0"
期中:
- 第一个命令中的create_thin是关键字,后面的0表示这个Volume的device的id
- 第二个命令,是真正的为这个Volumn创建一个可以mount的设备,名字叫test-thin-volumn-001。 2091512只有1GB。
在mount前,格式化一下:
[root@localhost tmp]# mkfs.ext4 /dev/mapper/test-thin-volumn-001
mke2fs 1.42.9 (28-Dec-2013)
Discarding device blocks: done
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=16 blocks, Stripe width=16 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376
Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done
可以挂载了
[root@localhost tmp]# mkdir /mnt/base
[root@localhost tmp]# mount /dev/mapper/test-thin-volumn-001 /mnt/base
[root@localhost tmp]# echo "hello, im am a base" > /mnt/base/id.txt
[root@localhost tmp]# cat /mnt/base/id.txt
hello, im am a base
创建snapshot:
[root@localhost tmp]# dmsetup message /dev/mapper/test-thin-pool 0 "create_snap 1 0"
[root@localhost tmp]# dmsetup create mysnap1 --table "0 2097152 thin /dev/mapper/test-thin-pool 1"
挂载snapshot:
[root@localhost tmp]# ll /dev/mapper/mysnap1
lrwxrwxrwx. 1 root root 7 Jan 25 20:37 /dev/mapper/mysnap1 -> ../dm-4
[root@localhost tmp]# mkdir /mnt/mysnap1
[root@localhost tmp]# mount /dev/mapper/mysnap1 /mnt/mysnap1/
[root@localhost tmp]# ls /mnt/mysnap1/
id.txt lost+found
[root@localhost tmp]# cat /mnt/mysnap1/id.txt
hello, im am a base
[root@localhost tmp]# echo >> i am snap1 >> /mnt/mysnap1/id.txt
[root@localhost tmp]# echo i am snap1 >> /mnt/mysnap1/id.txt
[root@localhost tmp]# cat /mnt/mysnap1/id.txt
hello, im am a base
am snap1
i am snap1
我们再看下/mnt/base,没有新加的内容。
[root@localhost tmp]# cat /mnt/base/id.txt
hello, im am a base
我们能看到分层镜像的样子了。
Docker存储驱动devicemapper
devicemapper是RHEL的Docker Engine的默认存储驱动,有两种配置模式:loop-lvm和direct-lvm。
loop-lvm是默认的模式,它使用OS层面离散的文件来构建精简池(thin pool)。该模式主要是设计出来让Docker能够简单的被”开箱即用(out-of-the-box)”而无需额外的配置。但如果是在生产环境的部署Docker,官方明文不推荐使用该模式。
direct-lvm是Docker推荐的生产环境的推荐模式,他使用块设备来构建精简池来存放镜像和容器的数据。
自动配置
自动配置docker的devicemapper的存储驱动,需要一块独立的块设备,比如/dev/sdb。
[root@localhost ~]# fdisk -l
...
...
Disk /dev/sdb: 53.7 GB, 53687091200 bytes, 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0007eabf
Device Boot Start End Blocks Id System
/dev/sdb1 2048 104857599 52427776 8e Linux LVM
全新安装的docker,启动服务前,修改配置文件/etc/sysconfig/docker-storage-setup
DEVS="/dev/sdb"
VG=docker-vg
DATA_SIZE=100%FREE
说明一下:
- DEVS是独立的快设备名
- VG是vg的名字
- DATA_SIZE默认50%,这里设定是100%FREE
启动docker后,devicemapper的存储卷就创建成功了。
[root@node1 ~]# lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
docker-pool docker-vg twi-aot--- 49.89g 24.35 6.20
lv_01 root_vg01 -wi-ao---- <41.00g
手动配置
如果没有独立的磁盘块设备,可以在系统中的磁盘设备上空闲的块空间配置。
- 查看设备名,我们假定是/dev/xvdf
- 停止docker
# systemctl stop docker
- 安装包
yum install device-mapper-persistent-data lvm2 -y
- 创建pv
# pvcreate /dev/xvdf
Physical volume "/dev/xvdf" successfully created.
- 创建
docker
vg
# vgcreate docker /dev/xvdf
Volume group "docker" successfully created
- 创建两个lv
thinpool
和thinpoolmeta
# sudo lvcreate --wipesignatures y -n thinpool docker -l 95%VG
Logical volume "thinpool" created.
# sudo lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG
Logical volume "thinpoolmeta" created.
- 把lv转换成thin pool和metadata
# sudo lvconvert -y \
--zero n \
-c 512K \
--thinpool docker/thinpool \
--poolmetadata docker/thinpoolmeta
WARNING: Converting logical volume docker/thinpool and docker/thinpoolmeta to
thin pool's data and metadata volumes with metadata wiping.
THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.)
Converted docker/thinpool to thin pool.
- 通过lvm profile配置自动扩容
# vi /etc/lvm/profile/docker-thinpool.profile
- 指定
thin_pool_autoextend_threshold
和thin_pool_autoextend_percent
值。
当磁盘使用率达到80%,增加20%的容量.
activation {
thin_pool_autoextend_threshold=80
thin_pool_autoextend_percent=20
}
- 应用LVM profile.
sudo lvchange --metadataprofile docker-thinpool docker/thinpool
Logical volume docker/thinpool changed.
- 启用LV的监控
# sudo lvs -o+seg_monitor
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert Monitor
thinpool docker twi-a-t--- 95.00g 0.00 0.01 monitored
- 如果之前运行过docker,先备份
# mv /var/lib/docker /var/lib/docker.bk
- 编辑
/etc/docker/daemon.json
配置devicemapper存储驱动需要的参数
{
"storage-driver": "devicemapper",
"storage-opts": [
"dm.thinpooldev=/dev/mapper/docker-thinpool",
"dm.use_deferred_removal=true",
"dm.use_deferred_deletion=true"
]
}
- 启动docker
# systemctl start docker
- 验证
# docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 17.03.1-ce
Storage Driver: devicemapper
Pool Name: docker-thinpool
Pool Blocksize: 524.3 kB
Base Device Size: 10.74 GB
Backing Filesystem: xfs
Data file:
Metadata file:
Data Space Used: 19.92 MB
Data Space Total: 102 GB
Data Space Available: 102 GB
Metadata Space Used: 147.5 kB
Metadata Space Total: 1.07 GB
Metadata Space Available: 1.069 GB
Thin Pool Minimum Free Space: 10.2 GB
Udev Sync Supported: true
Deferred Removal Enabled: true
Deferred Deletion Enabled: true
Deferred Deleted Device Count: 0
Library Version: 1.02.135-RHEL7 (2016-11-16)
<output truncated>
Data file
和Metadata file
是空的,pool名字是docker-thinpool
。
device mapper在Docker中的性能表现
device mapper的性能主要受“需要时分配”策略和“写时复制”策略影响,下面分别介绍:
需要时分配(allocate-on-demand)
device mapperdriver通过allocate-on-demand策略为需要写入的数据分配数据块。也就是说,每当容器中的进程需要向容器写入数据时,device mapper就从资源池中分配一些数据块并将其映射到容器。
当容器频繁进行小数据的写操作时,这种机制非常影响影响性能。
一旦数据块被分配给了容器,对它进行的读写操作都直接对块进行操作了。
写时复制(copy-on-write)
与aufs一样,device mapper也支持写时复制策略。容器中第一次更新某个文件时,device mapper调用写时复制策略,将数据块从镜像快照中复制到容器快照中。
device mapper的写时复制策略以64KB作为粒度,意味着无论是对32KB的文件还是对1GB大小的文件的修改都仅复制64KB大小的文件。这相对于在文件层面进行的读操作具有很明显的性能优势。
但是,如果容器频繁对小于64KB的文件进行改写,device mapper的性能是低于aufs的。
存储空间使用效率
device mapper不是最有效使用存储空间的storage driver,启动n个相同的容器就复制了n份文件在内存中,这对内存的影响很大。所以device mapper并不适合容器密度高的场景。
参考
本文参考和节选了以下文章:
- https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/
- https://coolshell.cn/articles/17200.html
- https://github.com/torvalds/linux/blob/master/Documentation/device-mapper/thin-provisioning.txt
- http://blog.csdn.net/vchy_zhao/article/details/70238690
- http://blog.csdn.net/qq_26923057/article/details/52351731