docker学习笔记-DeviceMapper

2016-11-28 16:09:12

DeviceMapper简介

DeviceMapper自Linux 2.6被引入成为了Linux最重要的一个技术。它在内核中支持逻辑卷管理的通用设备映射机制,它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核架构,它包含三个重要的对象概念,Mapped Device、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在内核中通过一个一个模块化的Target Driver插件实现对IO请求的过滤或者重新定向等工作,当前已经实现的插件包括软 Raid、加密、多路径、镜像、快照等,这体现了在Linux内核设计中策略和机制分离的原则。如下图可以看到DeviceMapper只是一个框架,在这个框架上,可以插入各种各样的策略,在这诸多“插件”中,有一个东西叫Thin Provisioning Snapshot,这是Docker使用DeviceMapper中最重要的模块

Thin Provisioning 简介

Thin Provisioning是虚拟化技术中的一种。类似于虚拟内存技术。操作系统给每个进程N多用不完的内址地址(32位下,每个进程可以有最多2GB的内存空间)。但是物理内存是没有那么多的。所以操作系统引入了虚拟内存的设计。逻辑上给无限多的内存,但是实际上是实报实销,达到了内存使用率提高的效果。 第一个是Fat Provisioning。 第二个是Thin Provisioning

Docker是怎么使用Thin Provisioning这个技术做到像UnionFS那样的分层镜像的呢?答案是,Docker使用了Thin Provisioning的Snapshot的技术,下面我们来介绍一下Thin Provisioning的Snapshot。 先建两个文件,一个是data.img,一个是meta.data.img

1
2
3
4
5
6
7
8
$ sudo 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.000621428 s, 1.6 MB/s
$ sudo 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.000140858 s, 7.3 MB/s

注意命令中seek选项,其表示为略过of选项指定的输出文件的前10G个output的bloksize的空间后再写入内容。因为bs是1个字节,所以也就是10G的尺寸,但其实在硬盘上是没有占有空间的,占有空间只有1k的内容。当向其写入内容时,才会在硬盘上为其分配空间。可以用ls命令看一下,实际分配了12K和4K

1
2
3
4
sudo ls -lsh /tmp/data.img
12K -rw-r--r--. 1 root root 11G Aug 25 23:01 /tmp/data.img
$ sudo ls -slh /tmp/meta.data.img
4.0K -rw-r--r--. 1 root root 101M Aug 25 23:17 /tmp/meta.data.img

为这个文件创建一个loopback设备

1
2
3
4
5
$ sudo losetup /dev/loop2015 /tmp/data.img
$ sudo losetup /dev/loop2016 /tmp/meta.data.img
$ sudo losetup -a
/dev/loop2015: [64768]:103991768 (/tmp/data.img)
/dev/loop2016: [64768]:103991765 (/tmp/meta.data.img)

为这个设备建一个Thin Provisioning的Pool,用dmsetup命令

1
2
3
sudo dmsetup create hchen-thin-pool \
--table "0 20971522 thin-pool /dev/loop2016 /dev/loop2015 \
128 65536 1 skip_block_zeroing"

参数解释如下

  • dmsetup create是用来创建thin pool的命令。
  • hchen-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的设备了

1
2
sudo ll /dev/mapper/hchen-thin-pool
lrwxrwxrwx. 1 root root 7 Aug 25 23:24 /dev/mapper/hchen-thin-pool -> ../dm-4

接下来创建一个Thin Provisioning的Volume

1
2
3
$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_thin 0"
$ sudo dmsetup create hchen-thin-volumn-001 \
--table "0 2097152 thin /dev/mapper/hchen-thin-pool 0"
  • 第一个命令中的create_thin是关键字,后面的0表示这个Volume的device的id。
  • 第二个命令是真正的为这个Volumn创建一个可以mount的设备,名字叫hchen-thin-volumn-001。2097152只有1GB。

在mount之前,我们格式化一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ sudo mkfs.ext4 /dev/mapper/hchen-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

终于可以mount了

1
2
3
4
5
$ sudo mkdir -p /mnt/base
$ sudo mount /dev/mapper/hchen-thin-volumn-001 /mnt/base
$ sudo echo "hello world, I am a base" > /mnt/base/id.txt
$ sudo cat /mnt/base/id.txt
hello world, I am a base

下面来操作snapshot。

1
2
3
4
5
$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_snap 1 0"
$ sudo dmsetup create mysnap1 \
--table "0 2097152 thin /dev/mapper/hchen-thin-pool 1"
$ sudo ll /dev/mapper/mysnap1
lrwxrwxrwx. 1 root root 7 Aug 25 23:49 /dev/mapper/mysnap1 -> ../dm-5
  • 第一条命令是向hchen-thin-pool发一个create_snap的消息,后面跟两个id,第一个是新的dev id,第二个是要从哪个已有的dev id上做snapshot。
  • 第二条命令是创建一个mysnap1的device,并可以被mount
