修改文件
雖然 lower 和 upper 中的文件都出現(xiàn)在了 merged 目錄,但是二者還是有區(qū)別的。
lower 為底層目錄,只提供數(shù)據(jù),不能寫。
upper 為上層目錄,是可讀寫的。
測試:
# 分別對 merged 中的文件b和c寫入數(shù)據(jù)
# 其中文件 c 來自 lower,b來自 upper
echo "will-persist" > ./merged/b
echo "wont-persist" > ./merged/c
修改后從 merged 這個視圖進(jìn)行查看:
$ cat ./merged/b
will-persist
$ cat ./merged/c
wont-persist
可以發(fā)現(xiàn),好像兩個文件都被更新了,難道上面的結(jié)論是錯的?
再從 upper 和 lower 視角進(jìn)行查看:
$ cat ./upper/b
will-persist
$ cat ./lower/c
(empty)
可以發(fā)現(xiàn) lower 中的文件 c 確實(shí)沒有被改變。
那么 merged 中查看的時(shí)候,文件 c 為什么有數(shù)據(jù)呢?
由于 lower 是不可寫的,因此采用了 CoW 技術(shù),在對 c 進(jìn)行修改時(shí),復(fù)制了一份數(shù)據(jù)到 overlay 的 upper dir,即這里的 upper 目錄,進(jìn)入 upper 目錄查看是否存在 c 文件:
[root@iZ2zefmrr626i66omb40ryZ upper]$ ll
total 8
-rw-r--r-- 1 root root 0 Jan 18 18:50 a
-rw-r--r-- 1 root root 13 Jan 18 19:10 b
-rw-r--r-- 1 root root 13 Jan 18 19:10 c
[root@iZ2zefmrr626i66omb40ryZ upper]$ cat c
wont-persist
可以看到,upper 目錄中確實(shí)存在了 c 文件,
因?yàn)槭菑?lower copy 到 upper,因此也叫做 copy_up。
刪除文件
首先往 lower 目錄中寫入一個文件 f
[root@iZ2zefmrr626i66omb40ryZ ufs]$ cd lower/
[root@iZ2zefmrr626i66omb40ryZ lower]$ echo fff >> f
然后到 merge 目錄查看,能否看到文件 f
[root@iZ2zefmrr626i66omb40ryZ lower]$ ls ../merged/
f
果然 lower 中添加后,merged 中也能直接看到了。
然后再 merged 中去刪除文件 f:
[root@iZ2zefmrr626i66omb40ryZ lower]$ cd ../merged/
[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf f
# merged 中刪除后 lower 中文件還在
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls ../lower/
a c e f
# 而 upper 中出現(xiàn)了一個大小為0的c類型文件f
[root@iZ2zefmrr626i66omb40ryZ merged]# ls -l ../upper/
total 0
c--------- 1 root root 0, 0 Jan 18 19:28 f
可以發(fā)現(xiàn),overlay 中刪除 lower 中的文件,其實(shí)也是在 upper 中創(chuàng)建一個標(biāo)記,表示這個文件已經(jīng)被刪除了,而不會真正刪除 lower 中的文件。
測試一下:
[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf ../upper/f
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
f
[root@iZ2zefmrr626i66omb40ryZ merged]$ cat f
fff
把 upper 中的大小為 0 的 f 文件給刪掉后,merged 中又可以看到 lower 中 f 了,而且內(nèi)容也是一樣的。
說明 overlay 中的刪除其實(shí)是標(biāo)記刪除。再 upper 中添加一個刪除標(biāo)記,這樣該文件就被隱藏了,從 merged 中看到的效果就是文件被刪除了。
刪除文件或文件夾時(shí),會在 upper 中添加一個同名的
c
標(biāo)識的文件,這個文件叫whiteout
文件。當(dāng)掃描到此文件時(shí),會忽略此文件名。
添加文件
最后再試一下添加文件
# 首先在 merged 中創(chuàng)建文件 g
[root@iZ2zefmrr626i66omb40ryZ merged]$ echo ggg >> g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
g
# 然后查看 upper,發(fā)現(xiàn)也存在文件 g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls ../upper/
g
# 在查看內(nèi)容,發(fā)送是一樣的
[root@iZ2zefmrr626i66omb40ryZ merged]$ cat ../upper/g
ggg
說明 overlay 中添加文件其實(shí)就是在 upper 中添加文件。
測試一下刪除會怎么樣呢:
[root@iZ2zefmrr626i66omb40ryZ merged]$ rm -rf ../upper/g
[root@iZ2zefmrr626i66omb40ryZ merged]$ ls
f
把 upper 中的文件 g 刪除了,果然 merged 中的文件 g 也消失了。
3. docker 是如何使用 overlay 的?
上一節(jié)分析了 overlayfs 具體使用,這里分享一下 docker 是怎么使用 overlayfs。
大致流程
每一個 Docker image 都是由一系列的 read-only layers 組成:
- image layers 的內(nèi)容都存儲在 Docker hosts filesystem 的 /var/lib/docker/aufs/diff 目錄下
- 而 /var/lib/docker/aufs/layers 目錄則存儲著 image layer 如何堆棧這些 layer 的 metadata。
docker 支持多種 graphDriver,包括 vfs、devicemapper、overlay、overlay2、aufs 等等,其中最常用的就是 aufs 了,但隨著 linux 內(nèi)核 3.18 把 overlay 納入其中,overlay 的地位變得更重。
docker info
命令可以查看 docker 的文件系統(tǒng)。
$ docker info
# ...
Storage Driver: overlay2
#...
比如這里用的就是 overlay2。
例如,假設(shè)我們有一個由兩層組成的容器鏡像:
layer1: layer2:
/etc /bin
myconf.ini my-binary
然后,在容器運(yùn)行時(shí)將把這兩層作為 lower 目錄,創(chuàng)建一個空upper
目錄,并將其掛載到某個地方:
sudo mount \
-t overlay \
overlay \
-o lowerdir=/layer1:/layer2,upperdir=/upper,workdir=/work \
/merged
最后將/merged
用作容器的 rootfs。
這樣,容器中的文件系統(tǒng)就完成了。
具體分析
以構(gòu)建鏡像方式演示以下 docker 是如何使用 overlayfs 的。
先拉一下 Ubuntu:20.04 的鏡像:
$ docker pull ubuntu:20.04
20.04: Pulling from library/ubuntu
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:20.04
docker.io/library/ubuntu:20.04
然后寫個簡單的 Dockerfile :
FROM ubuntu:20.04
RUN echo "Hello world" > /tmp/newfile
開始構(gòu)建:
$ docker build -t hello-ubuntu .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM ubuntu:20.04
---> ba6acccedd29
Step 2/2 : RUN echo "Hello world" > /tmp/newfile
---> Running in ee79bb9802d0
Removing intermediate container ee79bb9802d0
---> 290d8cc1f75a
Successfully built 290d8cc1f75a
Successfully tagged hello-ubuntu:latest
查看構(gòu)建好的鏡像:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-ubuntu latest 290d8cc1f75a 13 minutes ago 72.8MB
ubuntu 20.04 ba6acccedd29 3 months ago 72.8MB
使用docker history
命令,查看鏡像使用的 image layer 情況:
$ docker history hello-ubuntu
IMAGE CREATED CREATED BY SIZE COMMENT
290d8cc1f75a 22 seconds ago /bin/sh -c echo "Hello world" > /tmp/newfile 12B
ba6acccedd29 3 months ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:5d68d27cc15a80653… 72.8MB
帶 missing 標(biāo)記的 layer 是自 Docker 1.10 之后,一個鏡像的 image layer image history 數(shù)據(jù)都存儲在 個文件中導(dǎo)致的,這是 Docker 官方認(rèn)為的正常行為。
可以看到,290d8cc1f75a 這一層在最上面,只用了 12Bytes,而下面的兩層都是共享的,這也證明了 AUFS 是如何高效使用磁盤空間的。
然后去找一下具體的文件:
docker 默認(rèn)的存儲目錄是/var/lib/docker
,具體如下:
[root@iZ2zefmrr626i66omb40ryZ docker]$ ls -al
total 24
drwx--x--x 13 root root 167 Jul 16 2021 .
drwxr-xr-x. 42 root root 4096 Oct 13 15:07 ..
drwx--x--x 4 root root 120 May 24 2021 buildkit
drwx-----x 7 root root 4096 Jan 17 20:25 containers
drwx------ 3 root root 22 May 24 2021 image
drwxr-x--- 3 root root 19 May 24 2021 network
drwx-----x 53 root root 12288 Jan 17 20:25 overlay2
drwx------ 4 root root 32 May 24 2021 plugins
drwx------ 2 root root 6 Jul 16 2021 runtimes
drwx------ 2 root root 6 May 24 2021 swarm
drwx------ 2 root root 6 Jan 17 20:25 tmp
drwx------ 2 root root 6 May 24 2021 trust
drwx-----x 5 root root 266 Dec 29 14:31 volumes
在這里,我們只關(guān)心image
和overlay2
就足夠了。
- image:鏡像相關(guān)
- overlay2:docker 文件所在目錄,也可能不叫這個名字,具體和文件系統(tǒng)有關(guān),比如可能是 aufs 等。
先看 image
目錄:
docker 會在/var/lib/docker/image
目錄下按每個存儲驅(qū)動的名字創(chuàng)建一個目錄,如這里的overlay2
。
[root@iZ2zefmrr626i66omb40ryZ docker]$ cd image/
[root@iZ2zefmrr626i66omb40ryZ image]$ ls
overlay2
# 看下里面有哪些文件
[root@iZ2zefmrr626i66omb40ryZ image]$ tree -L 2 overlay2/
overlay2/
├── distribution
│ ├── diffid-by-digest
│ └── v2metadata-by-diffid
├── imagedb
│ ├── content
│ └── metadata
├── layerdb
│ ├── mounts
│ ├── sha256
│ └── tmp
└── repositories.json
這里的關(guān)鍵地方是imagedb
和layerdb
目錄,看這個目錄名字,很明顯就是專門用來存儲元數(shù)據(jù)的地方。
- layerdb:docker image layer 信息
- imagedb:docker image 信息
因?yàn)?docker image 是由 layer 組成的,而 layer 也已復(fù)用,所以分成了 layerdb 和 imagedb。
先去 imagedb 看下剛才構(gòu)建的鏡像:
$ cd overlay2/imagedb/content/sha256
$ ls
[root@iZ2zefmrr626i66omb40ryZ sha256]# ls
0c7ea9afc0b18a08b8d6a660e089da618541f9aa81ac760bd905bb802b05d8d5 61ad638751093d94c7878b17eee862348aa9fc5b705419b805f506d51b9882e7
// .... 省略
b20b605ed599feb3c4757d716a27b6d3c689637430e18d823391e56aa61ecf01
60d84e80b842651a56cd4187669dc1efb5b1fe86b90f69ed24b52c37ba110aba ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1
可以看到,都是 64 位的 ID,這些就是具體鏡像信息,剛才構(gòu)建的鏡像 ID 為290d8cc1f75a
,所以就找290d8cc1f75a
開頭的文件:
[root@iZ2zefmrr626i66omb40ryZ sha256]$ cat 290d8cc1f75a4e230d645bf03c49bbb826f17d1025ec91a1eb115012b32d1ff8
{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["bash"],"Image":"sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"ee79bb9802d0ff311de6d606fad35fa7e9ab0c1cb4113837a50571e79c9454df","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","echo \"Hello world\" \u003e /tmp/newfile"],"Image":"sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2022-01-17T12:25:14.91890037Z","docker_version":"20.10.6","history":[{"created":"2021-10-16T00:37:47.226745473Z","created_by":"/bin/sh -c #(nop) ADD file:5d68d27cc15a80653c93d3a0b262a28112d47a46326ff5fc2dfbf7fa3b9a0ce8 in / "},{"created":"2021-10-16T00:37:47.578710012Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2022-01-17T12:25:14.91890037Z","created_by":"/bin/sh -c echo \"Hello world\" \u003e /tmp/newfile"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b","sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c"]}}
這就是 image 的 metadata,這里主要關(guān)注 rootfs:
# 和 docker inspect 命令顯示的內(nèi)容差不多
// ...
"rootfs":{"type":"layers","diff_ids":
[
"sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b",
"sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c"
]
}
// ...
可以看到 rootfs 的 diff_ids 是一個包含了兩個元素的數(shù)組,這兩個元素就是組成 hello-ubuntu 鏡像的兩個 Layer 的diffID
。
從上往下看,就是底層到頂層,即9f54eef412...
是 image 的最底層。
然后根據(jù) layerID 去layerdb
目錄尋找對應(yīng)的 layer:
[root@iZ2zefmrr626i66omb40ryZ overlay2]# tree -L 2 layerdb/
layerdb/
├── mounts
├── sha256
└── tmp
在這里我們只管mounts
和sha256
兩個目錄,先打印以下 sha256 目錄
$ cd /var/lib/docker/image/overlay2/layerdb/sha256/
$ ls
05dd34c0b83038031c0beac0b55e00f369c2d6c67aed11ad1aadf7fe91fbecda
// ... 省略
6aa07175d1ac03e27c9dd42373c224e617897a83673aa03a2dd5fb4fd58d589f
可以看到,layer 里也是 64 位隨機(jī) ID 構(gòu)成的目錄,找到剛才 hello-ubuntu 鏡像的最底層 layer:
$ cd 9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b
[root@iZ2zefmrr626i66omb40ryZ 9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b]$ ls
cache-id diff size tar-split.json.gz
文件含義如下:
- cache-id:為具體
/var/lib/docker/overlay2/<cache-id>
存儲路徑 - diff:diffID,用于計(jì)算 ChainID
- size:當(dāng)前 layer 的大小
docker 使用了 chainID 的方式來保存 layer,layer.ChainID 只用本地,根據(jù) layer.DiffID 計(jì)算,并用于 layerdb 的目錄名稱。
chainID 唯一標(biāo)識了一組(像糖葫蘆一樣的串的底層)diffID 的 hash 值,包含了這一層和它的父層(底層),
- 當(dāng)然這個糖葫蘆可以有一顆山楂,也就是 chainID(layer0)==diffID(layer0);
- 對于多顆山楂的糖葫蘆,ChainID(layerN) = SHA256hex(ChainID(layerN-1) + " " + DiffID(layerN))。
# 查看 diffID,
$ cat diff
sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b
由于這是 layer0,所以 chainID 就是 diffID,然后開始計(jì)算 layer1 的 chainID:
ChainID(layer1) = SHA256hex(ChainID(layer0) + " " + DiffID(layer1))
layer0 的 chainID 是9f54...
,而 layer1 的 diffID 根據(jù) rootfs 中的數(shù)組可知,為b3cce...
計(jì)算 ChainID:
$ echo -n "sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b sha256:b3cce2ce0405ffbb4971b872588c5b7fc840514b807f18047bf7d486af79884c" | sha256sum| awk '{print $1}'
6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c
一定注意要加上 “sha256:”和中間的空格“ ” 這兩部分。
因此 layer1 的 chainID 就是6613...
找到 layerdb 里面以sha256+6613
開頭的目錄
$ cd /var/lib/docker/image/overlay2/layerdb/sha2566613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c
# 根據(jù)這個大小可以知道,就是hello-ubuntu 鏡像的最上面層 layer
[root@iZ2zefmrr626i66omb40ryZ 6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c]$ cat size
12
# 查看 cache-id 找到 文件系統(tǒng)中的具體位置
[root@iZ2zefmrr626i66omb40ryZ 6613b10b697b0a267c9573ee23e54c0373ccf72e7991cf4479bd0b66609a631c]$ cat cache-id
83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65
根據(jù) cache-id 進(jìn)入具體數(shù)據(jù)存儲目錄:
格式為
/var/lib/docker/overlay2/<cache-id>
# 進(jìn)入剛才生成的目錄
$ cd /var/lib/docker/overlay2/83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65
[root@iZ2zefmrr626i66omb40ryZ 83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65]# ls -al
total 24
drwx-----x 4 root root 55 Jan 17 20:25 .
drwx-----x 53 root root 12288 Jan 17 20:25 ..
drwxr-xr-x 3 root root 17 Jan 17 20:25 diff
-rw-r--r-- 1 root root 26 Jan 17 20:25 link
-rw-r--r-- 1 root root 28 Jan 17 20:25 lower
drwx------ 2 root root 6 Jan 17 20:25 work
# 查看 diff 目錄
[root@iZ2zefmrr626i66omb40ryZ
83b569c0f5de093192944931e4f41dafb2d7f80eae97e4bd62425c20e2079f65]$ cd diff/
[root@iZ2zefmrr626i66omb40ryZ diff]$ ls
tmp
[root@iZ2zefmrr626i66omb40ryZ diff]$ cd tmp/
[root@iZ2zefmrr626i66omb40ryZ tmp]$ ls
newfile
[root@iZ2zefmrr626i66omb40ryZ tmp]# cat newfile
Hello world
可以看到,我們新增的 newfile 就在這里。
如果你對云原生技術(shù)充滿好奇,想要深入了解更多相關(guān)的文章和資訊,歡迎關(guān)注微信公眾號。
搜索公眾號【探索云原生】即可訂閱