Docker在Linux上支持很多存储驱动,每种驱动都有自己的镜像分层、镜像层共享以及写时复制(CoW)技术的具体实现。
Docker存储基础技术
镜像分层
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。默认Docker镜像由多个只读层镜像叠加而成,启动容器后,Docker会加载只读镜像层,并再顶部添加一个读写层,并通过写时复制的方式,来写读写层
镜像层共享
多个镜像之间可以并且确实会共享镜像层,这样可以有效节省空间 并提升性能。例如多个centos7的镜像可以共享centos7的基础image,因为基础image大家都一样
写时复制CoW
CoW就是copy-on-write,表示只在需要写时才去复制,这个是针对已有文件的修改场景。比如基于一个image启动多个Container,如果为每个Container都去分配一个image一样的文件系统,那么将会占用大量的磁盘空间。而CoW技术可以让所有的容器共享image的文件系统,所有数据都从image中读取,只有当要对文件进行写操作时,才从image里把要写的文件复制到自己的文件系统进行修改。所以无论有多少个容器共享同一个image,所做的写操作都是对从image中复制到自己的文件系统中的复本上进行,并不会修改image的源文件,且多个容器操作同一个文件,会在每个容器的文件系统里生成一个复本,每个容器修改的都是自己的复本,相互隔离,相互不影响。使用CoW可以有效的提高磁盘的利用率。
非持久性存储
每个Docker容器都有自己的非持久化存储,即容器的本地存储,默认情况这是容器全部文件和文件系统保存的地方。非持久化存储自动创建,从属于容器,生命周期与容器相同,这意味着删除容器也会删除全部非持久化数据。
持久性存储
在redhat和centos环境,官方主要支持使用如下几种存储驱动。性能最好的为overlay2,目前官方在redhat和centos环境,推荐使用overlay2。
devicemapper驱动
在linux2.6内核版本中并入内核,devicemapper将所有的镜像和容器存储在自己的虚拟块设备上,devicemapper工作在块层次上而不是文件层次上。在Docker 17.06以及更高的版本中可以配置direct-lvm 作为存储驱动,这种模式下通过使用基于裸块设备 (Raw Block Device)的LVM精简池(LVM thin pool)来获取更好的性能。
overlay驱动
如上图所示,Overlay在主机上用到2个目录,这2个目录被看成是overlay的层。upperdir为容器层、lowerdir为镜像层使用联合挂载技术将它们挂载在同一目录(merged)下,提供统一视图。
overlay只使用2层,意味着多层镜像不会被实现为多个OverlayFS层。每个镜像被实现为自己的目录,这个目录在路径/var/lib/docker/overlay下。硬链接被用来索引和低层共享的数据,节省了空间,但是会有inode占用过多的问题。
当创建一个容器时,overlay驱动连接代表镜像层顶层的目录(只读)和一个代表容器层的新目录(读写)。
overlay2驱动
docker的overlay2驱动需要在kernel 3.10.0-514以上和Docker 17.06以上版本支持。而centos/redhat7系统默认内核为3.10.*。overlay2可以直接造成muitiple lower layers(最大128层)不用像overlay一样要通过硬链接的方式共享数据。
在overlay2每层的内容都是不一样的,diff是文件系统的统一挂载点,link文件描述的是该层的标识符,lower文件描述了层与层之间的组织关系,overlay2是将底层多个lowerdir和upperdir和workdir联合挂载,形成最终的merged挂载点。
如下为ubuntu:18.04的镜像
/var/lib/docker/overlay2/
|-- 6e599f35a3366d95287390fc00183a80bfc9a34492aaf9694836ec7b5722d0b6
| |-- diff
| |-- link
| |-- lower
| |-- merged
| `-- work
|-- 6f66846fe6a29834193bf36380eb1664947f21435edd8ce8fbc9b79dea92c51b
| |-- diff
| |-- link
| |-- lower
| |-- merged
| `-- work
|-- a1869d02b056d66742372c4b6d31d64f98b477590cdd76a842a653424f46710b
| |-- diff
| |-- link
| |-- lower
| |-- merged
| `-- work
|-- backingFsBlockDev
|-- da05539957d703ae81cf279c76059c947c96bd1f91fd24691b9e7630dcb13ff7
| |-- diff
| `-- link
`-- l
|-- 2LYVTSJQWPVOB46BCA74GFHJ7T -> ../da05539957d703ae81cf279c76059c947c96bd1f91fd24691b9e7630dcb13ff7/diff
|-- HEXAQW5O23XL6HHRVOMKWGAI4Z -> ../6f66846fe6a29834193bf36380eb1664947f21435edd8ce8fbc9b79dea92c51b/diff
|-- KXIOHK4OI6PPPFZ56I3ZORNHS7 -> ../6e599f35a3366d95287390fc00183a80bfc9a34492aaf9694836ec7b5722d0b6/diff
`-- PHQQ6RCEXR6BLZJGKBP6GYS55Q -> ../a1869d02b056d66742372c4b6d31d64f98b477590cdd76a842a653424f46710b/diff
overlay相比overlay2要更加占用inode
1、overlay只支持两层lowerdir和upperdir,并且只支持一个lowerdir,所以如果你的容器镜像有多层的话,层与层之前的数据共享是通过硬链接来实现的,我们也知道硬链接本身是同一个inode但不同的文件名而已,但为什么还是会大量消耗inode这里简单做的实验
我们在一台配置了storage-driver的机器上PULL ubuntu:18.04镜像
[root@master dir]# docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
32802c0cfa4d: Pull complete
da1315cffa03: Pull complete
fa83472a3562: Pull complete
f85999a86bef: Pull complete
Digest: sha256:48eb09a5c832172357f172593ce5e98e027814f758f6aeaef59a7fd658e50d49
Status: Downloaded newer image for ubuntu:18.04
整个镜像是一个四层镜像层组成的镜像,在/var/lib/docker/overlay/下每层对应一个目录,通过tree命令可以看见每个目录内只包含一个root文件夹
tree -L 2 /var/lib/docker/overlay/
/var/lib/docker/overlay/
|-- 0a9cd41c44a802a325bfc5bfdda757bced8eaf69a8d6004e5d6fcb730591ab31
| `-- root
|-- 1f9c95c6642a98930a14d914ac9bcfe9535b5a604dc27851cd76266326c76ed7
| `-- root
|-- 2d79f688e1bf505ef20100f7450ad7e5ea550500bd07a17b7b9b08513fc96988
| `-- root
`-- e8b1fcddec600628a75964619da3d0de7310fcbd6350b0c07ddf038d71c25d8b
`-- root
8 directories, 0 files
这个root目录内包含的是该层独有的文件和根lowdir共享的数据硬链接,这里看见共享的数据他们本身都是通过硬链接方式连接起来的。
[root@master overlay]# ls -i /var/lib/docker/overlay/0a9cd41c44a802a325bfc5bfdda757bced8eaf69a8d6004e5d6fcb730591ab31/root/bin/ls
70832997 /var/lib/docker/overlay/0a9cd41c44a802a325bfc5bfdda757bced8eaf69a8d6004e5d6fcb730591ab31/root/bin/ls
[root@master overlay]# ls -i /var/lib/docker/overlay/1f9c95c6642a98930a14d914ac9bcfe9535b5a604dc27851cd76266326c76ed7/root/bin/ls
70832997 /var/lib/docker/overlay/1f9c95c6642a98930a14d914ac9bcfe9535b5a604dc27851cd76266326c76ed7/root/bin/ls
[root@master overlay]# ls -i /var/lib/docker/overlay/2d79f688e1bf505ef20100f7450ad7e5ea550500bd07a17b7b9b08513fc96988/root/bin/ls
70832997 /var/lib/docker/overlay/2d79f688e1bf505ef20100f7450ad7e5ea550500bd07a17b7b9b08513fc96988/root/bin/ls
[root@master overlay]# ls -i /var/lib/docker/overlay/e8b1fcddec600628a75964619da3d0de7310fcbd6350b0c07ddf038d71c25d8b/root/bin/ls
70832997 /var/lib/docker/overlay/e8b1fcddec600628a75964619da3d0de7310fcbd6350b0c07ddf038d71c25d8b/root/bin/ls
但为什么overlay还是会占用大量inode呢?根本原因在于那些文件夹,每层的root目录内存放的都是完整的rootfs文件夹,但它们都是新建出来
的,它们inode都不一样,所以在overlay下一个容器镜像层数越多,占用的inode就越多
四,参考资料
https://docs.docker.com/storage/storagedriver/overlayfs-driver/
https://docs.docker.com/storage/storagedriver/select-storage-driver/#supported-backing-filesystems
https://www.bladewan.com/2018/01/25/docker_storage_driver/