1
2
3
4
5
6
7
8
$ sudo mkdir -p /mnt/mysnap1
$ sudo mount /dev/mapper/mysnap1 /mnt/mysnap1
$ sudo ll /mnt/mysnap1/
total 20
-rw-r--r--. 1 root root 25 Aug 25 23:46 id.txt
drwx------. 2 root root 16384 Aug 25 23:43 lost+found
$ sudo cat /mnt/mysnap1/id.txt
hello world, I am a base

我们在修改一下/mnt/mysnap1/id.txt,并加上一个snap1.txt的文件

1
2
3
4
5
6
7
$ sudo echo "I am snap1" >> /mnt/mysnap1/id.txt
$ sudo echo "I am snap1" > /mnt/mysnap1/snap1.txt
$ sudo cat /mnt/mysnap1/id.txt
hello world, I am a base
I am snap1
$ sudo cat /mnt/mysnap1/snap1.txt
I am snap1

再看一下/mnt/base,你会发现没有什么变化

1
2
3
4
$ sudo ls /mnt/base
id.txt lost+found
$ sudo cat /mnt/base/id.txt
hello world, I am a base

似乎已经看到了分层镜像的样子。。。
在刚才的snapshot上再建一个snapshot

1
2
3
4
5
6
7
8
9
$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_snap 2 1"
$ sudo dmsetup create mysnap2 \
--table "0 2097152 thin /dev/mapper/hchen-thin-pool 2"
$ sudo ll /dev/mapper/mysnap2
lrwxrwxrwx. 1 root root 7 Aug 25 23:52 /dev/mapper/mysnap1 -> ../dm-7
$ sudo mkdir -p /mnt/mysnap2
$ sudo mount /dev/mapper/mysnap2 /mnt/mysnap2
$ sudo ls /mnt/mysnap2
id.txt lost+found snap1.txt

看到了分层镜像。这里需要补充的理论知识

  • Snapshot来自LVM(Logic Volumn Manager),它可以在不中断服务的情况下为某个device打一个快照
  • Snapshot是Copy-On-Write的,也就是说,只有发生了修改,才会对对应的内存进行拷贝

docker的loopback设备

1
2
3
$ sudo losetup -a
/dev/loop0: [64768]:38050288 (/var/lib/docker/devicemapper/devicemapper/data)
/dev/loop1: [64768]:38050289 (/var/lib/docker/devicemapper/devicemapper/metadata)

其中data 100GB,metadata 2.0GB

1
2
3
$ sudo ls -alhs /var/lib/docker/devicemapper/devicemapper
506M -rw-------. 1 root root 100G Sep 10 20:15 data
1.1M -rw-------. 1 root root 2.0G Sep 10 20:15 metadata

下面是相关的thin-pool。其中,有个当一大串hash串的device是正在启动的容器

1
2
3
$ sudo ll /dev/mapper/dock*
lrwxrwxrwx. 1 root root 7 Aug 25 07:57 /dev/mapper/docker-253:0-104108535-pool -> ../dm-2
lrwxrwxrwx. 1 root root 7 Aug 25 11:13 /dev/mapper/docker-253:0-104108535-deefcd630a60aa5ad3e69249f58a68e717324be4258296653406ff062f605edf -> ../dm-3

看一下device id

1
2
sudo cat /var/lib/docker/devicemapper/metadata/deefcd630a60aa5ad3e69249f58a68e717324be4258296653406ff062f605edf
{"device_id":24,"size":10737418240,"transaction_id":26,"initialized":false}

device_id是24,size是10737418240,除以512,就是20971520 个 sector,我们用这些信息来做个snapshot看看(注:我用了一个比较大的dev id – 1024)

1
2
3
4
5
6
7
8
9
10
sudo dmsetup message "/dev/mapper/docker-253:0-104108535-pool" 0 \
"create_snap 1024 24"
$ sudo dmsetup create dockersnap --table \
"0 20971520 thin /dev/mapper/docker-253:0-104108535-pool 1024"
$ sudo mkdir /mnt/docker
$ sudo mount /dev/mapper/dockersnap /mnt/docker/
$ sudo ls /mnt/docker/
id lost+found rootfs
$ sudo ls /mnt/docker/rootfs/
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var

在docker的容器里用findmnt命令也可以看到相关的mount的情况

1
2
3
4
5
6
# findmnt
TARGET SOURCE
/ /dev/mapper/docker-253:0-104108535-deefcd630a60[/rootfs]
/etc/resolv.conf /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/resolv.conf]
/etc/hostname /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/hostname]
/etc/hosts /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/hosts]

ref

Docker基础技术:DeviceMapper
Docker学习笔记(六)Linux DeviceMapper


您的鼓励是我写作最大的动力

俗话说,投资效率是最好的投资。 如果您感觉我的文章质量不错,读后收获很大,预计能为您提高 10% 的工作效率,不妨小额捐助我一下,让我有动力继续写出更多好文章。