Docker基礎(chǔ)技術(shù):做DeviceMappee做分層鏡像
因?yàn)镈ocker***的AUFS并不在Linux的內(nèi)核主干里,所以,對(duì)于非Ubuntu的Linux分發(fā)包,比如CentOS,就無法使用AUFS作為Docker的文件系統(tǒng)了。于是作為第二優(yōu)先級(jí)的 DeviceMapper就被拿出來做分層鏡像的一個(gè)實(shí)現(xiàn)。
Device Mapper 簡介
DeviceMapper自Linux 2.6被引入成為了Linux最重要的一個(gè)技術(shù)。它在內(nèi)核中支持邏輯卷管理的通用設(shè)備映射機(jī)制,它為實(shí)現(xiàn)用于存儲(chǔ)資源管理的塊設(shè)備驅(qū)動(dòng)提供了一個(gè)高度模塊化的內(nèi)核架構(gòu),它包含三個(gè)重要的對(duì)象概念,Mapped Device、Mapping Table、Target device。
Mapped Device 是一個(gè)邏輯抽象,可以理解成為內(nèi)核向外提供的邏輯設(shè)備,它通過Mapping Table描述的映射關(guān)系和 Target Device 建立映射。Target device 表示的是 Mapped Device 所映射的物理空間段,對(duì) Mapped Device 所表示的邏輯設(shè)備來說,就是該邏輯設(shè)備映射到的一個(gè)物理設(shè)備。
Mapping Table里有 Mapped Device 邏輯的起始地址、范圍、和表示在 Target Device 所在物理設(shè)備的地址偏移量以及Target 類型等信息(注:這些地址和偏移量都是以磁盤的扇區(qū)為單位的,即 512 個(gè)字節(jié)大小,所以,當(dāng)你看到128的時(shí)候,其實(shí)表示的是128*512=64K)。
DeviceMapper 中的邏輯設(shè)備Mapped Device不但可以映射一個(gè)或多個(gè)物理設(shè)備Target Device,還可以映射另一個(gè)Mapped Device,于是,就是構(gòu)成了一個(gè)迭代或遞歸的情況,就像文件系統(tǒng)中的目錄里除了文件還可以有目錄,理論上可以***嵌套下去。
DeviceMapper在內(nèi)核中通過一個(gè)一個(gè)模塊化的 Target Driver 插件實(shí)現(xiàn)對(duì) IO 請(qǐng)求的過濾或者重新定向等工作,當(dāng)前已經(jīng)實(shí)現(xiàn)的插件包括軟 Raid、加密、多路徑、鏡像、快照等,這體現(xiàn)了在 Linux 內(nèi)核設(shè)計(jì)中策略和機(jī)制分離的原則。如下圖所示。從圖中,我們可以看到DeviceMapper只是一個(gè)框架,在這個(gè)框架上,我們可以插入各種各樣的策略(讓我不自然地想到了面向?qū)ο笾械牟呗阅J?,在這諸多“插件”中,有一個(gè)東西叫Thin Provisioning Snapshot,這是Docker使用DeviceMapper中最重要的模塊。

(圖片來源:http://people.redhat.com/agk/talks/FOSDEM_2005/)
Thin Provisioning 簡介
Thin Provisioning要怎么翻譯成中文,真是一件令人頭痛的事,我就不翻譯了。這個(gè)技術(shù)是虛擬化技術(shù)中的一種。它是什么意思呢?你可以聯(lián)想一下我們計(jì)算機(jī)中的內(nèi)存管理中用到的——“虛擬內(nèi)存技術(shù)”——操作系統(tǒng)給每個(gè)進(jìn)程N(yùn)多N多用不完的內(nèi)址地址(32位下,每個(gè)進(jìn)程可以有最多2GB的內(nèi)存空間),但是呢,我們知道,物理內(nèi)存是沒有那么多的,如果按照進(jìn)程內(nèi)存和物理內(nèi)存一一映射來玩的話,那么,我們得要多少的物理內(nèi)存啊。所以,操作系統(tǒng)引入了虛擬內(nèi)存的設(shè)計(jì),意思是,我邏輯上給你***多的內(nèi)存,但是實(shí)際上是實(shí)報(bào)實(shí)銷,因?yàn)槲抑滥阋欢ㄓ貌涣四敲炊?,于是,達(dá)到了內(nèi)存使用率提高的效果。(今天云計(jì)算中很多所謂的虛擬化其實(shí)完全都是在用和“虛擬內(nèi)存”相似的Thin Provisioning的技術(shù),所謂的超配,或是超賣)
好了,話題拉回來,我們這里說的是存儲(chǔ)??聪旅鎯蓚€(gè)圖(圖片來源),***個(gè)是Fat Provisioning,第二個(gè)是Thin Provisioning,其很好的說明了是個(gè)怎么一回事(和虛擬內(nèi)存是一個(gè)概念)


