S_lion's Studio

理解存储驱动overlay2

字数统计: 2.7k阅读时长: 12 min
2021/07/12 Share

容器中通过使用不同的rootfs来模拟了不同的操作系统及应用,不过,如果使用每个镜像都需要一个独立的根文件系统的话,那想必磁盘早已拥挤不堪了;且一个镜像可以同时运行多个容器,每个容器对文件的改动该怎么办?

Linux提供了一种叫做联合文件系统的文件系统,它具备如下特性:

  • 联合挂载:将多个目录按层次组合,一并挂载到一个联合挂载点。
  • 写时复制:对联合挂载点的修改不会影响到底层的多个目录,而是使用其他目录记录修改的操作。

目前有多种文件系统可以被当作联合文件系统,实现如上的功能:overlay2,aufs,devicemapper,btrfs,zfs,vfs等等。而overlay2就是其中的佼佼者,也是docker目前推荐的文件系统:https://docs.docker.com/storage/storagedriver/select-storage-driver/

Overlay2

overlay2是一个类似于aufs的现代的联合文件系统,并且更快。overlay2已被收录进linux内核,它需要内核版本不低于4.0,如果是RHEL或Centos的话则不低于3.10.0-514。

我们可以查看下自己环境docker配置的存储驱动是什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@slions_pc1 ~]# docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 1
Server Version: 18.09.3
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: d71fcd7d8303cbf684402823e425e9dd2e99285d
runc version: N/A
init version: fec3683
Security Options:
seccomp
Profile: default
Kernel Version: 3.10.0-957.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.683GiB
Name: slions_pc1
ID: 7W4N:NDNK:BDXL:ADXV:ONCZ:PUA7:KSK2:73JQ:Q3HS:AEEK:VKAL:H3RC
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Live Restore Enabled: false
Product License: Community Engine

默认就是overlay2,如果你本地环境不是overlay2,只需编辑/etc/docker/daemon.json, 添加以下内容并重启docker。

1
2
3
{
"storage-driver": "overlay2"
}

docker默认的存储目录是/var/lib/docker,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@slions_pc1 ~]# ls -l /var/lib/docker
总用量 0
drwx------. 2 root root 24 7月 10 11:52 builder
drwx------. 4 root root 92 7月 10 11:52 buildkit
drwx------. 3 root root 78 7月 12 13:08 containers
drwx------. 3 root root 22 7月 10 11:52 image
drwxr-x---. 3 root root 19 7月 10 11:52 network
drwx------. 6 root root 261 7月 12 14:52 overlay2
drwx------. 4 root root 32 7月 10 11:52 plugins
drwx------. 2 root root 6 7月 12 12:07 runtimes
drwx------. 2 root root 6 7月 10 11:52 swarm
drwx------. 2 root root 6 7月 12 12:07 tmp
drwx------. 2 root root 6 7月 10 11:52 trust
drwx------. 2 root root 25 7月 10 11:52 volumes

在这里,我们只关心imageoverlay2就足够了。

在我本地起了一个叫slions的容器,容器id为47c26762a49a,镜像id 为4cdc5dd7eaad,这两个id作为了容器和镜像的唯一标识符,我们后面会用到

1
2
3
4
5
6
7
8
9
[root@slions_pc1 ~]# docker run -itd --name slions nginx:latest
47c26762a49a3f397e6796cf499f97acbc606a68c04d08b9cd169d6e33469c99
[root@slions_pc1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
47c26762a49a nginx:latest "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 80/tcp slions
[root@slions_pc1 ~]# docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 4cdc5dd7eaad 5 days ago 133MB

docker会在/var/lib/docker/image目录下按每个存储驱动的名字创建一个目录,如这里的overlay2

1
2
3
[root@slions_pc1 ~]# ll /var/lib/docker/image/
总用量 0
drwx------. 5 root root 81 7月 12 14:52 overlay2

查看overlay2下的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@slions_pc1 ~]# tree -L 2 /var/lib/docker/image/overlay2/
/var/lib/docker/image/overlay2/
├── distribution
│   ├── diffid-by-digest
│   └── v2metadata-by-diffid
├── imagedb
│   ├── content
│   └── metadata
├── layerdb
│   ├── mounts
│   ├── sha256
│   └── tmp
└── repositories.json

