一、概述

    docker镜像采用分层分层构建设计,每层称为"layer", layer存放在/data/docker/存储驱动/目录下面

这些存储驱动有,AUFS,OverlayFS等,可以通过docker info命令查看存储驱动,centos7.1+默认采用OverlayFS模式.
二、OverlayFS介绍

OverlayFS是一种堆叠文件系统,建立在其他文件系统之上,并不参与磁盘底层划分,只是将底层文件系统目录"合并",实际是伪合并,只是呈现给用户好像一个文件系统结构,这也就是联合挂载技术,对比于AUFS,OverlayFS速度更快,实现更简单,因为OverlayFS只分两层,只读层,和读写层, Linux 内核为Docker提供的OverlayFS驱动有两种:overlay和overlay2。而overlay2是相对于overlay的一种改进,在inode利用率方面比overlay更有效。但是overlay有环境需求:docker版本17.06.02+,宿主机文件系统需要是ext4或xfs格式.

联合挂载技术:
overlayfs通过三个目录:lower目录、upper目录、以及work(不太理解这个目录怎么工作的)目录实现,其中lower目录可以是多个,work目录为工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见,最后联合挂载完成给用户呈现的统一视图称为为merged目录。以下使用mount将演示其如何工作的
docker 存储驱之overlayFS_第1张图片

然后使用mount联合挂载到/tmp/test 下:
mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=worker /tmp/test
挂载之后三个目录会合并成一个,并且相同文件名的文件会进行“覆盖”,这里覆盖并不是真正的覆盖,而是当合并时候目录中两个文件名称都相同时,merged(/tmp/test)层目录会显示离它最近层的文件(lowerdir=A:B,A离他最近)
看看挂载情况:
docker 存储驱之overlayFS_第2张图片

docker 存储驱之overlayFS_第3张图片

docker官网overlay驱动模型:
docker 存储驱之overlayFS_第4张图片

在上述图中可以看到三个层结构,即:lowerdir、uperdir、merged,其中lowerdir是只读的image layer,其实就是rootfs,对比我们上述演示的目录A和B,我们知道image layer可以分很多层,所以对应的lowerdir是可以有多个目录。而upperdir则是在lowerdir之上的一层,这层是读写层,在启动一个容器时候会进行创建,所有的对容器数据更改都发生在这里层,对比示例中的C。最后merged目录是容器的挂载点,也就是给用户暴露的统一视角,对比示例中的/tmp/test。而docker目录层都保存在了/var/lib/docker/overlay2/或者/var/lib/docker/overlay/(如果使用overlay)

容器示例:
创建一个容器:
docker run -it centos /bin/bash
查看挂载情况:
docker 存储驱之overlayFS

容器修改数据时,overlay怎么工作?
读:
1.如果文件在容器层(upperdir),直接读取文件
2.如果文件不在容器层(upperdir),则从镜像层(lowerdir)读取
写:
1.首次写入: 如果在upperdir中不存在,overlay和overlay2执行copy_up操作,把文件从lowdir拷贝到upperdir,由于overlayfs是文件级别的(即使文件只有很少的一点修改,也会产生的copy_up的行为),后续对同一文件的在此写入操作将对已经复制到容器的文件的副本进行操作。这也就是常常说的写时复制(copy-on-write)
2.删除文件和目录: 当文件在容器被删除时,在容器层(upperdir)创建whiteout文件,镜像层(lowerdir)的文件是不会被删除的,因为他们是只读的,但without文件会阻止他们显示,当目录在容器内被删除时,在容器层(upperdir)一个不透明的目录,这个和上面whiteout原理一样,阻止用户继续访问,即便镜像层仍然存在

注意:
1.copy_up操作只发生在文件首次写入,以后都是只修改副本
2.容器层的文件删除只是伪删除,是靠whiteout文件将其遮挡,image层并没有删除,这也就是为什么使用docker commit 提交保存的镜像会越来越大,无论在容器层怎么删除数据,image层都不会改变

过程演示:
docker 存储驱之overlayFS_第5张图片

发现修改操作只在C目录发生也就是upperdir层发生

三、overlay2 镜像存储结构

docker pull ubuntu
docker 存储驱之overlayFS_第6张图片
四层layer存储位置:

这里面多了一个l目录包含了所有层的软连接,短链接使用短名称,避免mount时候参数达到页面大小限制(演示中mount命令查看时候的短目录)

处于底层的镜像目录包含了一个diff和一个link文件,diff目录存放了当前层的镜像内容,而link文件则是与之对应的短名称
docker 存储驱之overlayFS

在这之上的镜像还多了work目录和lower文件,lower文件用于记录父层的短名称,work目录用于联合挂载指定的工作目录。
docker 存储驱之overlayFS

