》中介绍了镜像分层、写时复制以及内容寻址存储(content-addressable storage)等技术特性,为了支持这些特性,docker 设计了一套镜像元数据管理机制来管理镜像元数据。另外,为了能够让 docker 容器适应不同平台不同应用场景对存储的要求,docker 提供了各种基于不同文件系统实现的存储驱动来管理实际镜像文件。
文件。
文件的存储完全隔离开了。Docker 在管理镜像层元数据时采用的是从上至下 repository、image 和 layer 三个层次。由于 docker 以分层的形式存储镜像,所以 repository 和 image 这两类元数据并没有物理上的镜像文件与之对应,而 layer 这种元数据则存在物理上的镜像层文件与之对应。接下来我们就介绍这些元数据的管理与存储。
功能的 docker 镜像的所有迭代版本构成的镜像库。Repository 在本地的持久化文件存放于 /var/lib/docker/image/
内容,并通过命令 :%!python -m json.tool 进行格式化:
文件中存储了所有本地镜像的 repository 的名字,比如 ubuntu ,还有每个 repository 下的镜像的名字、标签及其对应的镜像 ID。当前 docker 默认采用 SHA256 算法根据镜像元数据配置文件计算出镜像 ID。上图中的两条记录本质上是一样的,第二条记录和第一条记录指向同一个镜像 ID。其中 sha256:c8c275751219dadad8fa56b3ac41ca6cb22219ff117ca98fe82b42f24e1ba64e 被称为镜像的摘要,在拉取镜像时可以看到它:
摘要(Digest)是对镜像的 manifest 内容计算 sha256sum 得到的。我们也可以直接指定一个镜像的摘要进行 pull 操作:摘要来对应 ubuntu:latest)。
包括了镜像架构(如 amd64)、操作系统(如 linux)、镜像默认配置、构建该镜像的容器 ID 和配置、创建时间、创建该镜像的 docker 版本、构建镜像的历史信息以及 rootfs 组成。其中构建镜像的历史信息和 rootfs 组成部分除了具有描述镜像的作用外,还将镜像和构成该镜像的镜像层关联了起来。Docker 会根据历史信息和 rootfs 中的 diff_ids 计算出构成该镜像的镜像层的存储索引 chainID,这也是 docker 1.10 镜像存储中基于内容寻址的核心技术。文件 /var/lib/docker/image/
452a96d81c30a1e426bc250428263ac9ca3f47c9bf086f876d11cb39cf57aeec 就是镜像的ID。其内容如下(简洁起见,省略中间大部分的内容):
内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容。注意,每个 diff_id 对应一个镜像层。上面的 diff_id 的排列也是有顺序的,从上到下依次表示镜像层的最低层到最顶层:
文件包。用户在 docker 宿主机上下载了某个镜像层之后,docker 会在宿主机上基于镜像层文件包和 image 元数据构建本地的 layer 元数据,包括 diff、parent、size 等。而当 docker 将在宿主机上产生的新的镜像层上传到 registry 时,与新镜像层相关的宿主机上的元数据也不会与镜像层一块打包上传。内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、父镜像层 parent、graphdriver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。这些元数据被保存在 /var/lib/docker/image/
内容为:
文件的内容即 diffID,其内容就是 image 元数据中对应层的 diff_id)。chainID 和父镜像层 parent 需要从所属 image 元数据中计算得到。而 cacheID 是在当前 docker 宿主机上随机生成的一个 uuid,在当前的宿主机上,cacheID 与该镜像层一一对应,用于标识并索引 graphdriver 中的镜像层文件:
属性中,diffID 采用 SHA256 算法,基于镜像层文件包的内容计算得到。而 chainID 是基于内容存储的索引,它是根据当前层与所有祖先镜像层 diffID 计算出来的,具体算如下:
- 加上一个空格和当前层的 diffID,再计算 SHA256 校验码。
内容主要为索引某个容器的可读写层(也叫容器层)的 ID(也对应容器层的 ID)、容器 init 层在 graphdriver 中的ID(initID)、读写层在 graphdriver 中的 ID(mountID) 以及容器层的父层镜像的 chainID(parent)。相关文件位于 /var/lib/docker/image/
支持提供了针对某种文件系统的初始化操作以及对镜像层的增、删、改、查和差异比较等操作。目前存储系统的接口已经有 aufs、btrfs、devicemapper、voerlay2 等多种。在启动 docker deamon 时可以指定使用的存储驱动,当然指定的驱动必须被底层操作系统支持。
支持联合挂载的文件系统。简单来说就是支持将不同目录挂载到同一个目录下,这些挂载操作对用户来说是透明的,用户在操作该目录时并不会觉得与其他目录有什么不同。这些目录的挂载是分层次的,通常来说最上层是可读写层,下面的层是只读层。所以,aufs 的每一层都是一个普通的文件系统。文件 A 时,会从最顶层的读写层开始向下寻找,本层没有,则根据层之间的关系到下一层开始找,直到找到第一个文件 A 并打开它。文件 A 时,如果这个文件不存在,则在读写层新建一个,否则像上面的过程一样从顶层开始查找,直到找到最近的文件 A,aufs 会把这个文件复制到读写层进行修改。修改某个已有文件时,如果这个文件很大,即使只要修改几个字节,也会产生巨大的磁盘开销。删除一个文件时,如果这个文件仅仅存在于读写层中,则可以直接删除这个文件,否则就需要先删除它在读写层中的备份,再在读写层中创建一个 whiteout 文件来标志这个文件不存在,而不是真正删除底层的文件。文件时,如果这个文件在读写层存在对应的 whiteout 文件,则先将 whiteout 文件删除再新建。否则直接在读写层新建即可。
文件在本地存放在哪里呢?内容:
包括只读层和可读写层,所有这些层最终一起被挂载在 mnt 下面的目录上,layers 下为与每层依赖有关的层描述文件。
文件数据都在 diff 目录下。一个 docker 容器创建与启动的过程中,会在 /var/lib/docker/aufs 下面新建出对应的文件和目录。由于 docker 镜像管理部分与存储驱动在设计上完全分离了,。在 Linux 环境下,mountID 是随机生成的并保存在 mountedLayer 的元数据 mountID 中,持久化在 image/aufs/layserdb/mounts/
生成一个以当前容器对应的
文件的内容如下:
文件夹下是空的。
文件夹中可以看到挂载好的文件系统:
文件文件:
文件执行的操作。
文件都还存在。
生成一个新的 cacheID 命名的文件夹,它存放了最新的差异变化文件,这时一个新的镜像层就诞生了。而原来的以 mountID 为名的文件夹会继续存在,直至对应容器被删除。
文件存储。由于 docker 镜像管理部分与存储驱动在设计上的完全分离,使得这部分内容初看起来并不是那么直观。希望本文能对大家理解 docker 镜像及其存储有所帮助。