看名字就能知道这儿是用来存放元数据的, 其中还细分为了imagedblayerdb,因为在docker中,image是由多个layer组合而成的,换句话就是layer是一个共享的层,可能有多个image会指向某个layer。
那如何才能确认image包含了哪些layer呢?答案就在imagedb这个目录中去找。

比如上面启动的slions容器,我们已知image id为4cdc5dd7eaad,接着打印/var/lib/docker/image/overlay2/imagedb/content/sha256这个目录:

1
2
3
[root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/imagedb/content/sha256
总用量 8
-rw-------. 1 root root 7729 7月 12 15:59 4cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f

4cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f正是记录slions镜像元数据的文件,接下来cat一下这个文件,得到一个长长的json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@slions_pc1 ~]# cat /var/lib/docker/image/overlay2/imagedb/content/sha256/4cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f |jq
{
.......
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7",
"sha256:ace9ed9bcfafbc909bc3e9451490652f685959db02a4e01e0528a868ee8eab3e",
"sha256:48b4a40de3597ec0a28c2d4508dec64ae685ed0da77b128d0fb5c69cada91882",
"sha256:c553c6ba5f1354e1980871b413e057950e0c02d2d7a66b39de2e03836048fda9",
"sha256:d97733c0a3b64c08bc0dd286926a8eff1b162b4d9fad229eab807c6dc516c172",
"sha256:9d1af766c81806211d5453b711169103e4f5c3c2609e1dfb9ea4dee7e96a7968"
]
}
}

查看最后的rootfs部分,diff_ids是一个包含了6个元素的数组,这6个元素正是组成nginx镜像的6个layerID,从上往下看,就是底层到顶层,也就是说764055e...是image的最底层。既然得到了组成这个image的所有layerID,那么我们就可以带着这些layerID去寻找对应的layer了。

接下来,我们返回到上一层的layerdb中,先打印一下这个目录:

1
2
3
4
5
6
[root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/layerdb/
总用量 4
drwxr-xr-x. 3 root root 78 7月 12 16:00 mounts
drwxr-xr-x. 8 root root 4096 7月 12 15:59 sha256
drwxr-xr-x. 2 root root 6 7月 12 15:59 tmp

在这里我们只管mountssha256两个目录,再打印一下sha256目录:

1
2
3
4
5
6
7
8
9
[root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/layerdb/sha256/
总用量 0
drwx------. 2 root root 85 7月 12 15:59 2c78bcd3187437a7a5d9d8dbf555b3574ba7d143c1852860f9df0a46d5df056a
drwx------. 2 root root 85 7月 12 15:59 435c6dad68b58885ad437e5f35f53e071213134eb9e4932b445eac7b39170700
drwx------. 2 root root 85 7月 12 15:59 63d268dd303e176ba45c810247966ff8d1cb9a5bce4a404584087ec01c63de15
drwx------. 2 root root 71 7月 12 15:59 764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7
drwx------. 2 root root 85 7月 12 15:59 b27eb5bbca70862681631b492735bac31d3c1c558c774aca9c0e36f1b50ba915
drwx------. 2 root root 85 7月 12 15:59 bdf28aff423adfe7c6cb938eced2f19a32efa9fa3922a3c5ddce584b139dc864

在这里,我们仅仅发现764055e..这个最底层的layer,那么剩余的layer为什么会没有呢?那是因为docker1.10版本以后layer层之间的关系通过chainId来进行关联。

公式是:chainID=sha256sum(H(chainID) diffid),也就是764055e..的上一层的sha256 id是:

1
2
[root@slions_pc1 ~]# echo -n "sha256:764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7 sha256:ace9ed9bcfafbc909bc3e9451490652f685959db02a4e01e0528a868ee8eab3e"  | sha256sum -
2c78bcd3187437a7a5d9d8dbf555b3574ba7d143c1852860f9df0a46d5df056a -

依次类推,我们就能找出所有的layerID的组合。
但是上面我们提到,/var/lib/docker/image/overlay2/layerdb存的只是元数据,那么真实的rootfs到底存在哪里呢?

答案就在cache-id中。我们打印一下

/var/lib/docker/image/overlay2/layerdb/sha256/764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7/cache-id:

1
2
[root@slions_pc1 ~]# cat /var/lib/docker/image/overlay2/layerdb/sha256/764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7/cache-id
45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be

这个id就是对应/var/lib/docker/overlay2/45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be。因此,以此类推,更高一层的layer对应的cache-id也能找到对应的rootfs,当这些rootfs的diff目录通过联合挂载的方式挂载到某个目录,就能提供整个容器需要的rootfs了。

overlay存储流程

overlay2

最下层是一个 lower 层,也就是镜像层,它是一个只读层。右上层是一个 upper 层,upper 是容器的读写层,upper 层采用了写时复制的机制,也就是说只有对某些文件需要进行修改的时候才会从 lower 层把这个文件拷贝上来,之后所有的修改操作都会对 upper 层的副本进行修改。

upper 并列的有一个 workdir,它的作用是充当一个中间层的作用。也就是说,当对 upper 层里面的副本进行修改时,会先放到 workdir,然后再从 workdir 移到 upper 里面去,这个是 overlay 的工作机制。

最上面的是 mergedir,是一个统一视图层。从 mergedir 里面可以看到 upper 和 lower 中所有数据的整合,然后我们 docker exec 到容器里面,看到一个文件系统其实就是 mergedir 统一视图层。

在运行容器后,可以通过mount命令查看其具体挂载信息:

1
2
[root@slions_pc1 ~]# mount -t overlay
overlay on /var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/XZ6TYQO6XXCBXKH6E4N3N7QTWU:/var/lib/docker/overlay2/l/U6O63XRKJYL3MKFKZCZ33EEDE6:/var/lib/docker/overlay2/l/Y2XZUZZM42C6HPF6BHHLAMGY2G:/var/lib/docker/overlay2/l/P35XQEQ2NQELR5QWTI3POCZPJG:/var/lib/docker/overlay2/l/KRCQJWNO5RDBHNQOQXFCLT2JLX:/var/lib/docker/overlay2/l/5KKQYPGMGGXBSLAIEHV72IJ2QH:/var/lib/docker/overlay2/l/4PZ2UIG6UG3GF2PLSFP6C6X7AO,upperdir=/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff,workdir=/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/work)

可以看到:

merged:
/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/merged

upperdir:
/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff

workdir:
/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/work

lowerdir:
/var/lib/docker/overlay2/l/XZ6TYQO6XXCBXKH6E4N3N7QTWU:...:.../overlay2/l/IG6UG3GF2PLSFP6C6X7AO
冒号分隔多个lowerdir,从左到右层次越低。

细心的你肯定发现了,lowerdir有7个,但是怎么看起来这么奇怪呢,其实观察下/var/lib/docker/overlay2/l/就会发现,正是之前我们得到的那6个rootfs目录的链接文件外加一个新的容器目录文件。

1
2
3
4
5
6
7
8
9
10
11
[root@slions_pc1 overlay2]# ll /var/lib/docker/overlay2/l/
总用量 0
lrwxrwxrwx. 1 root root 72 7月 12 15:59 4PZ2UIG6UG3GF2PLSFP6C6X7AO -> ../45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be/diff
lrwxrwxrwx. 1 root root 72 7月 12 15:59 5KKQYPGMGGXBSLAIEHV72IJ2QH -> ../de169f3af7d5f47b3d83eeb21408ecb637dd5fd4847d31f7644a72830c16db65/diff
lrwxrwxrwx. 1 root root 72 7月 12 16:00 AD3GPWKO46I5LBSKACROCGTCFN -> ../0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff
lrwxrwxrwx. 1 root root 72 7月 12 15:59 KRCQJWNO5RDBHNQOQXFCLT2JLX -> ../e78f609099df22f4ab410322759923ebab67112a3a88fc323b4bdeb98b00d8d2/diff
lrwxrwxrwx. 1 root root 72 7月 12 15:59 P35XQEQ2NQELR5QWTI3POCZPJG -> ../18998b6cffbcc8eb5984c1077e00d02d6d2f150a889c8c856055364bfa16b0a2/diff
lrwxrwxrwx. 1 root root 72 7月 12 15:59 U6O63XRKJYL3MKFKZCZ33EEDE6 -> ../192d460630fe2fefa59151b3a7b37032346487ac6af719d3be3f2e1dfd373494/diff
lrwxrwxrwx. 1 root root 77 7月 12 16:00 XZ6TYQO6XXCBXKH6E4N3N7QTWU -> ../0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232-init/diff
lrwxrwxrwx. 1 root root 72 7月 12 15:59 Y2XZUZZM42C6HPF6BHHLAMGY2G -> ../aca1597583b8862fae70d6bfc7487a041092a7b9d1cf400802d11225b2fb3f5b/diff

overlay实验

创建并挂载测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@slions_pc1 ~]# mkdir tmp
[root@slions_pc1 ~]# cd tmp/
[root@slions_pc1 tmp]# mkdir -p lower{1..2}/dir upper/dir worker merge
[root@slions_pc1 tmp]# ll
总用量 0
drwxr-xr-x. 3 root root 17 7月 12 16:42 lower1
drwxr-xr-x. 3 root root 17 7月 12 16:42 lower2
drwxr-xr-x. 2 root root 6 7月 12 16:42 merge
drwxr-xr-x. 3 root root 17 7月 12 16:42 upper
drwxr-xr-x. 2 root root 6 7月 12 16:42 worker
[root@slions_pc1 tmp]# for i in 1 2 ;do touch lower$i/foo$i;done
[root@slions_pc1 tmp]# tree
.
├── lower1
│   ├── dir
│   └── foo1
├── lower2
│   ├── dir
│   └── foo2
├── merge
├── upper
│   └── dir
└── worker
[root@slions_pc1 tmp]# mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=worker merge/
[root@slions_pc1 tmp]# mount |grep overlay
...
overlay on /root/tmp/merge type overlay (rw,relatime,seclabel,lowerdir=lower1:lower2,upperdir=upper,workdir=worker)