那么,Docker是怎么使用Thin Provisioning這個(gè)技術(shù)做到像UnionFS那樣的分層鏡像的呢?答案是,Docker使用了Thin Provisioning的Snapshot的技術(shù)。下面我們來介紹一下Thin Provisioning的Snapshot。
#p#
Thin Provisioning Snapshot 演示
下面,我們用一系列的命令來演示一下Device Mapper的Thin Provisioning Snapshot是怎么玩的。
首先,我們需要先建兩個(gè)文件,一個(gè)是data.img,一個(gè)是meta.data.img:
- ~hchen$ 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
- ~hchen$ 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選項(xiàng),其表示為略過of選項(xiàng)指定的輸出文件的前10G個(gè)output的 bloksize的空間后再寫入內(nèi)容。因?yàn)閎s是1個(gè)字節(jié),所以也就是10G的尺寸,但其實(shí)在硬盤上是沒有占有空間的,占有空間只有1k的內(nèi)容。當(dāng)向其寫入內(nèi)容時(shí),才會(huì)在硬盤上為其分配空間。我們可以用ls命令看一下,實(shí)際分配了12K和4K。
- ~hchen$ sudo ls -lsh /tmp/data.img
- 12K -rw-r--r--. 1 root root 11G Aug 25 23:01 /tmp/data.img
- ~hchen$ sudo ls -slh /tmp/meta.data.img
- 4.0K -rw-r--r--. 1 root root 101M Aug 25 23:17 /tmp/meta.data.img
然后,我們?yōu)檫@個(gè)文件創(chuàng)建一個(gè)loopback設(shè)備。
- ~hchen$ sudo losetup /dev/loop2015 /tmp/data.img
- ~hchen$ sudo losetup /dev/loop2015 /tmp/meta.data.img
- ~hchen$ sudo losetup -a
- /dev/loop2015: [64768]:103991768 (/tmp/data.img)
- /dev/loop2016: [64768]:103991765 (/tmp/meta.data.img)
現(xiàn)在,我們?yōu)檫@個(gè)設(shè)備建一個(gè)Thin Provisioning的Pool,用dmsetup命令:
-  ~hchen$ sudo dmsetup create hchen-thin-pool \
- --table "0 20971522 thin-pool /dev/loop2016 /dev/loop2015 \
- 128 65536 1 skip_block_zeroing"
其中的參數(shù)解釋如下(更多信息可參看Thin Provisioning的man page):
dmsetup create是用來創(chuàng)建thin pool的命令
hchen-thin-pool 是自定義的一個(gè)pool名,不沖突就好。
–table是這個(gè)pool的參數(shù)設(shè)置
- 0代表起的sector位置
- 20971522代碼結(jié)句的sector號(hào),前面說過,一個(gè)sector是512字節(jié),所以,20971522個(gè)正好是10GB
- /dev/loop2016是meta文件的設(shè)備(前面我們建好了)
- /dev/loop2015是data文件的設(shè)備(前面我們建好了)
- 128是最小的可分配的sector數(shù)
- 65536是最少可用sector的water mark,也就是一個(gè)threshold
- 1 代表有一個(gè)附加參數(shù)
- skip_block_zeroing是個(gè)附加參數(shù),表示略過用0填充的塊
然后,我們就可以看到一個(gè)Device Mapper的設(shè)備了:
- ~hchen$ sudo ll /dev/mapper/hchen-thin-pool
- lrwxrwxrwx. 1 root root 7 Aug 25 23:24 /dev/mapper/hchen-thin-pool -> ../dm-4
接下來,我們的初始還沒有完成,還要?jiǎng)?chuàng)建一個(gè)Thin Provisioning 的 Volume:
- ~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_thin 0"
- ~hchen$ sudo dmsetup create hchen-thin-volumn-001 \
- --table "0 2097152 thin /dev/mapper/hchen-thin-pool 0"
其中:
- ***個(gè)命令中的create_thin是關(guān)鍵字,后面的0表示這個(gè)Volume的device 的 id
- 第二個(gè)命令,是真正的為這個(gè)Volumn創(chuàng)建一個(gè)可以mount的設(shè)備,名字叫hchen-thin-volumn-001。2097152只有1GB
好了,在mount前,我們還要格式化一下:
- ~hchen$ 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了(下面的命令中,我還創(chuàng)建了一個(gè)文件)
- ~hchen$ sudo mkdir -p /mnt/base
- ~hchen$ sudo mount /dev/mapper/hchen-thin-volumn-001 /mnt/base
- ~hchen$ sudo echo "hello world, I am a base" > /mnt/base/id.txt
- ~hchen$ sudo cat /mnt/base/id.txt
- hello world, I am a base
好了,接下來,我們來看看snapshot怎么搞:
- ~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_snap 1 0"
- ~hchen$ sudo dmsetup create mysnap1 \
- --table "0 2097152 thin /dev/mapper/hchen-thin-pool 1"
- ~hchen$ sudo ll /dev/mapper/mysnap1
- lrwxrwxrwx. 1 root root 7 Aug 25 23:49 /dev/mapper/mysnap1 -> ../dm-5
上面的命令中:
- ***條命令是向hchen-thin-pool發(fā)一個(gè)create_snap的消息,后面跟兩個(gè)id,***個(gè)是新的dev id,第二個(gè)是要從哪個(gè)已有的dev id上做snapshot(0這個(gè)dev id是我們前面就創(chuàng)建了了)
- 第二條命令是創(chuàng)建一個(gè)mysnap1的device,并可以被mount。
下面我們來看看:
- ~hchen$ sudo mkdir -p /mnt/mysnap1
- ~hchen$ sudo mount /dev/mapper/mysnap1 /mnt/mysnap1
- ~hchen$ 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
- ~hchen$ sudo cat /mnt/mysnap1/id.txt
- hello world, I am a base
我們來修改一下/mnt/mysnap1/id.txt,并加上一個(gè)snap1.txt的文件:
- ~hchen$ sudo echo "I am snap1" >> /mnt/mysnap1/id.txt
- ~hchen$ sudo echo "I am snap1" > /mnt/mysnap1/snap1.txt
- ~hchen$ sudo cat /mnt/mysnap1/id.txt
- hello world, I am a base
- I am snap1
- ~hchen$ sudo cat /mnt/mysnap1/snap1.txt
- I am snap1
我們再看一下/mnt/base,你會(huì)發(fā)現(xiàn)沒有什么變化:
- ~hchen$ sudo ls /mnt/base
- id.txt lost+found
- ~hchen$ sudo cat /mnt/base/id.txt
- hello world, I am a base
你是不是已經(jīng)看到了分層鏡像的樣子了?
你還要吧繼續(xù)在剛才的snapshot上再建一個(gè)snapshot
- ~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 "create_snap 2 1"
- ~hchen$ sudo dmsetup create mysnap2 \
- --table "0 2097152 thin /dev/mapper/hchen-thin-pool 2"
- ~hchen$ sudo ll /dev/mapper/mysnap2
- lrwxrwxrwx. 1 root root 7 Aug 25 23:52 /dev/mapper/mysnap1 -> ../dm-7
- ~hchen$ sudo mkdir -p /mnt/mysnap2
- ~hchen$ sudo mount /dev/mapper/mysnap2 /mnt/mysnap2
- ~hchen$ sudo ls /mnt/mysnap2
- id.txt lost+found snap1.txt
好了,我相信你看到了分層鏡像的樣子了。
看完演示,我們再來補(bǔ)點(diǎn)理論知識(shí)吧:
- Snapshot來自LVM(Logic Volumn Manager),它可以在不中斷服務(wù)的情況下為某個(gè)device打一個(gè)快照。
- Snapshot是Copy-On-Write的,也就是說,只有發(fā)生了修改,才會(huì)對(duì)對(duì)應(yīng)的內(nèi)存進(jìn)行拷貝。
另外,這里有篇文章Storage thin provisioning benefits and challenges可以前往一讀。
#p#
Docker的DeviceMapper
上面基本上就是Docker的玩法了,我們可以看一下docker的loopback設(shè)備:
- ~hchen $ 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
- ~hchen $ 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
下面是相關(guān)的thin-pool。其中,有個(gè)當(dāng)一大串hash串的device是正在啟動(dòng)的容器:
- ~hchen $ 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(Docker都把它們記下來了):
- ~hchen $ 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 個(gè) sector,我們用這些信息來做個(gè)snapshot看看(注:我用了一個(gè)比較大的dev id – 1024):
- ~hchen$ sudo dmsetup message "/dev/mapper/docker-253:0-104108535-pool" 0 \
- "create_snap 1024 24"
- ~hchen$ sudo dmsetup create dockersnap --table \
- "0 20971520 thin /dev/mapper/docker-253:0-104108535-pool 1024"
- ~hchen$ sudo mkdir /mnt/docker
- ~hchen$ sudo mount /dev/mapper/dockersnap /mnt/docker/
- ~hchen$ sudo ls /mnt/docker/
- id lost+found rootfs
- ~hchen$ 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命令也可以看到相關(guān)的mount的情況(因?yàn)樘L,下面只是摘要):
- # 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]
Device Mapper 行不行?
Thin Provisioning的文檔中說,這還處理實(shí)驗(yàn)階段,不要上Production.
"These targets are very much still in the EXPERIMENTAL state. Please do not yet rely on them in production."
另外,Jeff Atwood在Twitter上發(fā)過這樣的一推

這個(gè)推指向的這個(gè)討論中,其中指向了這個(gè)code diff,基本上就是說,DeviceMapper這種東西問題太多了,我們應(yīng)該把其加入黑名單。Doker的Founder也這樣回復(fù)到:

所以,如果你在使用loopback的devicemapper的話,當(dāng)你的存儲(chǔ)出現(xiàn)了問題后,正確的解決方案是:
rm -rf /var/lib/docker