前面課時我分別介紹了 Docker 常見的聯合文件系統解決方案: AUFS 和 Devicemapper。今天我給你介紹一個性能更好的聯合文件系統解決方案—— OverlayFS。
OverlayFS 的發展分為兩個階段。2014 年,OverlayFS 第一個版本被合並到 Linux 內核 3.18 版本中,此時的 OverlayFS 在 Docker 中被稱為overlay文件驅動。由於第一版的overlay文件系統存在很多弊端(例如運行一段時間后Docker 會報 "too many links problem" 的錯誤), Linux 內核在 4.0 版本對overlay做了很多必要的改進,此時的 OverlayFS 被稱之為overlay2。
因此,在 Docker 中 OverlayFS 文件驅動被分為了兩種,一種是早期的overlay,不推薦在生產環境中使用,另一種是更新和更穩定的overlay2,推薦在生產環境中使用。下面的內容我們主要圍繞overlay2展開。
1. 使用 overlay2 的先決條件
overlay2雖然很好,但是它的使用是有一定條件限制的。
- 要想使用overlay2,Docker 版本必須高於 17.06.02。
- 如果你的操作系統是 RHEL 或 CentOS,Linux 內核版本必須使用 3.10.0-514 或者更高版本,其他 Linux 發行版的內核版本必須高於 4.0(例如 Ubuntu 或 Debian),你可以使用uname -a查看當前系統的內核版本。
- overlay2最好搭配 xfs 文件系統使用,並且使用 xfs 作為底層文件系統時,d_type必須開啟,可以使用以下命令驗證 d_type 是否開啟:
$ xfs_info /var/lib/docker | grep ftype naming =version 2 bsize=4096 ascii-ci=0 ftype=1
當輸出結果中有 ftype=1 時,表示 d_type 已經開啟。如果你的輸出結果為 ftype=0,則需要重新格式化磁盤目錄,命令如下:
$ sudo mkfs.xfs -f -n ftype=1 /path/to/disk
另外,在生產環境中,推薦掛載 /var/lib/docker 目錄到單獨的磁盤或者磁盤分區,這樣可以避免該目錄寫滿影響主機的文件寫入,並且把掛載信息寫入到 /etc/fstab,防止機器重啟后掛載信息丟失。
掛載配置中推薦開啟 pquota,這樣可以防止某個容器寫文件溢出導致整個容器目錄空間被占滿。寫入到 /etc/fstab 中的內容如下:
$UUID /var/lib/docker xfs defaults,pquota 0 0
其中 UUID 為 /var/lib/docker 所在磁盤或者分區的 UUID 或者磁盤路徑。
如果你的操作系統無法滿足上面的任何一個條件,那我推薦你使用 AUFS 或者 Devicemapper 作為你的 Docker 文件系統驅動。
通常情況下, overlay2 會比 AUFS 和 Devicemapper 性能更好,而且更加穩定,因為 overlay2 在 inode 優化上更加高效。因此在生產環境中推薦使用 overlay2 作為 Docker 的文件驅動。
下面我通過實例來教你如何初始化 /var/lib/docker 目錄,為后面配置 Docker 的overlay2文件驅動做准備。
1.1 准備 /var/lib/docker 目錄
1.使用 lsblk(Linux 查看磁盤和塊設備信息命令)命令查看本機磁盤信息:
$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 500G 0 disk `-vda1 253:1 0 500G 0 part / vdb 253:16 0 500G 0 disk `-vdb1 253:17 0 8G 0 part
可以看到,我的機器有兩塊磁盤,一塊是 vda,一塊是 vdb。其中 vda 已經被用來掛載系統根目錄,這里我想把 /var/lib/docker 掛載到 vdb1 分區上。
2.使用 mkfs 命令格式化磁盤 vdb1:
$ sudo mkfs.xfs -f -n ftype=1 /dev/vdb1
3.將掛載信息寫入到 /etc/fstab,保證機器重啟掛載目錄不丟失:
$ sudo echo "/dev/vdb1 /var/lib/docker xfs defaults,pquota 0 0" >> /etc/fstab
4.使用 mount 命令使得掛載目錄生效:
$ sudo mount -a
5.查看掛載信息:
$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 253:0 0 500G 0 disk `-vda1 253:1 0 500G 0 part / vdb 253:16 0 500G 0 disk `-vdb1 253:17 0 8G 0 part /var/lib/docker
可以看到此時 /var/lib/docker 目錄已經被掛載到了 vdb1 這個磁盤分區上。我們使用 xfs_info 命令驗證下 d_type 是否已經成功開啟:
$ xfs_info /var/lib/docker | grep ftype naming =version 2 bsize=4096 ascii-ci=0 ftype=1
可以看到輸出結果為 ftype=1,證明 d_type 已經被成功開啟。
准備好 /var/lib/docker 目錄后,我們就可以配置 Docker 的文件驅動為 overlay2,並且啟動 Docker 了。
1.2 如何在 Docker 中配置 overlay2?
當你的系統滿足上面的條件后,就可以配置你的 Docker 存儲驅動為 overlay2 了,具體配置步驟如下。
1.停止已經運行的 Docker:
$ sudo systemctl stop docker
2.備份 /var/lib/docker 目錄:
$ sudo cp -au /var/lib/docker /var/lib/docker.back
3.在 /etc/docker 目錄下創建 daemon.json 文件,如果該文件已經存在,則修改配置為以下內容:
{ "storage-driver": "overlay2", "storage-opts": [ "overlay2.size=20G", "overlay2.override_kernel_check=true" ] }
其中 storage-driver 參數指定使用 overlay2 文件驅動,overlay2.size 參數表示限制每個容器根目錄大小為 20G。限制每個容器的磁盤空間大小是通過 xfs 的 pquota 特性實現,overlay2.size 可以根據不同的生產環境來設置這個值的大小。我推薦你在生產環境中開啟此參數,防止某個容器寫入文件過大,導致整個 Docker 目錄空間溢出。
4.啟動 Docker:
$ sudo systemctl start docker
5.檢查配置是否生效:
$ docker info Client: Debug Mode: false Server: Containers: 1 Running: 0 Paused: 0 Stopped: 1 Images: 1 Server Version: 19.03.12 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs ... 省略部分無用輸出
可以看到 Storage Driver 已經變為 overlay2,並且 d_type 也是 true。至此,你的 Docker 已經配置完成。下面我們看下 overlay2 是如何工作的。
2. overlay2 工作原理
2.1 overlay2 是如何存儲文件的?
overlay2 和 AUFS 類似,它將所有目錄稱之為層(layer),overlay2 的目錄是鏡像和容器分層的基礎,而把這些層統一展現到同一的目錄下的過程稱為聯合掛載(union mount)。overlay2 把目錄的下一層叫作lowerdir,上一層叫作upperdir,聯合掛載后的結果叫作merged。
總體來說,overlay2 是這樣儲存文件的:overlay2將鏡像層和容器層都放在單獨的目錄,並且有唯一 ID,每一層僅存儲發生變化的文件,最終使用聯合掛載技術將容器層和鏡像層的所有文件統一掛載到容器中,使得容器中看到完整的系統文件。
overlay2 文件系統最多支持 128 個層數疊加,也就是說你的 Dockerfile 最多只能寫 128 行,不過這在日常使用中足夠了。
下面我們通過拉取一個 Ubuntu 操作系統的鏡像來看下 overlay2 是如何存放鏡像文件的。
首先,我們通過以下命令拉取 Ubuntu 鏡像:
$ docker pull ubuntu:16.04 16.04: Pulling from library/ubuntu 8e097b52bfb8: Pull complete a613a9b4553c: Pull complete acc000f01536: Pull complete 73eef93b7466: Pull complete Digest: sha256:3dd44f7ca10f07f86add9d0dc611998a1641f501833692a2651c96defe8db940 Status: Downloaded newer image for ubuntu:16.04 docker.io/library/ubuntu:16.04
可以看到鏡像一共被分為四層拉取,拉取完鏡像后我們查看一下 overlay2 的目錄:
$ sudo ls -l /var/lib/docker/overlay2/ total 0 drwx------. 3 root root 47 Sep 13 08:16 01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a drwx------. 4 root root 55 Sep 13 08:16 0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb drwx------. 4 root root 72 Sep 13 08:16 94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f drwx------. 4 root root 72 Sep 13 08:16 9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396 brw-------. 1 root root 253, 17 Sep 13 08:14 backingFsBlockDev drwx------. 2 root root 142 Sep 13 08:16 l
可以看到 overlay2 目錄下出現了四個鏡像層目錄和一個l目錄,我們首先來查看一下l目錄的內容:
$ sudo ls -l /var/lib/docker/overlay2/l total 0 lrwxrwxrwx. 1 root root 72 Sep 13 08:16 FWGSYEA56RNMS53EUCKEQIKVLQ -> ../9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff lrwxrwxrwx. 1 root root 72 Sep 13 08:16 RNN2FM3YISKADNAZFRONVNWTIS -> ../0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff lrwxrwxrwx. 1 root root 72 Sep 13 08:16 SHAQ5GYA3UZLJJVEGXEZM34KEE -> ../01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff lrwxrwxrwx. 1 root root 72 Sep 13 08:16 VQSNH735KNX4YK2TCMBAJRFTGT -> ../94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff
可以看到l目錄是一堆軟連接,把一些較短的隨機串軟連到鏡像層的 diff 文件夾下,這樣做是為了避免達到mount命令參數的長度限制。
下面我們查看任意一個鏡像層下的文件內容:
$ sudo ls -l /var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/ total 8 drwxr-xr-x. 3 root root 17 Sep 13 08:16 diff -rw-r--r--. 1 root root 26 Sep 13 08:16 link -rw-r--r--. 1 root root 86 Sep 13 08:16 lower drwx------. 2 root root 6 Sep 13 08:16 work
鏡像層的 link 文件內容為該鏡像層的短 ID,diff 文件夾為該鏡像層的改動內容,lower 文件為該層的所有父層鏡像的短 ID。
我們可以通過docker image inspect命令來查看某個鏡像的層級關系,例如我想查看剛剛下載的 Ubuntu 鏡像之間的層級關系,可以使用以下命令:
$ docker image inspect ubuntu:16.04 ...省略部分輸出 "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff:/var/lib/docker/overlay2/94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff:/var/lib/docker/overlay2/01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff", "MergedDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/merged", "UpperDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff", "WorkDir": "/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/work" }, "Name": "overlay2" }, ...省略部分輸出
其中 MergedDir 代表當前鏡像層在 overlay2 存儲下的目錄,LowerDir 代表當前鏡像的父層關系,使用冒號分隔,冒號最后代表該鏡像的最底層。
下面我們將鏡像運行起來成為容器:
$ docker run --name=ubuntu -d ubuntu:16.04 sleep 3600
我們使用docker inspect命令來查看一下容器的工作目錄:
$ docker inspect ubuntu ...省略部分輸出 "GraphDriver": { "Data": { "LowerDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2-init/diff:/var/lib/docker/overlay2/0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb/diff:/var/lib/docker/overlay2/9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396/diff:/var/lib/docker/overlay2/94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f/diff:/var/lib/docker/overlay2/01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a/diff", "MergedDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/merged", "UpperDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/diff", "WorkDir": "/var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2/work" }, "Name": "overlay2" }, ...省略部分輸出
MergedDir 的內容即為容器層的工作目錄,LowerDir 為容器所依賴的鏡像層目錄。 然后我們查看下 overlay2 目錄下的內容:
$ sudo ls -l /var/lib/docker/overlay2/ total 0 drwx------. 3 root root 47 Sep 13 08:16 01946de89606800dac8530e3480b32be9d7c66b493a1cdf558df52d7a1476d4a drwx------. 4 root root 72 Sep 13 08:47 0849daa41598a333101f6a411755907d182a7fcef780c7f048f15d335b774deb drwx------. 5 root root 69 Sep 13 08:47 4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2 drwx------. 4 root root 72 Sep 13 08:47 4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2-init drwx------. 4 root root 72 Sep 13 08:16 94222a2fa3b2405cb00459285dd0d0ba7e6936d9b693ed18fbb0d08b93dc272f drwx------. 4 root root 72 Sep 13 08:16 9d392cf38f245d37699bdd7672daaaa76a7d702083694fa8be380087bda5e396 brw-------. 1 root root 253, 17 Sep 13 08:14 backingFsBlockDev drwx------. 2 root root 210 Sep 13 08:47 l
可以看到 overlay2 目錄下增加了容器層相關的目錄,我們再來查看一下容器層下的內容:
$ sudo ls -l /var/lib/docker/overlay2/4753c2aa5bdb20c97cddd6978ee3b1d07ef149e3cc2bbdbd4d11da60685fe9b2 total 8 drwxr-xr-x. 2 root root 6 Sep 13 08:47 diff -rw-r--r--. 1 root root 26 Sep 13 08:47 link -rw-r--r--. 1 root root 144 Sep 13 08:47 lower drwxr-xr-x. 1 root root 6 Sep 13 08:47 merged drwx------. 3 root root 18 Sep 13 08:47 work
link 和 lower 文件與鏡像層的功能一致,link 文件內容為該容器層的短 ID,lower 文件為該層的所有父層鏡像的短 ID 。diff 目錄為容器的讀寫層,容器內修改的文件都會在 diff 中出現,merged 目錄為分層文件聯合掛載后的結果,也是容器內的工作目錄。
總體來說,overlay2 是這樣儲存文件的:overlay2將鏡像層和容器層都放在單獨的目錄,並且有唯一 ID,每一層僅存儲發生變化的文件,最終使用聯合掛載技術將容器層和鏡像層的所有文件統一掛載到容器中,使得容器中看到完整的系統文件。
2.2 overlay2 如何讀取、修改文件?
overlay2 的工作過程中對文件的操作分為讀取文件和修改文件。
讀取文件
容器內進程讀取文件分為以下三種情況。
- 文件在容器層中存在:當文件存在於容器層並且不存在於鏡像層時,直接從容器層讀取文件;
- 當文件在容器層中不存在:當容器中的進程需要讀取某個文件時,如果容器層中不存在該文件,則從鏡像層查找該文件,然后讀取文件內容;
- 文件既存在於鏡像層,又存在於容器層:當我們讀取的文件既存在於鏡像層,又存在於容器層時,將會從容器層讀取該文件。(由於寫時復制,所以此時肯定是修改過的文件才會復制到容器層,所以應該讀取容器層的文件)
修改文件或目錄
overlay2 對文件的修改采用的是寫時復制的工作機制,這種工作機制可以最大程度節省存儲空間。具體的文件操作機制如下。
- 第一次修改文件:當我們第一次在容器中修改某個文件時,overlay2 會觸發寫時復制操作,overlay2 首先從鏡像層復制文件到容器層,然后在容器層執行對應的文件修改操作。
overlay2 寫時復制的操作將會復制整個文件,如果文件過大,將會大大降低文件系統的性能,因此當我們有大量文件需要被修改時,overlay2 可能會出現明顯的延遲。好在,寫時復制操作只在第一次修改文件時觸發,對日常使用沒有太大影響。
- 刪除文件或目錄:當文件或目錄被刪除時,overlay2 並不會真正從鏡像中刪除它,因為鏡像層是只讀的,overlay2 會創建一個特殊的文件或目錄,這種特殊的文件或目錄會阻止容器的訪問。
結語
overlay2 目前已經是 Docker 官方推薦的文件系統了,也是目前安裝 Docker 時默認的文件系統,因為 overlay2 在生產環境中不僅有着較高的性能,它的穩定性也極其突出。但是 overlay2 的使用還是有一些限制條件的,例如要求 Docker 版本必須高於 17.06.02,內核版本必須高於 4.0 等。因此,在生產環境中,如果你的環境滿足使用 overlay2 的條件,請盡量使用 overlay2 作為 Docker 的聯合文件系統。
本文源自:拉勾教育課程:由淺入深吃透 Docker,講師:郭少 前 360 高級容器技術專家