而这些目录和镜像的关系是通过元数据关联。元数据分为image元数据和layer元数据:

image镜像原数据:

镜像元数据存储在了/var/lib/docker/image//imagedb/content/sha256/目录下,名称是以镜像ID命名的文件,镜像ID可通过docker images查看,这些文件以json的形式保存了该镜像的rootfs信息、镜像创建时间、构建历史信息、所用容器、包括启动的Entrypoint和CMD等等:

ubuntu镜像id
docker 存储驱之overlayFS_第7张图片

进入镜像元数据目录
cd /data/docker/image/overlay2/imagedb/content/sha256/
vim 775349758637aff77bf85e2ff0597e86e3e859183ef0baba8b3e8fc8d3cba51c
docker 存储驱之overlayFS_第8张图片
上面的 diff_id 对应的的是一个镜像层,其排列也是有顺序的,从上到下依次表示镜像层的最低层到最顶层

docker 利用 rootfs 中的每个diff_id 和历史信息计算出与之对应的内容寻址的索引(chainID) ,而chaiID则关联了layer层,进而关联到每一个镜像层的镜像文件

layer元数据:

镜像层只包含一个具体的镜像层文件包。用户在 docker 宿主机上下载了某个镜像层之后,docker 会在宿主机上基于镜像层文件包和 image 元数据构建本地的 layer 元数据,包括 diff、parent、size 等。而当 docker 将在宿主机上产生的新的镜像层上传到 registry 时,与新镜像层相关的宿主机上的元数据也不会与镜像层一块打包上传。
Docker 中定义了 Layer 和 RWLayer 两种接口,分别用来定义只读层和可读写层的一些操作,又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、父镜像层 parent、storage_driver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。这些元数据被保存在

docker 存储驱之overlayFS_第9张图片

每个chainID目录下会存在三个文件cache-id、diff、size
docker 存储驱之overlayFS
cache-id文件:
docker随机生成的uuid,内容是保存镜像层的目录索引,也就是/var/lib/docker/overlay2/中的目录,这就是为什么通过chainID能找到对应的layer目录。以chainID为565879c6effe6a013e0b2e492f182b40049f1c083fc582ef61e49a98dca23f7e 对应的目录为 0b7bbc608eca835ac9bdb31a1794016a8fd1dacc8f58fbcf2763db1dc40d3f32,也就保存在/data/docker/overlay2/0b7bbc608eca835ac9bdb31a1794016a8fd1dacc8f58fbcf2763db1dc40d3f32
docker 存储驱之overlayFS_第10张图片

diff文件:
保存了镜像元数据中的diff_id(与元数据中的diff_ids中的uuid对应
docker 存储驱之overlayFS

size文件:
保存了镜像层的大小
docker 存储驱之overlayFS

在 layer 的所有属性中,diffID 采用 SHA256 算法,基于镜像层文件包的内容计算得到。而 chainID 是基于内容存储的索引,它是根据当前层与所有祖先镜像层 diffID 计算出来的,具体算如下:
1.如果该镜像层是最底层(没有父镜像层),该层的 diffID 便是 chainID
2.该镜像层的 chainID 计算公式为 chainID(n)=SHA256(chain(n-1) diffID(n)),也就是根据父镜像层的 chainID 加上一个空格和当前层的 diffID,再计算 SHA256 校验码
mountedLayer 信息存储的可读init层以及容器挂载点信息包括:容器 init 层ID(init-id)、联合挂载使用的ID(mount-id)以及容器层的父层镜像的 chainID(parent)。相关文件位于/var/lib/docker/image//layerdb/mounts// 目录下

docker 存储驱之overlayFS_第11张图片
可以看到initID是在mountID后加了一个-init,同时initID就是存储在/var/lib/docker/overlay2/的目录名称:
docker 存储驱之overlayFS_第12张图片
查看mountID还可以直接通过mount命令查看对应挂载的mountID,对应着/data/docker/overlay2/目录,这也是overlayfs呈现的merged目录
docker 存储驱之overlayFS_第13张图片

在容器中创建一个文件,在merged目录将能看到:
docker 存储驱之overlayFS

关于init层:
init层是以一个uuid+-init结尾表示,夹在只读层和读写层之间,作用是专门存放/etc/hosts、/etc/resolv.conf等信息,需要这一层的原因是当容器启动时候,这些本该属于image层的文件或目录,比如hostname,用户需要修改,但是image层又不允许修改,所以启动时候通过单独挂载一层init层,通过修改init层中的文件达到修改这些文件目的。而这些修改往往只读当前容器生效,而在docker commit提交为镜像时候,并不会将init层提交。该层文件存放的目录为/data/docker/overlay2//diff