docker容器技術基礎之聯合文件系統OverlayFS

我們在上篇介紹了容器技術中資源隔離與限制docker容器技術基礎之linux cgroup、namespace

這篇小作文我們要嘗試學習容器的另外一個重要技術之聯合文件系統之OverlayFS,在介紹OverlayFS之前我們會學習一下鏡像、容器、層的相關知識,然後是OverlayFS及相關實例,最後介紹docker中overlay2驅動即overlayfs在容器中的實現。


一、鏡像、容器和層

docker中鏡像是層級結構的,即圖中的image layers,每一層只是與它之前的層的一組差異。這些層堆疊在彼此的頂部。當我們創建一個新容器時,會在鏡像層加一個新的可寫層。這一層通常被稱為「容器層」。對正在運行的容器所做的所有更改,例如寫入新文件、修改現有文件和刪除文件,都將寫入這個薄的可寫容器層。

container-layers

那麼一個鏡像創建多個容器時是怎樣的景象呢?如下圖所示,添加新數據或修改現有數據的所有寫入容器都存儲在此可寫層中。當容器被刪除時,可寫層也被刪除。底層鏡像保持不變。因為每個容器都有自己的可寫容器層,所有的變化都存儲在這個容器層中,所以多個容器可以共享對同一個底層鏡像的訪問,同時又擁有自己的數據狀態。

sharing-layers

到這裡你可能要問了:鏡像為什麼要分層啊?亂七八糟的!

其實不然,通過鏡像的層級結構主要的一個優點是你可以把你的基礎鏡像進行共享,什麼意思呢?比如你現在需要一個Nginx鏡像、一個Tomcat鏡像它們都可以通過一個base鏡像如centos或者ubuntu製作而成,它看起來是這樣的

image-20210723103255000

如此一來通過鏡像分層可以大大減少磁碟空間佔用,同時降低鏡像復構建雜度,何樂而不為。


二、聯合文件系統OverlayFS

通過上面我們大概了解了鏡像、容器和層的關係,那麼又有一個問題了:鏡像層和可寫容器層的文件或者內容是如何來管理的?明明是分層的又是怎麼合併的?

接下來我們將介紹UnionFS(聯合文件系統),它的厲害之處在於可以將多個目錄掛載到一個根目錄。OverlayFS 是linux現代聯合文件系統的一個代表,合併於Linux內核的3.18版本。從 docker 18.06後docker為OverlayFS提供了兩個存儲驅動,原始的overlay及overlay2(改善 inode 利用率),overlay2是目前docker推薦和首選存儲驅動,通過它來管理鏡像層和可寫容器層內容。

我們可以在docker info中查看docker存儲驅動版本

[root@i-k9pwet2d ~]# docker info
...
 Server Version: 20.10.6
 Storage Driver: overlay2
 Backing Filesystem: extfs
...

OverlayFS這種堆疊的文件系統,依賴於其他文件系統之上,比如我們在info 中看到的extfs或者xfs等,它的結構如下圖:

upper-lower

我們的基礎層稱為「lowerdir」即原始文件所在的位置。

客戶端所做的任何修改都將反映在「upperdir」層上:

  • 如果更改文件,新版本將寫入其中(file1)。
  • 如果刪除文件,將在該層上創建一個刪除標記(file2)。
  • 創建一個新文件(file4)。

最後,「merged」是所有層合併後的最終視圖。

假如你有一些數據,需要多個進程來訪問和修改它。每個進程都要創建一個獨立的數據視圖,你要存儲多份原始數據,數據量大的話顯然這會非常低效的。使用OverlayFS將會是very good!

接下來我們來我們搞個實驗看看

我建立如下目錄結構,workdir在OverlayFS中需要為空,用作內部臨時存儲。lowerdir包含3個文件file1、file2、file3

[root@i-k9pwet2d overlayfs_test]# tree .
.
├── client_1
│   ├── upperdir
│   └── workdir
├── client_2
│   ├── upperdir
│   └── workdir
├── lowerdir
│   ├── file1.txt
│   └── file2.txt
│	└── file3.txt
└── merged
    ├── client_1
    └── client_2

10 directories, 3 files

掛載overlay

mount -t overlay overlay \
-o lowerdir=/overlaytest/lowerdir \
-o upperdir=/overlaytest/client_1/upperdir \
-o workdir=/overlaytest/client_1/workdir \
/overlaytest/merged/client_1

mount -t overlay overlay \
-o lowerdir=/overlaytest/lowerdir \
-o upperdir=/overlaytest/client_2/upperdir \
-o workdir=/overlaytest/client_2/workdir \
/overlaytest/merged/client_2

掛載後查看我們的視圖,可以看到三個文件已經被合併到merged區了

[root@i-k9pwet2d overlaytest]# tree .
.
├── client_1
│   ├── upperdir
│   └── workdir
│       └── work
├── client_2
│   ├── upperdir
│   └── workdir
│       └── work
├── lowerdir
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
└── merged
    ├── client_1
    │   ├── file1.txt
    │   ├── file2.txt
    │   └── file3.txt
    └── client_2
        ├── file1.txt
        ├── file2.txt
        └── file3.txt

下一步我們修改merged/client_1下修改我們的都數據

[root@i-k9pwet2d client_1]# echo "data no.1">>file1.txt 
[root@i-k9pwet2d client_1]# rm file2.txt 
[root@i-k9pwet2d client_1]# echo "data4" > file4.txt
[root@i-k9pwet2d client_1]# ls
file1.txt  file3.txt  file4.txt

再看我們的視圖,可以看到修改只作用於client_1/upperdir,對我們lowerdir下原始數據以及client_2數據並不影響。

[root@i-k9pwet2d overlaytest]# tree .
.
├── client_1
│   ├── upperdir
│   │   ├── file1.txt
│   │   ├── file2.txt
│   │   └── file4.txt
│   └── workdir
│       └── work
├── client_2
│   ├── upperdir
│   └── workdir
│       └── work
├── lowerdir
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
└── merged
    ├── client_1
    │   ├── file1.txt
    │   ├── file3.txt
    │   └── file4.txt
    └── client_2
        ├── file1.txt
        ├── file2.txt
        └── file3.txt

12 directories, 12 files

OverlayFS在容器中的實現

下圖顯示了 在Docker 中鏡像和 容器是如何通過OverlayFS分層與互相構造的映射。影像層是lowerdir,容器層是upperdir。統一視圖合併到merged目錄,該目錄實際上是容器安裝點。

overlay_constructs

我們查看一個真正運行容器centos的inspect

docker inspect ca9a9e0a35c7

可以看到OverlayFS對應的目錄地址,lowerDir即我們的原始數據包含image的rootfs(根文件)以及init相關文件,關於docker init層可以自行檢索一下哈,這裡不做介紹了。

"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968-init/diff:/var/lib/docker/overlay2/0d6b94986ba1af1cc75e7c237f78d7e02d40f5ae5ec3f67ddb699ae6d07a2ca8/diff",
                "MergedDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/merged",
                "UpperDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/diff",
                "WorkDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/work"
            },
            "Name": "overlay2"
        },

LowerDir下查看原始rootfs

[root@i-k9pwet2d client_1]# ls /var/lib/docker/overlay2/0d6b94986ba1af1cc75e7c237f78d7e02d40f5ae5ec3f67ddb699ae6d07a2ca8/diff
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

我們進入容器添加和刪除文件看看

[root@i-k9pwet2d ~]# docker exec -it ca9a /bin/bash

[root@ca9a9e0a35c7 /]# echo "newfile" >file 

[root@ca9a9e0a35c7 /]# rm /tmp/ks-script-esd4my7v

顯然到到LowerDir下查看原始rootfs並不受影響而是把變更寫入到了upperdir即容器層

cd  /var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/diff

[root@i-k9pwet2d diff]# tree .
.
├── file
└── tmp
    └── ks-script-esd4my7v

1 directory, 2 files

以上的操作也就是docker中所謂的CoW(寫時複製)策略。在docker中overlay2驅動對聯合文件系統操作的更多場景可以參閱官方文檔


這樣我們實現容器的三大基礎技術Namespace、Cgroup、UnionFS聯合文件系統已經介紹完啦,希望這三篇小作文對想了解容器實現原理的讀者有些許幫助。


參考:


小作文有不足的地方歡迎指出。

感謝收藏、點贊。關注頂級飲水機管理員,除了燒熱水,有時還做點別的。

您的支援是我燒熱水最大的動力…