# 第11章 Docker存儲引擎
使用Docker的最大的好處之一在于它可以從一個現有的鏡像快速的實例化一個新的容器。作為Docker的前身,歷史上的[LXC](https://linuxcontainers.org)容器,它的做法是將會為每個新創建的容器分配宿主機上一個單獨的目錄,然后將鏡像的根文件系統復制到該目錄下。這顯然是相當低效的。在這種情況下,磁盤空間的消耗會隨著每一個新創建的容器而不斷的增長,并且容器的啟動時間也取決于從一個目錄復制到另一個目錄下的數據量的大小。與之不同的是,Docker利用**鏡像分層技術**來解決這些難題。
從整體上來講,鏡像層就是一個簡單的文件樹結構,它可以按需掛載和更改。新的鏡像層可以是全新的也可以基于現有的鏡像即所謂的**父鏡像層**之上創建。基于一個現有鏡像層創建出來的新的鏡像層實際上算是它的一個副本——他們兩個都可以被Docker根據一個相同且唯一的名字所定位。而一旦這個新的鏡像層發生更改,那么Docker將會為它立馬生成和分配一個新的唯一的名字。從這一刻起,父鏡像層將會保持不變,而未來對該鏡像做出的任何更改都只會應用到新的這一層。如果這讓你想到了著名的[寫時復制](https://en.wikipedia.org/wiki/Copy-on-write)(CoW)機制,那么也許你會立刻恍然大悟!
Docker這一鏡像分層技術的實現有賴于眾多的寫時復制文件系統的支持,其中有一些已經內置到了原生的Linux內核里。與之前直接復制父鏡像的做法不同的是,Docker只關注父鏡像及基于它所創建的新的鏡像層之間的變更內容(又稱為**增量**)。這樣一來便節省了大量的磁盤空間。整個鏡像文件系統主要的增長點只在于各鏡像層之間的增量的大小。
Docker在容器的存儲管理方面采取了類似的概念。每一個容器都分為以下兩層。
- **初始層**——基于父鏡像的基礎鏡像層。它包含了每個Docker容器都會出現的一些基本文件:`/etc/hosts`、`/etc/resolv.conf`等。
- **容器文件系統層**——初始層之上的鏡像層。它包含容器本身存儲的一些數據。
Docker通過它所提供的遠程API對外公開了它的鏡像分層,這里面也提供了一些比較貼心的功能,例如,用戶可以實現容器的版本控制以及可以為鏡像打上標簽等。如果用戶想從一個現有的容器保存出一個新的鏡像層,只需要簡單的調用`docker commit container_id`命令,隨后Docker便會自動去定位自**初始層**起一路到該容器層所應用的所有變更,然后在父鏡像層之上創建一個新的分層。用戶也可以為剛提交的這一分層打上一個標簽,要么據此構建新鏡像,又或者是自此實例化新的容器。由于在容器文件系統上應用了相同的寫時復制概念,Docker容器的啟動時間也因此大大縮短。
一圖勝千言——如果讀者有興趣想挖掘一下存儲在Docker Hub里的任一Docker鏡像對應具體的鏡像分層,可以試試由[Centurylink實驗室](https://labs.ctl.io)研發的一個非常棒的[Image Layers](https://imagelayers.io)工具,它使用戶可以手動檢查該鏡像具體可用的分層,而它實際提供的功能還遠不止這些。
到目前為止,上述所講的內容已經覆蓋了關于Docker如何處理鏡像和容器文件系統必備的基礎知識,接下來,我們將探討所有Docker原生支持的存儲引擎。我們將深入剖析里面的一些核心概念,從而讓讀者能夠更好的理解其中的原理,并且我們還會提供一些實際的例子,這里面的每條命令讀者都可以在自己的Docker宿主機上一個個地直接執行。
Docker原生提供了不少開箱即用的存儲引擎。用戶需要做的只是選用其中之一罷了。一旦決定了要使用哪款引擎,用戶就需要在環境變量`DOCKER_OPTS`里追加一個`--storage-driver`命令行參數來告知Docker守護進程。跟其他的服務一樣,用戶必須重啟一次Docker守護進程來使得新的配置參數生效。下面,我們的探索之旅將首先從Docker默認的存儲引擎`aufs`開始。
就像之前所提到的那樣,`aufs`是Docker提供的默認的存儲引擎。選擇它的**部分原因**在于Docker團隊最開始在[dotCloud](https://www.dotcloud.com)內部便是使用它來運行的容器,因此他們對于如何在生產環境下應用它已經有了一個比較堅實的理論基礎和運維經驗。
顧名思義(該引擎的正式名稱尚且待定),`aufs`使用[AUFS](http://aufs.sourceforge.net)文件系統來存儲鏡像和容器。AUFS的工作原理是通過層層“堆疊”多個稱為**分支**的文件系統層,然后每一層都對外公開一個單獨的掛載點以使用戶可以獨立訪問它們。每個分支都是一個簡單的目錄,里面包含一些普通的文件和元數據。最上層的分支則是**唯一**的一個可讀寫的文件層。AUFS正是靠元數據在所有堆疊的鏡像層之間查找和定位文件的具體位置。它每一次的查找操作總是先從最頂層開始,而當某個文件需要做讀寫相關的操作時該文件也將會被復制到最頂層。一旦這個文件本身很大的時候,這類操作所耗費的時間可能就會比較長。
理論就先講到這里。接下來,我們來看一個具體的實戰例子,它將為我們展示Docker是如何運用AUFS存儲引擎的。首先,先確認一下Docker是否的確配置了`aufs`為存儲引擎:
```
# sudo docker info
Containers: 10
Images: 60
Storage Driver: aufs
Root Dir: /var/lib/docker/aufs
Backing Filesystem: extfs
Dirs: 80
Execution Driver: native-0.2
Kernel Version: 3.13.0-40-generic
Operating System: Ubuntu 14.04.1 LTS
CPUs: 1
Total Memory: 490 MiB
Name: docker-hacks
ID:DK4P:GBM6:NWWP:VOWT:PNDF:A66E:B4FZ:XMXA:LSNB:JLGB:TUOL:J3IH
```
正如我們所看到的,AUFS引擎默認的基礎鏡像存儲目錄便是`/var/lib/docker/aufs`。讓我們一起來看看這個目錄里包含了哪些內容:
```
# ls -l /var/lib/docker/aufs/
total 36
drwxr-xr-x 82 root root 12288 Apr 6 15:29 diff
drwxr-xr-x 2 root root 12288 Apr 6 15:29 layers
drwxr-xr-x 82 root root 12288 Apr 6 15:29 mnt
```
如果用戶還沒有創建任何容器,那么所有相關的目錄都將是空的。顧名思義,`mnt`子目錄里面包含的內容便是所有容器文件系統的掛載點。他們只會在容器運行的時候才會被掛載上。既然如此,讓我們先試試創建一個新容器,然后實際來看看它里面生成的內容吧。我們將在一個新的Docker容器里運行一個`top`命令,這樣一來它便會一直保持運行的狀態,直到我們主動停止它:
```
# docker run -d busybox top
Unable to find image 'busybox:latest' locally
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
ea13149945cb: Pull complete
4986bf8c1536: Pull complete
busybox:latest: The image you are pulling has been verified. Im-
portant: image verification is a tech preview feature and
should not be relied on to provide security.
Status: Downloaded newer image for busybox:latest
f534838e081ea8c3fc6c76aa55a719629dccbf7d628535a88be0b3996574fa47
```
從上面的輸出可以看到,`busybox`鏡像由5個鏡像層組成,它們分別對應了5個AUFS分支。AUFS分支的數據則存放在`diff`目錄,用戶可以很輕松的通過如下命令來驗證該`diff`目錄下的每個子目錄對應的鏡像層:
```
# ls -1 /var/lib/docker/aufs/diff/
511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
df7546f9f060a2268024c8a230d8639878585defcc1bc6f79d2728a13957871b
ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2
4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125
f534838e081ea8c3fc6c76aa55a719629dccbf7d628535a88be0b3996574fa47
f534838e081ea8c3fc6c76aa55a719629dccbf7d628535a88be0b3996574fa47-init
```
讀者也可以看到當我們啟動該容器時,像本章開頭講述的那樣,**初始鏡像層**在最上層最先被創建出來。如今,該容器已經處于運行狀態,它的文件系統也應該被掛載上,讓我們一起來確認一下:
```
# grep f534838e081e /proc/mounts
/var/lib/docker/aufs/mnt/
f534838e081ea8c3fc6c76aa55a719629dccbf7d628535a88be0b3996574fa47
aufs rw,relatime,si=fa8a65c73692f82b 0 0
```
該運行中的容器文件系統的掛載點會被映射到`/var/lib/docker/aufs/mnt/container_id`并且它會被掛載成讀-寫模式。我們不妨試試修改該容器的文件系統,在它里面創建一個簡單的文件(`/etc/test`)然后將這個修改提交:
```
# docker exec -it f534838e081e touch /etc/test
# docker commit f534838e081e
4ff22ae4060997f14703b49edd8dc1938438f1ce73070349a4d4413d16a284e2
```
上述操作應該會創建一個新的鏡像層,它將包含剛剛我們創建的新文件并且會把該增量存放到一個特定的diff目錄。這一點可以很方便的通過列出`diff`目錄下的子目錄里的內容來確認:
```
# find /var/lib/docker/aufs/diff/
4ff22ae4060997f14703b49edd8dc1938438f1ce73070349a4d4413d16a284e2/
-type f
/var/lib/docker/aufs/diff/
4ff22ae4060997f14703b49edd8dc1938438f1ce73070349a4d4413d16a284e2/
etc/test
```
現在,可以基于剛創建的包含`/etc/test`這個文件的鏡像層啟動一個新容器:
```
# docker run -d
4ff22ae4060997f14703b49edd8dc1938438f1ce73070349a4d4413d16a284e2
top
9ce0bef93b3ac8c3d37118c0cff08ea698c66c153d78e0d8ab040edd34bc0ed9
# docker ps -q
9ce0bef93b3a
f534838e081e
```
可以通過如下命令來確認該文件是否的確存在于新創建的這個容器內:
```
# docker exec -it 9ce0bef93b3a ls -l /etc/test
-rw-r--r-- 1 root root 0 Apr 7 00:27 /etc/
test
```
那么,讓我們再來看看如果刪除容器里的一個文件并且提交這一更改會發生什么:
```
# docker exec -it 9ce0bef93b3a rm /etc/test
# docker commit 9ce0bef93b3a
e3b7c789792da957c4785190a5044a773c972717f6c2ba555a579ee68f4a4472
```
當刪除一個文件時,AUFS會創建一個所謂的“**寫出**”文件,基本上就是一個重命名的加上“`.wh.`”前綴的文件。這便是AUFS將文件**標記**為已刪除的方式。這一點同樣也非常容易驗證,只需要檢索對應鏡像層目錄里的內容:
```
ls -a /var/lib/docker/aufs/diff/
e3b7c789792da957c4785190a5044a773c972717f6c2ba555a579ee68f4a4472/
etc/
. .. .wh.test
```
該隱藏文件實際上仍然存放在宿主機的文件系統上,但是當用戶基于這一創建的鏡像層啟動一個新容器時,AUFS會非常智能地將其剔除,而這個文件將不會再出現在新運行的容器的文件系統里:
```
# docker run --rm -it e3b7c789792da957c4785190a5044a773c972717f6c2ba555a579ee68f4a4472
test -f /etc/.wh.test || echo "File does not exist"
File does not exist
```
以上便是我們介紹的`aufs`存儲引擎的全部內容。在這里,我們一起探討了Docker是如何利用AUFS文件系統所提供的一些功能特性來創建的容器并且展示了一些實戰案例。下面我們對這一節做一個簡單的總結,然后轉到下一個存儲引擎的介紹。
AUFS的掛載速度是相當快的,因此它們能夠非常快速的創建出新容器。它們的讀/寫速度也幾乎跟原生的差不了多少。這使得它成為眾多運行容器的Docker存儲引擎里一個比較合適和成熟的方案。AUFS的性能瓶頸主要在于需要寫入大文件的場景,因此使用aufs存儲引擎來存放數據庫文件可能不是一個好主意。同樣地,太多的鏡像層可能會導致文件查找時間過長,因此最好不要讓自己的容器有太多的分層。
雖然用戶可以通過一些變通手段來解決先前所提到的一些不足之處(如用卷來掛載數據目錄和減少鏡像層數),但是aufs存儲引擎的最大的問題還是在于AUFS文件系統本身還沒有被容納到主流的Linux內核版本里,而且以后也不太可能。這就是說它不大可能會出現在主流的Linux發行版中,而它在使用上就可能需要用到一些黑科技,這對于用戶而言實在是不太方便,并且由于它沒有被內置到內核里,如何將其更新補丁應用到Linux內核也是一件令人頭疼的事情。即便是曾經在他們的內核里加入了對AUFS的默認支持的Ubuntu,如今也決定了在12.04版本里[禁用](https://lists.ubuntu.com/archives/ubuntu-devel/2012-February/034850.html)這一特性,并且明確鼓勵用戶遷移到[OverlayFS](https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt),該文件系統自11.10版本起被內嵌到了Ubuntu的內核里,而且仍然在積極地迭代更新。關于OverlayFS,我們將在這一章的稍后部分詳細討論。現在,讓我們來一起看看另外一個建立在相對成熟的Linux存儲技術基礎上的存儲引擎`devicemapper`。
DeviceMapper是一個由Linux內核提供的先進的存儲框架,它能夠將物理塊設備映射為虛擬塊設備。它也是[LVM2](http://sourceware.org/lvm2/),塊級別存儲加密,[多路徑](https://en.wikipedia.org/wiki/Linux_DM_Multipath)以及許多其他的Linux存儲工具等諸多技術實現的基礎。用戶可以在Linux內核的[官方文檔](https://www.kernel.org/doc/Documentation/device-mapper/)里獲取更多有關DeviceMapper的信息。在這一小節里,我們將把重點放在Docker是如何使用DeviceMapper來管理容器以及鏡像的存儲。
`devicemapper`存儲引擎使用了DeviceMapper的預分配(thin provisioning[\[1\]](part0017.xhtml#anchor111))模塊來實現鏡像的分層。從整體上來說,預分配機制(也被稱之為thinp)會對外提供一組原始的物理存儲(塊),用戶可以據此創建任意大小的虛擬塊設備或是虛擬磁盤。thinp技術比較神奇的一點在于直到用戶實際開始將數據寫到它們里面之前,這些設備不會占用任何實際的磁盤空間,也不會有任何的原始存儲塊會被標記為正在使用。
另外,thinp技術支持創建數據卷的快照功能。用戶也可以據此創建一個現有卷的副本,而新的快照卷將不會占用任何額外的存儲空間。值得再次強調的是,在用戶開始寫入數據之前,它將不會從存儲池里申請任何額外的存儲空間。
預分配技術本身使用兩種塊設備:
- **數據設備**——用作存儲池的設備,一般都很大;
- **元數據設備**——用來存放已創建的卷(包括快照點)的存儲塊和存儲池之間的映射關系等信息。
`devicemapper`存儲引擎的寫時復制技術是基于單個塊設備級別實現的,這和`aufs引擎`基于文件系統層面的實現略有不同。當Docker守護進程啟動時,它會為之自動創建預分配機制正常工作所必需的兩個塊設備:
- 用作存儲池的數據設備;
- 維護元數據的設備。
默認情況下,這些設備都只是一些綁定到回環設備上的[稀疏文件](https://en.wikipedia.org/wiki/Sparse_file)。這些文件大小上一般看上去是100 GB和2 GB,但是因為它們是稀疏文件,因此實際上并不會用去宿主機上太多的磁盤空間。
一個實際的例子可能會更好地幫助理解這些概念。首先,我們需要設置一下環境變量`DOCKER_OPTS`從而告知Docker守護進程采用`devicemapper`存儲引擎作為默認的存儲選項,然后重啟服務。在服務重啟完成之后,我們可以通過如下方式來驗證它現在是否真的使用`devicemapper`作為存儲引擎:
```
# docker info
Containers: 0
Images: 0
Storage Driver: devicemapper
Pool Name: docker-253:1-143980-pool
Pool Blocksize: 65.54 kB
Backing Filesystem: extfs
Data file: /dev/loop0
Metadata file: /dev/loop1
Data Space Used: 305.7 MB
Data Space Total: 107.4 GB
Metadata Space Used: 729.1 kB
Metadata Space Total: 2.147 GB
Udev Sync Supported: false
Data loop file: /var/lib/docker/devicemapper/devicemapper/data
Metadata loop file: /var/lib/docker/devicemapper/devicemapper/
metadata
Library Version: 1.02.82-git (2013-10-04)
Execution Driver: native-0.2
Kernel Version: 3.13.0-40-generic
Operating System: Ubuntu 14.04.1 LTS
CPUs: 1
Total Memory: 490 MiB
Name: docker-book
ID: IZT7:TU36:TNKP:RELL:2Q2J:CA24:OK6Z:A5KZ:HP5Q:WBPG:X4UJ:WB6A
```
讓我們一起來看看`devicemapper`在`/var/lib/docker/devicemapper/`這個目錄下都做了哪些動作:
```
# ls -alhs /var/lib/docker/devicemapper/devicemapper/
total 292M
4.0K drwx------ 2 root root 4.0K Apr 7 20:58 .
4.0K drwx------ 4 root root 4.0K Apr 7 20:58 ..
291M -rw------- 1 root root 100G Apr 7 20:58 data
752K -rw------- 1 root root 2.0G Apr 7 21:07 metadata
```
如上所示,Docker創建的`data`和`metadata`文件只占用了很少的磁盤空間。我們可以通過執行如下命令來確認這兩個文件實際上是否真的被用作了回環設備的后端存儲:
```
# lsblk
NAME MAJ:MIN RM SIZE RO TYPE
MOUNTPOINT
loop0 7:0 0 100G 0 loop
docker-253:1-143980-pool (dm-0) 252:0 0 100G 0 dm
docker-253:1-143980-base (dm-1) 252:1 0 10G 0 dm
loop1 7:1 0 2G 0 loop
docker-253:1-143980-pool (dm-0) 252:0 0 100G 0 dm
docker-253:1-143980-base (dm-1) 252:1 0 10G 0 dm
```
除了為預分配創建必要的稀疏文件之外,Docker守護進程還會在預分配的存儲池上自動創建一個包含一個空白的`ext4`文件系統的**基礎設備**。所有新的鏡像層都是基礎設備的一個快照,這意味著每個容器和鏡像都擁有一個屬于它自己的塊設備。這樣一來,用戶在任何時間點都可以為任意現有鏡像或者容器創建一個新的快照點。基礎設備的默認大小設置是10 GB,這也是一個容器或鏡像的最大空間大小,但是由于使用了預分配機制,它們實際占用空間會小很多。用戶可以很輕松地通過執行如下命令來驗證基礎設備的存在:
```
# dmsetup ls
docker-253:1-143980-base (252:1)
docker-253:1-143980-pool (252:0)
```
讓我們來試試創建一個簡單的容器,然后在里面執行我們在11.1節里做過的類似測試:
```
# docker run -d busybox top
Unable to find image 'busybox:latest' locally
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
ea13149945cb: Pull complete
4986bf8c1536: Pull complete
busybox:latest: The image you are pulling has been verified. Im-
portant: image verification is a tech preview feature and
should not be relied on to provide security.
Status: Downloaded newer image for busybox:latest
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01
```
如果列出`/var/lib/docker/devicemapper/mnt/`目錄下的具體內容,讀者會發現這里面有一列對應每個鏡像層的文件目錄:
```
# ls -1 /var/lib/docker/devicemapper/mnt/
511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
df7546f9f060a2268024c8a230d8639878585defcc1bc6f79d2728a13957871b
ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2
4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01-init
```
這些目錄即是對應特定`devicemapper`鏡像層的掛載點。除非哪個特定的鏡像層被實際掛載,例如,當某個容器正在運行時,用戶會發現它目錄下面的內容全部是空的。同樣地,用戶也可以非常簡單地通過檢查正在運行的容器對應的掛載目錄里的內容來驗證這一點:
```
# docker ps -q
f5a805967279
# grep f5a805967279 /proc/mounts
/dev/mapper/docker-253:1-143980-
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01/
var/lib/docker/devicemapper/mnt/
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01
ext4 rw,relatime,discard,stripe=16,data=ordered 0 0
```
現在,如果用戶列出該目錄下的內容,將能夠看到它里面包含的容器文件系統的具體內容。而一旦用戶停止了該容器,那么它的附加塊設備都將會被卸載而對應掛載點所在的目錄里的內容都會被置為空:
```
# docker stop f5a805967279
f5a805967279
# ls -l /var/lib/docker/devicemapper/mnt/
f5a805967279e0e07c597c0607afe9adb82514d6184f4fe4c24f064e1fda8c01
total 0
```
這就意味著當用戶使用`devicemapper`作為存儲引擎的時候,將不太容易直觀的得到鏡像層之間的差異。
此外,當采用`devicemapper`作為Docker的默認存儲引擎時,它將會使用稀疏文件作為容器和鏡像的后端存儲。這會對性能方面產生顯著的影響。每當一個容器修改一次它的文件系統時,Docker就必須從存儲池里申請一個新的存儲塊,這在采用稀疏文件的情況下可能需要耗費一定的時間。試想下如果同時運行數百個容器并且它們正在修改各自的文件系統時會是怎樣的一個場景吧。
幸運的是,Docker允許用戶通過傳入特定的命令行參數`--storage-opt`來告知Docker守護進程使用真實的塊設備來存儲`devicemapper`的數據和元數據設備。它們是:
- 針對數據塊設備的`dm.datadev`;
- 針對元數據設備的`dm.metadatadev`。
在生產環境下應用`devicemapper`引擎時請記得**一定要**設置成使用真實的塊設備來存儲數據和元數據設備。當運行了很多的容器時這一點尤其重要!
讀者可以轉到https://github.com/docker/docker/tree/master/daemon/graphdriver/devmapper,了解`devicemapper`引擎提供的所有可用的選項。
**提示**:*如果想從*`aufs`*轉到*`devicemapper`*引擎,用戶必須首先通過執行*`docker save`*命令將所有鏡像都對應保存到一個個單獨的tar包里,一旦*`devicemapper`*存儲引擎被Docker守護進程啟用,用戶便可以輕松地使用*`docker load`*命令加載這些已經保存的鏡像。*
正如我們所看到的,`devicemapper`存儲引擎為Docker提供了一個非常有意思的容器存儲的備選方案。如果用戶對[LVM](https://en.wikipedia.org/wiki/Logical_Volume_Manager_%28Linux%29)以及它周邊豐富的工具集非常熟悉,可以很輕松地使用類似的方式來管理Docker的存儲。然而,當用戶決定要使用這個存儲引擎的時候,往往在使用上有一些需要特別注意的地方:
- 使用`devicemapper`引擎至少需要了解`devicemapper`各個子系統的一些基礎運維知識;
- 更改`devicemapper`的任何選項都需要先停止Docker守護進程并且擦除`/var/lib/docker`目錄下的所有內容;
- 正如我們之前所提到的那樣,容器文件系統的大小默認設置為10 GB(可以在守護進程啟動時通過`dm.basesize`參數來修改這一配置);
- 通常很難去擴展一個已經超出其基礎設備大小的運行中的容器的空間;
- 不能輕易的擴展鏡像的空間大小——提交一個大過它基礎設備大小的容器真的不是那么容易。
現在,我們對于如何使用`devicemapper`存儲引擎應該有了一些不錯的想法,那么是時候繼續前行,接著討論下一個可選方案了,下一個存儲引擎的背后是一個在Linux社區長久以來擁有著廣泛影響力(積極的和負面的兼而有之)的文件系統`btrfs`。
即便是一個普通人都能猜到,`btrfs`存儲引擎使用的是[BTRFS文件系統](https://btrfs.wiki.kernel.org/index.php/Main_Page)來實現Docker鏡像層寫時復制的功能。為了進一步理解`btrfs`存儲引擎,讓我們先來了解下BTRFS文件系統所具備的一些功能特性,然后通過一些實戰案例來講解如何在Docker中應用它。
`btrfs`是一個已經內置到Linux內核主干中很長一段時間的疊加文件系統,但是它依然沒有達到一個生產環境文件系統所需的質量或者說成熟度。它設計的初衷是為了用來和Sun公司的[ZFS文件系統](https://en.wikipedia.org/wiki/ZFS)提供的一些功能特性相抗衡。BTRFS具備的一些顯著特性包括:
- 快照;
- 子卷;
- 無間斷地添加或刪除塊設備;
- 透明壓縮。
`btrfs`以**塊**為單位存儲數據。一個塊就是簡單的一段原始存儲,一般大小在1 GB左右,BTRFS即是使用它來存放實際的數據。數據塊一般都是均勻的分布到所有底層的塊設備里。所以,即使物理磁盤上有存儲空間,數據塊仍然可能提前耗盡。當這樣的情況發生時,用戶唯一能做的便是重新調整自己的文件系統,這樣一來它將會從空白或者接近空白的存儲塊里挪走數據從而釋放一些磁盤空間。這一操作過程中間沒有任何的宕機成本。
以上便是我們需要介紹的關于BTRFS的一些理論基礎。現在,讓我們一起來看看`btrfs`存儲引擎的一些實際應用案例。為了能夠使用這一引擎,Docker要求將`/var/lib/docker`目錄掛載到一個BTRFS文件系統上。我們就不講解如何去做的詳細步驟了——這里我們假定用戶已經事先準備好了一個BTRFS分區并且在其上面創建好了`btrfs`引擎所需的特定目錄:
```
# grep btrfs /proc/mounts
/dev/sdb1 /var/lib/docker btrfs rw,relatime,space_cache 0 0
```
現在,需要通過修改環境變量`DOCKER_OPTS`來告知Docker守護進程使用`btrfs`作為其存儲引擎。在守護進程重啟后,用戶可以通過如下命令來查詢當前Docker正在使用的存儲引擎的信息:
```
# docker info
Containers: 0
Images: 0
Storage Driver: btrfs
Execution Driver: native-0.2
Kernel Version: 3.13.0-24-generic
Operating System: Ubuntu 14.04 LTS
CPUs: 1
Total Memory: 490.1 MiB
Name: docker
ID: NQJM:HHFZ:5636:VGNJ:ICQA:FK4U:6A7F:EUDC:VFQL:PJFF:MI7N:TX7L
WARNING: No swap limit support
```
用戶可以通過執行如下命令來檢索BTRFS文件系統對應的一些信息,它可以展示該文件系統使用情況的概要:
```
# btrfs filesystem show /var/lib/docker
Label: none uuid: 1d65647c-b920-4dc5-b2f4-de96f14fe5af
Total devices 1 FS bytes used 14.63MiB
devid 1 size 5.00GiB used 1.03GiB path /dev/sdb1
Btrfs v3.12
# btrfs filesystem df /var/lib/docker
Data, single: total=520.00MiB, used=14.51MiB
System, DUP: total=8.00MiB, used=16.00KiB
System, single: total=4.00MiB, used=0.00
Metadata, DUP: total=255.94MiB, used=112.00KiB
Metadata, single: total=8.00MiB, used=0.00
```
Docker還利用了BTRFS的**子卷**特性。用戶可以在https://lwn.net/Articles/579009/了解更多有關子卷的知識。整體上說,子卷是一個相當復雜的概念,可以簡單地認為它是一個可以通過文件系統頂層子卷訪問的POSIX文件命名空間,或者它也能夠以自己的方式掛載。
每一個新創建的Docker容器都會被分配一個新的BTRFS子卷,并且如果存在父鏡像層,那么它會以父鏡像層子卷的一個快照的形式創建。Docker鏡像同樣如此。我們不妨先創建一個新的容器然后再通過具體的案例深入理解這相關的概念:
```
# docker run -d busybox top
Unable to find image 'busybox:latest' locally
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
ea13149945cb: Pull complete
4986bf8c1536: Pull complete
busybox:latest: The image you are pulling has been verified. Im-
portant: image verification is a tech preview feature and
should not be relied on to provide security.
Status: Downloaded newer image for busybox:latest
86ab6d8602036cadb842d3a030adf2b05598ac0e178ada876da84489c7ebc612
```
用戶可以很方便地通過如下命令來驗證每一層是否真的對應分配了一個新的BTRFS子卷:
```
# btrfs subvolume list /var/lib/docker/
ID 258 gen 9 top level 5 path btrfs/subvolumes/
511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158
ID 259 gen 10 top level 5 path btrfs/subvolumes/
df7546f9f060a2268024c8a230d8639878585defcc1bc6f79d2728a13957871b
ID 260 gen 11 top level 5 path btrfs/subvolumes/
ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2
ID 261 gen 12 top level 5 path btrfs/subvolumes/
4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125
ID 262 gen 13 top level 5 path btrfs/subvolumes/
86ab6d8602036cadb842d3a030adf2b05598ac0e178ada876da84489c7ebc612-init
ID 263 gen 14 top level 5 path btrfs/subvolumes/
86ab6d8602036cadb842d3a030adf2b05598ac0e178ada876da84489c7ebc612
```
用戶可以在任何時間為任意一層鏡像設置快照點。一個快照點等同于一個新的子卷,它和其他的子卷共享它的數據(和元數據)。由于一個快照點便是一個子卷,因此還可以創建快照的快照。一個實際的案例也許可以幫助我們理解的更加透徹。我們將對一個正在運行中的容器做變更然后提交它:
```
# docker exec -it 86ab6d860203 touch /etc/testfile
# docker commit 86ab6d860203
32cb186de0d0890c807873a3126e797964c0117ce814204bcbf7dc143c812a33
```
如預期那樣,被提交的容器在`/var/lib/docker/btrfs/subvolumes/32cb186de0d0890c807873a3126e797964c0117ce814204bcbf7dc143c812a33`目錄里創建了一個新的BTRFS子卷。如果進一步查看這個子卷里的具體內容,會發現它實際上包含了一個完整的鏡像文件系統,而不只是一個增量鏡像。這是由于BTRFS沒有讀寫分層的概念并且也很難去列出不同的快照點之間的差異。關于這一點也許以后會得到改善。
現在,讓我們探討一下當運用`btrfs`存儲引擎作為Docker存儲時還有哪些值得關注的地方。正如之前所提到的,`btrfs`引擎要求將`/var/lib/docker`目錄掛載到BTRFS文件系統上。這樣做的優勢在于可以讓用戶的整個宿主操作系統的其他部分免受潛在的文件系統中斷的影響。在這里,我們也推薦把`/var/lib/docker/vfs/`目錄掛載到像`ext4`或者`vfs`這樣久經考驗的文件系統。
BTRFS文件系統對于磁盤空間不足的問題非常敏感。用戶必須確保能隨時監控存儲塊的使用情況并且在需要的時候不斷地重新調整文件系統。這可能會對運維人員造成一些負擔,尤其是當用戶運行了大量的容器然后必須在宿主機上保留大量容器鏡像的時候。從另一方面來看,用戶也因此得獲一個可以輕松擴展而無需中斷服務的存儲服務。
通過運用它的快照特性,BTRFS的寫時復制功能使得備份容器和鏡像變得超級簡單。但是,話說回來,BTRFS的寫時復制特性并不適用于創建和修改大量小文件的容器,例如數據庫。這會導致經常出現文件系統碎片,因而需要頻繁地進行文件系統的調整。針對那些綁定掛載到高IO類應用的容器的目錄或者卷,用戶可能需要通過禁用它們的寫時復制來避免上述的這些問題。
如果下定決心要刪除Docker的BTRFS子卷,在通過`rm -rf`命令實際刪除底下的目錄內容之前,別忘了先確保已經使用`btrfs subvolume delete subvolume_directory`刪除了對應的子卷,否則這可能會導致文件系統的損壞。這種情況有時候也會在嘗試刪除鏡像或者銷毀容器的時候發生,所以請一定要留意這一點。
綜上所述,`btrfs`存儲引擎最大的優勢和弊端正是BTRFS文件系統本身。它要求使用者在使用方面有充足的操作經驗,而且它在多樣的生產環境下性能不是很好。BTRFS也不允許共享頁面緩存,因為這可能會導致過高的內存使用率。以上這些也正是為什么[CoreOS](https://coreos.com)[團隊最近](https://lwn.net/Articles/627232/)決定在他們的操作系統發行版里放棄內置對btrfs的支持的主要原因。
`overlay`是Docker最新引入的存儲引擎。它使用[OverlayFS](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/Documentation/filesystems/overlayfs.txt)文件系統來為Docker鏡像分層提供寫時復制的支持。和之前一樣,需要將環境變量`DOCKER_OPTS`修改為指向該引擎,然后重啟Docker守護進程。在深入講解一些實際案例之前,讓我們先來介紹一下OverlayFS文件系統。
OverlayFS是一個聯合文件系統,Linux內核在3.18版本起就已經內置了對此文件系統的支持。它實際上結合了兩種文件系統:
- **上層**——子文件系統;
- **下層**——父文件系統。
OverlayFS引入了**工作目錄**的概念,所謂的工作目錄指的便是一個駐留在上層文件系統的目錄,而它通常用于完成上、下層之間文件的原子復制。
下層文件系統可以是任意一個Linux內核(包括overlayFS本身)支持的文件系統并且通常都是**只讀**的。事實上,我們可以通過層層堆疊或者說互相在彼此的頂部“疊加”來劃分出多個下層文件系統層。上層文件系統一般是可寫的。作為一個聯合文件系統,overlayFS也實現了通過經典的“寫出”文件來標記將要刪除的文件,這與我們在第一節里講述的AUFS文件系統的做法有些類似。
疊加的主要操作便是目錄的**合并**。每個目錄樹下相同路徑的兩個文件將會被合并到疊加后的文件系統里的相同目錄下。與之前容器的鏡像層數越多,AUFS需要耗費的查找時間則越長的情況相比,OverlayFS在查找效率方面做出了不小的改進。每當有針對合并目錄的查找請求時,它會在被合并的目錄中同時進行查找,然后將最終的結果**緩存**到疊加文件系統的固定條目里。如果它們均找到對應的文件,那么這些查找記錄會被保存下來,并且會為此創建一個新的合并目錄將這些相同的文件合并,否則便**只有其中一個會被保存**:如果上層查找得到便是上層,否則便是下層。
那么,Docker是如何使用OverlayFS的呢?它將下層的文件系統作為基礎鏡像層,當創建一個新的Docker容器時,它會自動為之創建一個新的只包含兩個鏡像層之間增量的上層文件系統。這和我們之前介紹的AUFS的做法簡直一模一樣。正如我們預期的那樣,提交一個容器所創建的新的鏡像層同樣也只是包含基礎鏡像層和新鏡像層之間的差異而已。
好了,理論就先講到這里。接下來,讓我們來看一個具體案例。在此之前,為了滿足Overlayfs的基本使用條件,用戶的Linux內核必須是3.18或以上版本。如果用戶的內核版本比這個低,Docker將會選擇下一個可用的存儲引擎,而不使用`overlay`。
與之前一樣,用戶需要告知Docker守護進程采用`overlay`存儲引擎。記住,存儲引擎的名字**是`overlay`**而不是**`overlayfs`**:
```
# docker info
Containers: 0
Images: 0
Storage Driver: overlay
Backing Filesystem: extfs
Execution Driver: native-0.2
Kernel Version: 3.18.0-031800-generic
Operating System: Ubuntu 14.04 LTS
CPUs: 1
Total Memory: 489.2 MiB
Name: docker
ID: NQJM:HHFZ:5636:VGNJ:ICQA:FK4U:6A7F:EUDC:VFQL:PJFF:MI7N:TX7L
WARNING: No swap limit support
```
現在,讓我們創建一個新容器,隨后看下`/var/lib/docker`目錄里的內容:
```
# docker run -d busybox top
Unable to find image 'busybox:latest' locally
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
ea13149945cb: Pull complete
4986bf8c1536: Pull complete
busybox:latest: The image you are pulling has been verified. Important:
image verification is a tech preview feature and
should not be relied on to provide security.
Status: Downloaded newer image for busybox:latest eb9e1a68c70532ecd31e20d8ca4b7f598d2259d1ac8accaa02090f32ee0b95c1
```
所有的鏡像層都如預期那樣出現在了`/var/lib/docker/overlay`目錄里。查找基礎鏡像和容器鏡像層實際上和檢索掛載好的該容器文件系統一樣簡單,而且用戶可以在這里看到**下層**和**上層**目錄里的內容:
```
# grep eb9e1a68c705 /proc/mounts
overlay /var/lib/docker/overlay/
eb9e1a68c70532ecd31e20d8ca4b7f598d2259d1ac8ac-
caa02090f32ee0b95c1/merged overlay
rw,relatime,lowerdir=/var/lib/docker/overlay/
4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125/
root,upperdir=/var/lib/docker/overlay/
eb9e1a68c70532ecd31e20d8ca4b7f598d2259d1ac8ac-
caa02090f32ee0b95c1/upper,workdir=/var/lib/docker/overlay/
eb9e1a68c70532ecd31e20d8ca4b7f598d2259d1ac8ac-
caa02090f32ee0b95c1/work 0 0
```
和我們測試之前的存儲引擎一樣,我們將會在一個正在運行的容器里創建一個空文件然后提交,而這會立刻觸發創建一個新的鏡像層:
```
# docker exec -it eb9e1a68c705 touch /etc/testfile
# docker ps
CONTAINER ID IMAGE COMMAN
CREATED STATUS PORTS
NAMES
eb9e1a68c705 busybox:latest "top" 9
minutes ago Up 9 minutes
cocky_bohr
# docker commit eb9e1a68c705
eae6654d10c2c6467e975a80559dcc2dd57178baaea57dd8d347c950a46c404b
```
用戶可以簡單地通過如下命令來確認它是否已經創建好新的鏡像層以及里面是否包含我們剛剛創建的那個新的空白文件:
```
# ls -l /var/lib/docker/overlay/
eae6654d10c2c6467e975a80559dcc2dd57178baaea57dd8d347c950a46c404b/
root/etc/testfile
-rw-r--r-- 1 root root 0 Apr 8 21:44 /var/lib/docker/overlay/
eae6654d10c2c6467e975a80559dcc2dd57178baaea57dd8d347c950a46c404b/
root/etc/testfile
```
上述所有結果完全符合我們的預期,而它最終的表現和`aufs`存儲引擎所做的幾乎完全一樣。但是,這里面也有些許的不同。`overly`這里沒有`diff`子目錄。然而這并不意味著我們就沒有辦法檢索文件系統的增量——它們只是被“隱藏”在了某個地方罷了。`overlay`存儲引擎會為每個容器創建3個子目錄:
- `upper`——這個便是可讀寫的上層文件系統層;
- `work`——原子復制所需的臨時目錄;
- `merged`——正在運行中的容器的掛載點。
此外,該引擎會創建一個叫做**lower-id**的文件,這里面會包含父鏡像層的`id`而它的"根"目錄將會被`overlay`當做下層目錄——該文件一般用于存放父鏡像層的查找`id`。
讓我們從之前提交的一個鏡像層啟動一個新容器:
```
# docker run -d
eae6654d10c2c6467e975a80559dcc2dd57178baaea57dd8d347c950a46c404b
007c9ca6bb483474f1677349a25c769ee7435f7b22473305f18cccb2fca21333
```
我們可以很方便地通過查看“lower-id”文件來確認容器的父鏡像層的`id`:
```
# cat /var/lib/docker/overlay/
007c9ca6bb483474f1677349a25c769ee7435f7b22473305f18cccb2fca21333/lower-id
eae6654d10c2c6467e975a80559dcc2dd57178baaea57dd8d347c950a46c404b
```
由于`overlay`存儲引擎和之前評測過的`aufs`引擎有非常多的相似之處,因此這里不再贅述。話說回來,為什么`overlay`引擎能提供如此多令人興奮的特性呢?這里面有幾個重要的原因:
- Linux內核主干內置了對Overlay文件系統的支持——我們不需要再安裝任何額外的內核補丁;
- 因為采用了頁面緩存共享的機制,所以它能夠占用更少的內存資源;
- 雖然它在下層和上層文件系統之間的復制速度方面有一定的問題,但是綜合來說,它依舊比aufs引擎要快上不少;
- 對于鏡像之間相同的文件是以硬鏈接的方式關聯在一起,如此就可以避免重復的覆蓋并且容許更快的刪除/銷毀。
盡管有上述這些優點,但OverlayFS仍舊是一個非常年輕的文件系統。雖然從初步的測試結果來看它是十分理想的,但是我們仍然沒有看到它實際投入生產環境下應用的具體案例。有鑒于此,越來越多像[CoreOS](https://coreos.com)這樣的企業和OverlayFS站到了同一[陣營](http://lwn.net/Articles/627232/),因此我們可以預見到的是未來它會有更多積極的開發和改進。
現在,是時候放下對寫時復制這一特性的迷戀了,接下來我們來看一看最后一個Docker存儲引擎`vfs`,這是一個不提供寫時復制機制的存儲引擎。
就像前面提到的,`vfs`存儲引擎是唯一的一個不采用任何寫時復制機制的存儲引擎。每個鏡像層就是一個單一的目錄。當Docker創建一個新的鏡像層時它所做的便是將基礎鏡像層所在的目錄**全量地物理復制**到新建鏡像的目錄里。這將導致這個引擎運轉非常緩慢并且磁盤空間的利用率也會很低。
讓我們來看一個使用`vfs`的實際案例。和之前一樣,我們首先需要修改一下環境變量`DOCKER_OPTS`,告知Docker守護進程采用`vfs`作為存儲引擎,然后重啟服務:
```
# ps -ef|grep docker
root 2680 1 0 21:51 ? 00:00:02 /usr/bin/docker
-d --storage-driver=vfs
```
我們將通過一個很小的只運行`top`命令的`busybox`容器來講解`vfs`引擎的一些特性。
```
# docker run -d busybox top
...
[FILTERED OUTPUT]
...
de8c6e2684acefa1e84791b163212d92003886ba8cb73eccef4b2c4d167a59a4
```
VFS鏡像層存放在`/var/lib/docker/vfs/dir`目錄里。現在,我們將測試一下使用這一引擎時的一些具體速度和磁盤空間方面的性能表現。我們會通過如下命令啟動一個新容器然后在里面生成一個合適大小的大文件:
```
# docker run -ti busybox /bin/sh
/ # dd if=/dev/zero of=sample.txt bs=200M count=1
1+0 records in
1+0 records out
/ # du -sh sample.txt
200.0M sample.txt
/#
```
緊接著,提交這個容器來觸發Docker創建一個新的鏡像層,并且觀察一下對應的執行速度:
```
# time docker commit 24247ae7c1c0
7f3a2989b52e0430e6372e34399757a47180080b409b94cb46d6cd0a7c46396e
real 0m1.029s
user 0m0.008s
sys 0m0.008s
```
結果表明,它耗費了1秒左右的時間來創建一個新的鏡像層——這要歸咎于基礎鏡像層里我們創建的那個200 MB大小的文件。最后,讓我們從新提交的這一層鏡像中再創建一個新容器,然后再看看它的速度是怎樣的:
```
# time docker run --rm -it
7f3a2989b52e0430e6372e34399757a47180080b409b94cb46d6cd0a7c46396e
echo VFS is slow
VFS is slow
real 0m3.124s
user 0m0.021s
sys 0m0.007s
```
從先前那個提交的鏡像創建一個新容器竟然花了將近3秒!此外,我們擁有的兩個容器在宿主機文件系統上每個均占用了200 MB的磁盤空間!
上述的這個例子已經證明VFS是不太適用于生產環境的。它更像是當宿主機上沒有任何支持寫時復制特性的文件系統的時候的一個備選方案。盡管如此,VFS依舊算是掛載Docker卷的一個不錯的解決方案,原因在于它具備良好的平臺兼容性并且當用戶打算在像FreeBSD這樣的非Linux平臺上運行Docker時它會是一個不錯的選擇。
Docker在存儲引擎方面提供了一個相當全面的選擇。這也許是一個喜憂參半的情況,因為通常初學者一旦知道了有這么多的備選后,可能會困惑到底應該選哪個。Donald Knuth關于“[過早優化](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize)”的名言常常會在我們討論技術選型的時候出現在腦海里。
實際上,人們往往會選擇自己在生產環境里最有把握運維的那個工具。最后,我們以一張簡短的表(表11-1)來結束本章,表中列出了一些在選擇使用哪個存儲引擎之前需要評估的要點。
表11-1
AUFS
OverlayFS
BTRFS
DeviceMapper
上線
非常快
非常快
快
快
小文件I/O
非常快
非常快
快
快
大文件I/O
慢
慢
快
快
內存使用率
高效
高效
高效
不是很高效
缺陷
沒有在內核主干里,有層數限制,隨機并發度的問題
不成熟
接近成熟,但是實際上還不是,存儲擴容會很費勁,需要一些扎實的運維知識
高密度的容器及很高的磁盤占用,周邊生態不好,大部分需要的是運維經驗
Docker在網絡領域同樣帶來了革命性的挑戰,我們將在第12章講解這方面的內容。
- - - - - -
[\[1\]](part0017.xhtml#ac111) 是DeviceMapper提供的一項特性,它允許在實際使用時實報實銷的資源利用,即類似虛擬內存的一項技術。——譯者注
- 版權信息
- 版權聲明
- 內容提要
- 對本書的贊譽
- 譯者介紹
- 前言
- 本書面向的讀者
- 誰真的在生產環境中使用Docker
- 為什么使用Docker
- 開發環境與生產環境
- 我們所說的“生產環境”
- 功能內置與組合工具
- 哪些東西不要Docker化
- 技術審稿人
- 第1章 入門
- 1.1 術語
- 1.1.1 鏡像與容器
- 1.1.2 容器與虛擬機
- 1.1.3 持續集成/持續交付
- 1.1.4 宿主機管理
- 1.1.5 編排
- 1.1.6 調度
- 1.1.7 發現
- 1.1.8 配置管理
- 1.2 從開發環境到生產環境
- 1.3 使用Docker的多種方式
- 1.4 可預期的情況
- 為什么Docker在生產環境如此困難
- 第2章 技術棧
- 2.1 構建系統
- 2.2 鏡像倉庫
- 2.3 宿主機管理
- 2.4 配置管理
- 2.5 部署
- 2.6 編排
- 第3章 示例:極簡環境
- 3.1 保持各部分的簡單
- 3.2 保持流程的簡單
- 3.3 系統細節
- 利用systemd
- 3.4 集群范圍的配置、通用配置及本地配置
- 3.5 部署服務
- 3.6 支撐服務
- 3.7 討論
- 3.8 未來
- 3.9 小結
- 第4章 示例:Web環境
- 4.1 編排
- 4.1.1 讓服務器上的Docker進入準備運行容器的狀態
- 4.1.2 讓容器運行
- 4.2 連網
- 4.3 數據存儲
- 4.4 日志
- 4.5 監控
- 4.6 無須擔心新依賴
- 4.7 零停機時間
- 4.8 服務回滾
- 4.9 小結
- 第5章 示例:Beanstalk環境
- 5.1 構建容器的過程
- 部署/更新容器的過程
- 5.2 日志
- 5.3 監控
- 5.4 安全
- 5.5 小結
- 第6章 安全
- 6.1 威脅模型
- 6.2 容器與安全性
- 6.3 內核更新
- 6.4 容器更新
- 6.5 suid及guid二進制文件
- 6.6 容器內的root
- 6.7 權能
- 6.8 seccomp
- 6.9 內核安全框架
- 6.10 資源限制及cgroup
- 6.11 ulimit
- 6.12 用戶命名空間
- 6.13 鏡像驗證
- 6.14 安全地運行Docker守護進程
- 6.15 監控
- 6.16 設備
- 6.17 掛載點
- 6.18 ssh
- 6.19 私鑰分發
- 6.20 位置
- 第7章 構建鏡像
- 7.1 此鏡像非彼鏡像
- 7.1.1 寫時復制與高效的鏡像存儲與分發
- 7.1.2 Docker對寫時復制的使用
- 7.2 鏡像構建基本原理
- 7.2.1 分層的文件系統和空間控管
- 7.2.2 保持鏡像小巧
- 7.2.3 讓鏡像可重用
- 7.2.4 在進程無法被配置時,通過環境變量讓鏡像可配置
- 7.2.5 讓鏡像在Docker變化時對自身進行重新配置
- 7.2.6 信任與鏡像
- 7.2.7 讓鏡像不可變
- 7.3 小結
- 第8章 存儲Docker鏡像
- 8.1 啟動并運行存儲的Docker鏡像
- 8.2 自動化構建
- 8.3 私有倉庫
- 8.4 私有registry的擴展
- 8.4.1 S3
- 8.4.2 本地存儲
- 8.4.3 對registry進行負載均衡
- 8.5 維護
- 8.6 對私有倉庫進行加固
- 8.6.1 SSL
- 8.6.2 認證
- 8.7 保存/載入
- 8.8 最大限度地減小鏡像體積
- 8.9 其他鏡像倉庫方案
- 第9章 CI/CD
- 9.1 讓所有人都進行鏡像構建與推送
- 9.2 在一個構建系統中構建所有鏡像
- 9.3 不要使用或禁止使用非標準做法
- 9.4 使用標準基礎鏡像
- 9.5 使用Docker進行集成測試
- 9.6 小結
- 第10章 配置管理
- 10.1 配置管理與容器
- 10.2 面向容器的配置管理
- 10.2.1 Chef
- 10.2.2 Ansible
- 10.2.3 Salt Stack
- 10.2.4 Puppet
- 10.3 小結
- 第11章 Docker存儲引擎
- 11.1 AUFS
- 11.2 DeviceMapper
- 11.3 BTRFS
- 11.4 OverlayFS
- 11.5 VFS
- 11.6 小結
- 第12章 Docker 網絡實現
- 12.1 網絡基礎知識
- 12.2 IP地址的分配
- 端口的分配
- 12.3 域名解析
- 12.4 服務發現
- 12.5 Docker高級網絡
- 12.6 IPv6
- 12.7 小結
- 第13章 調度
- 13.1 什么是調度
- 13.2 調度策略
- 13.3 Mesos
- 13.4 Kubernetes
- 13.5 OpenShift
- Red Hat公司首席工程師Clayton Coleman的想法
- 第14章 服務發現
- 14.1 DNS服務發現
- DNS服務器的重新發明
- 14.2 Zookeeper
- 14.3 基于Zookeeper的服務發現
- 14.4 etcd
- 基于etcd的服務發現
- 14.5 consul
- 14.5.1 基于consul的服務發現
- 14.5.2 registrator
- 14.6 Eureka
- 基于Eureka的服務發現
- 14.7 Smartstack
- 14.7.1 基于Smartstack的服務發現
- 14.7.2 Nerve
- 14.7.3 Synapse
- 14.8 nsqlookupd
- 14.9 小結
- 第15章 日志和監控
- 15.1 日志
- 15.1.1 Docker原生的日志支持
- 15.1.2 連接到Docker容器
- 15.1.3 將日志導出到宿主機
- 15.1.4 發送日志到集中式的日志平臺
- 15.1.5 在其他容器一側收集日志
- 15.2 監控
- 15.2.1 基于宿主機的監控
- 15.2.2 基于Docker守護進程的監控
- 15.2.3 基于容器的監控
- 15.3 小結
- DockOne社區簡介
- 看完了