Storage driver
Docker使用storage driver(存储驱动程序)来管理image和container的数据,不管是image的只读layer还是container的可写layer都基于这些storage driver的特点来设计实现。目前支持的storage driver有aufs、OverlayFS和Brtfs等。在满足版本约束的条件下,我们可以修改配置来使docker主机使用新的storage driver,但是改变storage driver将导致本地已经存在的container和image不可访问,所以最好是先用docker save保存并push到repository。
这些storage driver一般需要特定的后端文件系统(backing filesystem)的支持,支持列表如下:
Storage driver | Supported backing filesystems |
---|---|
overlay2 , overlay | xfs with ftype=1, ext4 |
aufs | xfs , ext4 |
devicemapper | direct-lvm |
btrfs | btrfs |
zfs | zfs |
vfs | any filesystem |
本文主要对aufs和OverlayFS两种联合文件系统进行讨论,更多storage driver的内容请参考https://docs.docker.com/storage/storagedriver/
aufs
aufs结构
aufs是一种联合文件系统,它将主机上的多个目录进行分层,然后提供统一的访问目录,在aufs的术语中,这些目录叫做branch,和docker的layer是一个概念。下面是Ubuntu的container和image在aufs中的结构层次。
可以通过/proc/filrsystems查询系统内核是否支持aufs,如果支持,我们可以通过修改/etc/docker/daemon.json配置或者加上docker启动参数--storage-driver来将docker的storage driver配置成aufs。
/var/lib/docker/aufs/的目录结构如下:
- diff:image layer和container layer的内容都保存在diff目录下的一个独立目录下,运行container时,diff里面将会创建一个可写的container layer和一个只读的container layer(*-init),container中创建的或者修改的文件都会放在可写的container layer,后面的描述不再区分这两个container layer。
- layers:保存所有layer的metadata,每个layer的metadata保存了继承的底层layer的ID,并按顺序存储。
- mnt:挂载点。
OverlayFS
OverlayFS是一种现代联合文件系统,现有overlay和overlay2两个版本作为docker的storage driver。下面的demo主要使用overlay2来描述。
一般情况下,overlay2只能在Linux kernel4.0及以上版本使用,如果是Docker EE运行在RHEL或者CentOS上,内核需要3.10.0-514或者更高的版本。
OverlayFS结构
对于overlay2,Image layer和container layer都放在/var/lib/docker/overlay2目录,一个以uuid命名的目录就是一个layer。l目录包含缩短的layer标识符作为符号链接,这些标识符用于避免在mount命令的参数上达到page size的限制。
root@hunk-virtual-machine:/var/lib/docker/overlay2# ll
total 40
drwx------ 10 root root 4096 12月 4 04:30 ./
drwx--x--x 15 root root 4096 12月 4 04:21 ../
drwx------ 4 root root 4096 12月 4 04:20 06dc2d5fef9560027592f841f91cb27f6fdcfae1515ee2eec8fd39e7c4fb4808/
drwx------ 4 root root 4096 12月 4 04:20 0c30b3a8d67e7a9b50e4209411cde9fc3d7d3eaacc8fa8c7eeebb29c7919fe7f/
drwx------ 3 root root 4096 12月 4 04:20 1997d30d6490ce04df5c79cb45085bd901a3255f33ca9ce81862caed98965780/
drwx------ 4 root root 4096 12月 4 04:20 445b76bec3697bde9929018a2541fa9470ba32fba409cacd052cedcc0ad831b9/
drwx------ 4 root root 4096 12月 4 04:20 4de4db8a8d3bdcdd3d385ed99cd8a7b565c9c51c7b05d08de6fe9d4212ee40ba/
drwx------ 4 root root 4096 12月 4 04:20 bc51237962b67512dc7d1c918447f3762f3e649f1990428756f13963fb871dee/
drwx------ 4 root root 4096 12月 4 04:20 e84be5ef3df8c4ce3548c5685a8c0e37a2d3d7c270b14f331e710a12c308a04c/
drwx------ 2 root root 4096 12月 4 04:30 l/
root@hunk-virtual-machine:/var/lib/docker/overlay2# ll l/
total 36
drwx------ 2 root root 4096 12月 4 04:30 ./
drwx------ 10 root root 4096 12月 4 04:30 ../
lrwxrwxrwx 1 root root 72 12月 4 04:20 2IJH4TC7VQPAGELW6GBMQF74QM -> ../06dc2d5fef9560027592f841f91cb27f6fdcfae1515ee2eec8fd39e7c4fb4808/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 5CQDP2JAIQMD53GCNKDQYVPOP2 -> ../0c30b3a8d67e7a9b50e4209411cde9fc3d7d3eaacc8fa8c7eeebb29c7919fe7f/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 GWPYHG3HF5KUJYRQUYWISRGIUX -> ../4de4db8a8d3bdcdd3d385ed99cd8a7b565c9c51c7b05d08de6fe9d4212ee40ba/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 LHHYDMCHKRF6UHQND5MDVTUQFD -> ../1997d30d6490ce04df5c79cb45085bd901a3255f33ca9ce81862caed98965780/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 LTPZS3YG6JTXTVYDOLRRKMVGVE -> ../bc51237962b67512dc7d1c918447f3762f3e649f1990428756f13963fb871dee/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 MAOE2MDSYVZHGSFEMI65J6H5HM -> ../445b76bec3697bde9929018a2541fa9470ba32fba409cacd052cedcc0ad831b9/diff/
lrwxrwxrwx 1 root root 72 12月 4 04:20 VPYQWDS3SFNRMYYV4GX3YI5KGW -> ../e84be5ef3df8c4ce3548c5685a8c0e37a2d3d7c270b14f331e710a12c308a04c/diff/
每个layer的目录下都会有如下的文件目录结构:
- diff:目录,包含这一层的内容。所有layer目录都有diff目录。
- link:文件,保存了l目录下对应的符号链接。所有layer目录都有link文件。
- lower:文件,顺序保存了父层layer的符号链接,记录了各个layer之间的层次关系。显然,最底层的layer不需要这个文件。
- work:目录。
root@hunk-virtual-machine:/var/lib/docker/overlay2# ls 0c30b3a8d67e7a9b50e4209411cde9fc3d7d3eaacc8fa8c7eeebb29c7919fe7f/
diff link lower work
root@hunk-virtual-machine:/var/lib/docker/overlay2# cat e84be5ef3df8c4ce3548c5685a8c0e37a2d3d7c270b14f331e710a12c308a04c/link
VPYQWDS3SFNRMYYV4GX3YI5KGWroot@hunk-virtual-machine:/var/lib/docker/overlay2# ll l/ |grep VPY
lrwxrwxrwx 1 root root 72 12月 4 04:20 VPYQWDS3SFNRMYYV4GX3YI5KGW -> ../e84be5ef3df8c4ce3548c5685a8c0e37a2d3d7c270b14f331e710a12c308a04c/diff/
root@hunk-virtual-machine:/var/lib/docker/overlay2# cat 0c30b3a8d67e7a9b50e4209411cde9fc3d7d3eaacc8fa8c7eeebb29c7919fe7f/lower
l/VPYQWDS3SFNRMYYV4GX3YI5KGW:l/2IJH4TC7VQPAGELW6GBMQF74QM:l/LTPZS3YG6JTXTVYDOLRRKMVGVE:l/GWPYHG3HF5KUJYRQUYWISRGIUX:l/LHHYDMCHKRF6UHQND5MDVTUQFDroot@hunk-virtual-machine:/var/lib/docker/overlay2# ll l/ |grep LHHYDMCHKRF6UHQND5MDVTUQFD
lrwxrwxrwx 1 root root 72 12月 4 04:20 LHHYDMCHKRF6UHQND5MDVTUQFD -> ../1997d30d6490ce04df5c79cb45085bd901a3255f33ca9ce81862caed98965780/diff/
root@hunk-virtual-machine:/var/lib/docker/overlay2# ls 1997d30d6490ce04df5c79cb45085bd901a3255f33ca9ce81862caed98965780/
diff link
运行container后,用mount | grep overlay命令可以看到OverlayFS是如何组织这些layer的。启动container后,docker会为container创建一个可写layer和一个只读layer(*-init),这个可写layer下的merged目录是真正的挂载路径,用df -h可以查看。
container只读layer包含了container启动过程中的一些内容,container内部后续的change不会在写到这个layer里,我们可以把它可以看做container layer的一部分。
root@hunk-virtual-machine:/var/lib/docker/overlay2# mount |grep overlay
overlay on /var/lib/docker/overlay2/077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/merged type overlay (rw,relatime,
lowerdir=/var/lib/docker/overlay2/l/L6AFIKENS335GOTSK7GY3XJ43R:/var/lib/docker/overlay2/l/MAOE2MDSYVZHGSFEMI65J6H5HM:/var/lib/docker/overlay2/l/5CQDP2JAIQMD53GCNKDQYVPOP2:/var/lib/docker/overlay2/l/VPYQWDS3SFNRMYYV4GX3YI5KGW:/var/lib/docker/overlay2/l/2IJH4TC7VQPAGELW6GBMQF74QM:/var/lib/docker/overlay2/l/LTPZS3YG6JTXTVYDOLRRKMVGVE:/var/lib/docker/overlay2/l/GWPYHG3HF5KUJYRQUYWISRGIUX:/var/lib/docker/overlay2/l/LHHYDMCHKRF6UHQND5MDVTUQFD,
upperdir=/var/lib/docker/overlay2/077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/diff,
workdir=/var/lib/docker/overlay2/077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/work)
在container内创建新的文件/app/app.py.bak后,container layer的merged目录和diff目录都会有这个文件。
root@hunk-virtual-machine:/var/lib/docker/overlay2# find ./ -name app.py.bak
./077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/merged/app/app.py.bak
./077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/diff/app/app.py.bak
查看overlay2各个目录所占空间的size,总共279M,启动container后新增的目录占用138M,而其中的merged目录占了138M。merged目录下的所有内容都以mount point的形式挂载到container内部,所以container内部看到的内容和merged目录是一致的。因此,container layer的merged目录的内容就是diff目录内容和其他layer的diff目录的并集。
root@hunk-virtual-machine:/var/lib/docker/overlay2# du -h --max-depth=1 ./
138M ./077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9
40K ./l
8.3M ./06dc2d5fef9560027592f841f91cb27f6fdcfae1515ee2eec8fd39e7c4fb4808
7.5M ./4de4db8a8d3bdcdd3d385ed99cd8a7b565c9c51c7b05d08de6fe9d4212ee40ba
24K ./e84be5ef3df8c4ce3548c5685a8c0e37a2d3d7c270b14f331e710a12c308a04c
52M ./bc51237962b67512dc7d1c918447f3762f3e649f1990428756f13963fb871dee
60M ./1997d30d6490ce04df5c79cb45085bd901a3255f33ca9ce81862caed98965780
40K ./077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9-init
14M ./445b76bec3697bde9929018a2541fa9470ba32fba409cacd052cedcc0ad831b9
36K ./0c30b3a8d67e7a9b50e4209411cde9fc3d7d3eaacc8fa8c7eeebb29c7919fe7f
279M ./
root@hunk-virtual-machine:/var/lib/docker/overlay2/077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9# du -h --max-depth=1 ./
138M ./merged
344K ./diff
8.0K ./work
138M ./
Container文件操作
首次写入文件
当container第一次向已经存在的文件写入change时,这个文件在upperdir中还不存在,所以overley/overlay2 driver执行
copy-up操作将文件从lowerdir拷贝到upperdir,然后将change写到container layer中的文件副本。
OverleyFS工作在文件级而不是块级,也就是说copy-up操作会拷贝整个文件,无论这个文件的change很大还是很小,这样对container的写性能就会有明显的影响。但是同样有两点值得考虑:
- copy-up操作只会在第一次写入change时执行,后续因为container layer中已经有该文件就不需要再执行copy-up。
- OverlayFS只对两个layer有效。这意味着性能应该优于AUFS,因为当在具有多个layer的image中搜索文件时,AUFS可能会出现明显的延迟。这个优点既适用于overlay driver和overlay2 driver。在初始读取时,overlayfs2的性能略低于overlayfs,因为它必须查看更多的层,但它会缓存结果,因此这只是一个很小的损失。
删除文件和目录
在container里删除lowerdir中的文件时,会在upperdir里创建一个whiteout file,lowerdir中的文件并不会删除(read-only),但是这个whiteout file使得lowerdir中的文件对于container是不可用的,也就是看起来文件被删除了。container中删除目录时,和删除文件类似,只是创建的是opaque directory。作为demo,在Nginx container里将/app/app.py改成/app/app.py.bak,可以看到upperdir下的这个特殊的字符型文件whiteout file(app.py)。
root@hunk-virtual-machine:/var/lib/docker/overlay2/077a6b9c1ba4b66093f6337a4998ffc7b56aec2866e32564b5206560e64db4b9/diff# ll app/
total 12
drwxr-xr-x 2 root root 4096 12月 13 21:46 ./
drwxr-xr-x 5 root root 4096 12月 13 21:13 ../
c--------- 1 root root 0, 0 12月 13 21:46 app.py
-rw-r--r-- 1 root root 665 10月 10 09:35 app.py.bak
OverlayFS和Docker性能
overlay2和overlay的性能都要好于aufs和devicemapper,在一定条件下overlay2 的性能可能比btrfs更好。
影响性能的方面
- 页缓存:OverlayFS支持页缓存共享,当多个container访问同一个文件时共享页缓存,所以OverlayFS的内存利用更加高效。
- 写时复制:OverlayFS和aufs在首次写入文件时都会执行copy-up操作,但是因为aufs支持的layer比OverlayFS更多,所以会有更大的延迟。
- inode限制:overlay可能会造成inode被用尽,尤其是docker主机上有大量的image和container时。增加inode数目的唯一办法时重新格式化文件系统,这显然会带来更多的问题。因为overlay2相比overlay对inode的利用更加有效,因此推荐尽量使用overlay2。
性能最佳实践
- 使用更快的存储,比如SSD。
- 对于写密集的工作负载,建议使用volume。volume提供最好的性能,因为volume绕过了storage driver,并且不会引入copy-up带来的开销。
OverlayFS兼容性约束
OverlayFS只是实现了POSIX标准的一个子集,导致OverlayFS的一些操作结果会与POSIX标准不一致。
- 两次open调用的问题:container里的应用程序中调用fd1=open("foo", O_RDONLY),然后调用fd2=open("foo", O_RDWR),期望fd1和fd2指向同一个文件。但是因为copy-up操作,就会造成fd2指向不同的文件。因为以只读模式打开,fd1指向lowerdir里面的文件,而fd2因为是以读写模式打开文件(执行了写时复制操作)所以指向了upperdir里的文件。当然,后续的open操作得到的fd都会执行upperdir目录中的文件。
- 重命名:Overlay2不完全支持rename系统调用,所以在程序中需要检测相关的错误并处理。