笔者在《Docker 镜像之进阶篇》中介绍了镜像分层、写时复制以及内容寻址存储(content-addressable storage)等技术特性,为了支持这些特性,docker 设计了一套镜像元数据管理机制来管理镜像元数据。另外,为了能够让 docker 容器适应不同平台不同应用场景对存储的要求,docker 提供了各种基于不同文件系统实现的存储驱动来管理实际镜像文件。
本文我们就来介绍 docker 如何管理镜像元数据,以及如何通过存储驱动来管理实际的容器镜像文件。
Docker 镜像元数据管理Docker 镜像在设计上将镜像元数据和镜像文件的存储完全隔离开了。Docker 在管理镜像层元数据时采用的是从上至下 repository、image 和 layer 三个层次。由于 docker 以分层的形式存储镜像,所以 repository 和 image 这两类元数据并没有物理上的镜像文件与之对应,而 layer 这种元数据则存在物理上的镜像层文件与之对应。接下来我们就介绍这些元数据的管理与存储。
repository 元数据
repository 是由具有某个功能的 docker 镜像的所有迭代版本构成的镜像库。Repository 在本地的持久化文件存放于 /var/lib/docker/image/<graph_driver>/repositories.json 中,下图显示了 docker 使用 aufs 存储驱动时 repositories.json 文件的路径:
我们可以通过 vim 查看 repositories.json 的内容,并通过命令 :%!python -m json.tool 进行格式化:
文件中存储了所有本地镜像的 repository 的名字,比如 ubuntu ,还有每个 repository 下的镜像的名字、标签及其对应的镜像 ID。当前 docker 默认采用 SHA256 算法根据镜像元数据配置文件计算出镜像 ID。上图中的两条记录本质上是一样的,第二条记录和第一条记录指向同一个镜像 ID。其中 sha256:c8c275751219dadad8fa56b3ac41ca6cb22219ff117ca98fe82b42f24e1ba64e 被称为镜像的摘要,在拉取镜像时可以看到它:
镜像的摘要(Digest)是对镜像的 manifest 内容计算 sha256sum 得到的。我们也可以直接指定一个镜像的摘要进行 pull 操作:
$ docker pull ubuntu@sha256:c8c275751219dadad8fa56b3ac41ca6cb22219ff117ca98fe82b42f24e1ba64e
这和 docker pull ubuntu:latest 是一样的(当然,如果镜像被更新了,就会有新的摘要来对应 ubuntu:latest)。
image 元数据
image 元数据包括了镜像架构(如 amd64)、操作系统(如 linux)、镜像默认配置、构建该镜像的容器 ID 和配置、创建时间、创建该镜像的 docker 版本、构建镜像的历史信息以及 rootfs 组成。其中构建镜像的历史信息和 rootfs 组成部分除了具有描述镜像的作用外,还将镜像和构成该镜像的镜像层关联了起来。Docker 会根据历史信息和 rootfs 中的 diff_ids 计算出构成该镜像的镜像层的存储索引 chainID,这也是 docker 1.10 镜像存储中基于内容寻址的核心技术。
镜像 ID 与镜像元数据之间的映射关系以及元数据被保存在文件 /var/lib/docker/image/<graph_driver>/imagedb/content/sha256/<image_id> 中。
452a96d81c30a1e426bc250428263ac9ca3f47c9bf086f876d11cb39cf57aeec 就是镜像的ID。其内容如下(简洁起见,省略中间大部分的内容):
它包含所有镜像层信息的 rootfs(见上图的 rootfs 部分),docker 利用 rootfs 中的 diff_id 计算出内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容。注意,每个 diff_id 对应一个镜像层。上面的 diff_id 的排列也是有顺序的,从上到下依次表示镜像层的最低层到最顶层: