聊聊 Docker 的儲存驅動 Overlay2

張晉濤發表於2022-11-28

大家好,我是張晉濤。

上週在我的交流群裡有個小夥伴問到了 Overlay2 相關的問題,這篇就來介紹一下。

本節,我將為你介紹 Docker 現在推薦使用的儲存驅動 Overlay2,在開始之前,你可以執行以下命令來檢視 Docker 正在使用的儲存驅動:

(MoeLove) ➜  ~ docker info --format '{{.Driver}}'                  
overlay2

如果你看到的結果也是 overlay2 說明你的 Docker 已經在使用 overlay2 儲存驅動了。我在個人工作站上用的是 btrfs,這是因為自從 Fedora 33 開始,btrfs 就成為了 Fedora 預設的檔案系統。不過伺服器上就都是 overlay2 了。

你也可能會看到其他不同的結果,可以在啟動 docker daemon 的時候,透過 --storage-driver 引數進行指定,也可以在 /etc/docker/daemon.json 檔案中透過 storage-driver 欄位進行配置。

目前對於 Docker 最新版本而言,你有以下幾種儲存驅動可供選擇:

  • overlay2
  • fuse-overlayfs
  • btrfs
  • zfs
  • aufs
  • overlay
  • devicemapper
  • vfs

但它們對於你使用的檔案系統之類的都有不同的要求,且實現方式也不盡相同。我以本節的重點 overlay2 儲存驅動為例,它需要你使用 Linux 4.x 以上版本的核心,或者是對於 RHEL/CentOS 等需要使用 3.10.0-514 以上的核心(舊版本中存在一些相容性問題,我在之前的文章中有提到過)。

同時,它支援你使用 ext4 的檔案系統,或者增加了 ftype=1 的 xfs 檔案系統。可以透過 docker info 進行得到檔案系統相關的資訊。

# 省略了部分輸出
(MoeLove) ➜  ~ docker info                  
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true

儲存驅動的作用

前面雖然已經聊瞭如何設定和檢查當前在用的儲存驅動,但尚未介紹為何一定要使用儲存驅動,以及它的作用。

還記得我在之前的文章《萬字長文:徹底搞懂容器映象構建》中為你介紹的 Docker 如何儲存映象相關的內容嗎,如果忘了可以回頭複習一下。

Docker 將容器映象做了分層儲存,每個層相當於包含著一條 Dockerfile 的指令。而這些層在磁碟上的儲存方式,以及在啟動容器時,如何組織這些層,並提供可寫層,便是儲存驅動的主要作用了。

另外需要注意的是:不同的儲存驅動實現不同,效能也有差異,同時使用不同的儲存驅動也會導致佔用的磁碟空間有所不同。

同時: 由於它們的實現不同,當你修改儲存驅動後,可能會導致看不到原有的映象,容器等,這是正常的,不必擔心,切換回原先的驅動即可見。

OverlayFS

瞭解完前面的背景知識後,你也看到了我剛才列出的可用儲存驅動中有兩個 overlayoverlay2,其實 overlay2 算是 overlay 的升級版,這兩個儲存驅動所用的都是 OverlayFS

overlay 驅動是在 2014 年 8 月份首次進入 Docker 的,而 overlay2 則是在 2016 年 6 月份被合併,並首次出現在 Docker 1.12 中的。它的出現是為了解決 overlay 儲存驅動可能早層 inode 耗盡的問題。

簡單介紹完 overlayoverlay2 ,我們將重點回歸到 OverlayFS 上。

我們啟動一個容器,以此為切入點來認識下 OverlayFS,注意: 以下內容使用 Linux 5.4 核心以及 Docker 20.10.21,不同環境下可能結果略有差異。

# 檢查無在執行的容器和 overlay 掛載
(MoeLove) ➜  ~ mount |grep overlay
(MoeLove) ➜  ~ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

# 啟動一個容器
(MoeLove) ➜  ~ docker run --rm -d alpine sleep 99999                         
caa9517ce0d799602735a30aaaaf123c07e07ff6e44c5a4b07e776af85780abe
(MoeLove) ➜  ~ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
caa9517ce0d7        alpine              "sleep 99999"       23 seconds ago      Up 22 seconds                           hopeful_dubinsky

# 檢查 overlay 掛載
(MoeLove) ➜  ~ mount |grep overlay                  
overlay on /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/5OO3RLRXHJPEH3IFEXNCTO4PY5:/var/lib/docker/overlay2/l/UVA7IR67ZZTN2BNTKCZ7T6HUWU,upperdir=/var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/diff,workdir=/var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/work)

可以看到,在啟動容器後,系統上多了一個 OverlayFS (overlay) 的掛載。注意看其中的幾個內容:

  • 掛載點在: /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/merged

    (MoeLove) ➜  ~ sudo ls /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/merged
    bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

    其中的內容,看著很熟悉,是我們所啟動容器根目錄中的內容。為了驗證這一說法,我在容器中新寫一個檔案:

    (MoeLove) ➜  ~ docker exec -it $(docker ps -ql) sh
    / # echo 'Hello Docker' > moelove-info

    再次檢視此掛載點中的內容:

    (MoeLove) ➜  ~ sudo ls  /var/lib/docker/overlay2/22be5e4dc4541a60aa4f6de628c3938e7fdc9c4b117277274cd911c46166986b/merged
    bin  dev  moelove-info  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    (MoeLove) ➜  ~ sudo cat /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/merged/moelove-info
    Hello Docker

    可以看到剛才寫的內容已經在這個掛載點的目錄中了。

  • lowerdir: 這是是我們 mount 中指定的目錄。

    這個 lowerdir 中包含兩個目錄,這是使用了核心對 OverlayFS multi layer 特性的支援,我們分別檢視下其中內容:

    (MoeLove) ➜  ~ sudo ls -a /var/lib/docker/overlay2/l/5OO3RLRXHJPEH3IFEXNCTO4PY5
    .  ..  dev  .dockerenv  etc
    (MoeLove) ➜  ~ sudo ls -a /var/lib/docker/overlay2/l/UVA7IR67ZZTN2BNTKCZ7T6HUWU
    .  ..  bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

    這兩個目錄,是不是看著很熟悉?

    是的,它們就是我們所啟動容器根目錄中的大部分內容。為什麼說是大部分內容呢?當我們檢視其中的內容時,你也會發現它們的內容也並不完整。比如我們剛才新寫入的 moelove-info 檔案,或者當我們檢視 etc 目錄下的檔案,你也會發現其中都只是常規系統 /etc 目錄下的部分內容。

    (MoeLove) ➜  ~ sudo ls /var/lib/docker/overlay2/l/5OO3RLRXHJPEH3IFEXNCTO4PY5/etc   
    hostname  hosts  mtab  resolv.conf
    (MoeLove) ➜  ~ sudo ls /var/lib/docker/overlay2/l/UVA7IR67ZZTN2BNTKCZ7T6HUWU/etc 
    alpine-release  fstab     init.d       modprobe.d      mtab        passwd     protocols  shells       udhcpd.conf
    apk             group     inittab      modules         network     periodic   securetty  ssl
    conf.d          hostname  issue        modules-load.d  opt         profile    services   sysctl.conf
    crontabs        hosts     logrotate.d  motd            os-release  profile.d  shadow     sysctl.d
  • upperdir 是另一個重要的目錄,我們來看看其中的內容

    (MoeLove) ➜  ~ sudo ls -a /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/diff  
    .  ..  moelove-info  root

    我們發現這個目錄中包含著剛才建立的 moelove-info 檔案。同時,其中也包含一個 root 目錄,這個目錄便是我們預設使用的 root 使用者的家目錄。

    如果去檢視其中的內容,也會發現剛才我們執行命令的歷史記錄。

  • workdir 這個目錄和 upperdir 在同一個父目錄下,檢視其內容發現裡面只有一個 work 目錄

    (MoeLove) ➜  ~ sudo ls -a /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/work
    .  ..  work

看完以上的介紹,想必你已經發現了它們之間的部分聯絡,在此之前,我們在額外看一個目錄,那就是 upperdirworkdir 以及掛載點共同的父目錄:

(MoeLove) ➜  ~ sudo ls  /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db 
diff  link  lower  merged  work

你會發現這個目錄下的內容就比較直觀了。我們剛才已經看了其中 diffmergedwork 目錄的內容了,現在看看 lower 中的內容吧:

(MoeLove) ➜  ~ sudo cat /var/lib/docker/overlay2/f4356a8f14342008fc298bf3d313b863d10f30ef447a3b2f51ea9ece0dec09db/lower
l/5OO3RLRXHJPEH3IFEXNCTO4PY5:l/UVA7IR67ZZTN2BNTKCZ7T6HUWU

我們發現,lower 檔案中的內容是以 : 分隔的兩個 lowerdir 的目錄名稱。

至此,我們可以得到以下結論:

  • lower 是基礎層,可以包含多個 lowerdir
  • diff 是可寫層,即掛載時的 upperdir,在容器內變更的檔案都在這一層儲存;
  • merged 是最終的合併結果,即容器給我們呈現出來的結果;

Overlay2

經過前面對 Docker 啟動容器後掛載的 OverlayFS 的介紹後,Overlay2 的工作流程想必你也就比較清楚了。

將映象各層作為 lower 基礎層,同時增加 diff 這個可寫層,透過 OverlayFS 的工作機制,最終將 merged 作為容器內的檔案目錄展示給使用者。

你可能會有疑問,如果只是這樣簡單的組織,會不會有什麼限制呢?答案是肯定的,當然有限制,我們可以透過 Overlay2 的程式碼來看

// daemon/graphdriver/overlay2/overlay.go#L442
func (d *Driver) getLower(parent string) (string, error) {
// 省略部分內容
    if len(lowers) > maxDepth {
        return "", errors.New("max depth exceeded")
    }
}

可以看到其對 lower 的深度有硬編碼的限制,當前硬編碼的限制是 128 。如果你在使用的過程中遇到這個錯誤,那表示你超過了最大深度限制,你就需要找些辦法來減少層級了。

總結

本節,我為你介紹了 OverlayFS 及 Overlay2 儲存驅動相關的內容。透過實際啟動容器生成的相關目錄來介紹 overlay2 的工作流程,想必透過這種方式能更易理解。


歡迎訂閱我的文章公眾號【MoeLove】

相關文章