挂载了一个名为overlay的overlay类型的文件系统,挂载点为merged目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@slions_pc1 tmp]# touch upper/foo3
[root@slions_pc1 tmp]# echo "from lower1" >lower1/dir/aa
[root@slions_pc1 tmp]# echo "from lower2" >lower2/dir/aa
[root@slions_pc1 tmp]# echo "from lower1 bb" >lower2/dir/bb
[root@slions_pc1 tmp]# echo "from upper" >upper/dir/bb
[root@slions_pc1 tmp]# tree
.
├── lower1
│   ├── dir
│   │   └── aa
│   └── foo1
├── lower2
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   └── foo2
├── merge
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   ├── foo1
│   ├── foo2
│   └── foo3
├── upper
│   ├── dir
│   │   └── bb
│   └── foo3
└── worker
└── work
[root@slions_pc1 tmp]# cat merge/dir/aa
from lower1
[root@slions_pc1 tmp]# cat merge/dir/bb
from upper

删除文件测试

删除文件时,会在upper层创建whiteout的特殊文件,overlayfs会自动过过滤掉和whiteout文件自身以及和它同名的lower层文件和目录,达到了隐藏文件的目的,让用户以为文件已经被删除了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[root@slions_pc1 tmp]# tree
.
├── lower1
│   ├── dir
│   │   └── aa
│   └── foo1
├── lower2
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   └── foo2
├── merge
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   ├── foo1
│   ├── foo2
│   └── foo3
├── upper
│   ├── dir
│   │   └── bb
│   └── foo3
└── worker
└── work

10 directories, 12 files
[root@slions_pc1 tmp]# rm merge/foo1
rm:是否删除普通空文件 "merge/foo1"?y
[root@slions_pc1 tmp]# tree
.
├── lower1
│   ├── dir
│   │   └── aa
│   └── foo1
├── lower2
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   └── foo2
├── merge
│   ├── dir
│   │   ├── aa
│   │   └── bb
│   ├── foo2
│   └── foo3
├── upper
│   ├── dir
│   │   └── bb
│   ├── foo1
│   └── foo3
└── worker
└── work


参考文档

Use the OverlayFS storage driver:https://docs.docker.com/storage/storagedriver/overlayfs-driver/#how-the-overlay2-driver-works

Docker storage drivers:https://docs.docker.com/storage/storagedriver/select-storage-driver/

Docker存储驱动之–overlay2:https://www.jianshu.com/p/3826859a6d6e

深入理解overlayfs(二):使用与原理分析:https://blog.csdn.net/linshenyuan1213/article/details/82527712

CATALOG
  1. 1. Overlay2
    1. 1.1. overlay存储流程
    2. 1.2. overlay实验
  2. 2. 参考文档