Docker镜像的存储机制

Docker pull流程

  1. docker发送image的名称+tag(或者digest)给registry服务器,服务器根据收到的image的名称+tag(或者digest),找到相应image的manifest,然后将manifest返回给docker
  2. docker得到manifest后,读取里面image配置文件的digest(sha256),这个sha256码就是image的ID
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 7023,
    "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 32654,
      "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 16724,
      "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 73109,
      "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
    }
  ],
  "annotations": {
    "com.example.key1": "value1",
    "com.example.key2": "value2"
  }
}

layers.digest就是镜像的digest,不是layer的diff_id也不是chainId,如果打包格式为tar时,digest等于diff_id
3. 根据ID在本地找有没有存在同样ID的image,有的话就不用继续下载了
4. 如果没有,那么会给registry服务器发请求(里面包含配置文件的sha256和media type),拿到image的配置文件(Image Config)

{
    "created": "2015-10-31T22:22:56.015925234Z",
    "author": "Alyssa P. Hacker <[email protected]>",
    "architecture": "amd64",
    "os": "linux",
    "config": {
        "User": "alice",
        "ExposedPorts": {
            "8080/tcp": {}
        },
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "FOO=oci_is_a",
            "BAR=well_written_spec"
        ],
        "Entrypoint": [
            "/bin/my-app-binary"
        ],
        "Cmd": [
            "--foreground",
            "--config",
            "/etc/my-app.d/default.cfg"
        ],
        "Volumes": {
            "/var/job-result-data": {},
            "/var/log/my-app-logs": {}
        },
        "WorkingDir": "/home/alice",
        "Labels": {
            "com.example.project.git.url": "https://example.com/project.git",
            "com.example.project.git.commit": "45a939b2999782a3f005621a8d0f29aa387e1d6b"
        }
    },
    "rootfs": {
      "diff_ids": [
        "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
        "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
      ],
      "type": "layers"
    },
    "history": [
      {
        "created": "2015-10-31T22:22:54.690851953Z",
        "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
      },
      {
        "created": "2015-10-31T22:22:55.613815829Z",
        "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
        "empty_layer": true
      }
    ]
}
  1. 根据配置文件中的diff_ids(每个diffid对应一个layer tar包的sha256,tar包相当于layer的原始格式),在本地找对应的layer是否存在
  2. 如果layer不存在,则根据manifest里面layer的sha256和media type去服务器拿相应的layer(相当去拿压缩格式的包)(media type: header.Set(“Accept”, “application/vnd.docker.distribution.manifest.v2+json”))
  3. 拿到后进行解压,并检查解压后tar包的sha256能否和配置文件(Image Config)中的diff_id对的上,对不上说明有问题,下载失败
  4. 根据docker所用的后台文件系统类型,解压tar包并放到指定的目录
  5. 等所有的layer都下载完成后,整个image下载完成,就可以使用了

什么是镜像

  • docker 镜像是一个只读的 docker 容器模板,含有启动 docker 容器所需的文件系统结构及其内容,因此是启动一个 docker 容器的基础。docker 镜像的文件内容以及一些运行 docker 容器的配置文件组成了 docker 容器的静态文件系统运行环境:rootfs。
    Docker镜像的存储机制
  • rootfs: docker 容器在启动时内部进程可见的文件系统,即 docker 容器的根目录。rootfs 通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类 Unix 操作系统中的目录系统,如 /dev、/proc、/bin、/etc、/lib、/usr、/tmp 及运行 docker 容器所需的配置文件、工具等。
  • 当 docker daemon 为 docker 容器挂载 rootfs 时,沿用了 Linux 内核启动时的做法,即将 rootfs 设为只读模式。在挂载完毕之后,利用联合挂载(union mount)技术在已有的只读 rootfs 上再挂载一个读写层。这样,可读写的层处于 docker 容器文件系统的最顶层,其下可能联合挂载了多个只读的层,只有在 docker 容器运行过程中文件系统发生变化时,才会把变化的文件内容写到可读写层,并隐藏只读层中的旧版本文件。

Docker镜像的主要特点

  • 分层:分层达到了在不的容器同镜像之间共享镜像层的效果。docker commit在这个镜像层之上新加了一层镜像
  • 写时复制:所有容器共享同一份数据,只有在 docker 容器运行过程中文件系统发生变化时,才会把变化的文件内容写到可读写层,并隐藏只读层中的老版本文件。写时复制配合分层机制减少了镜像对磁盘空间的占用和容器启动时间。
  • 内容寻址:新模型对镜像层的内容计算校验和,生成一个内容哈希值,并以此哈希值代替之前的 UUID 作为镜像层的唯一标识。该机制主要提高了镜像的安全性。(cache-id)
  • 联合挂载:联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。联合挂载是用于将多个镜像层的文件系统挂载到一个挂载点来实现一个统一文件系统视图的途径,是下层存储驱动(aufs、overlay等) 实现分层合并的方式。

Docker镜像中的关键概念

  • registry:registry 用以保存 docker 镜像,其中还包括镜像层次结构和关于镜像的元数据。
  • repository:registry 是 repository 的集合,repository 是镜像的集合。
  • manifest:manifest(描述文件)主要存在于 registry 中作为 docker 镜像的元数据文件,在 pull、push、save 和 load 过程中作为镜像结构和基础信息的描述文件。
  • image:是用来存储一组镜像相关的元数据信息,主要包括镜像的架构(如 amd64)、镜像默认配置信息、构建镜像的容器配置信息、包含所有镜像层信息的 * * rootfs。docker 利用 rootfs 中的 diff_id 计算出内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容。
  • layer:docker 镜像管理中的 layer 主要存放了镜像层的 diff_id、size、cache-id 和 parent 等内容,实际的文件内容则是由存储驱动来管理,并可以通过 cache-id 在本地索引到。

镜像关键点介绍

repository

  • 介绍:docker.elastic.co/elasticsearch/elasticsearch中的 elasticsearch/elasticsearch 就是repository
  • 位置: /var/lib/docker/image/<graph_driver>/repositories.json
    Docker镜像的存储机制
  • 当前 docker 默认采用 SHA256 算法根据镜像元数据配置文件计算出镜像 ID。上图中的两条记录本质上是一样的,第二条记录和第一条记录指向同一个镜像 ID。其中sha256:c8c275751219dadad8fa56b3ac41ca6cb22219ff117ca98fe82b42f24e1ba64e 被称为镜像的摘要
  • 镜像摘要Digest:SHA256对镜像manifest内容计算
  • 对于本地生成的镜像来说,由于没有上传到registry上去,所以没有digest,因为镜像的manifest由registry生成

manifest

  • 介绍:manifest也是一个json文件,media type为这个文件包含了对前面layers和image config的描述
  • 位置:无
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 7023,
    "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7"  //imageId
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 32654,
      "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 16724,
      "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" //大概可以理解为diffId
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "size": 73109,
      "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736"
    }
  ],
  "annotations": {
    "com.example.key1": "value1",
    "com.example.key2": "value2"
  }
}
  • 这个文件其实是registry的一种描述,镜像下载完成后会自动删除

Image Config

  • 介绍:json文件包含了对这个image的描述
  • 位置:/var/lib/docker/image/<graph_driver>/imagedb/content/sha256/<image_id>
    Docker镜像的存储机制
    Docker镜像的存储机制
  • diffId和chainId有换算公式的
  • sha256 /var/lib/docker/image/<graph_driver>/imagedb/content/sha256/<image_id> image_id

layer

layer元数据
  • 位置:/var/lib/docker/image/<graph_driver>/layerdb/sha256//
  • layer包含了上一个layer上的改动情况,主要包含三方面的内容:
    1)变化类型:是增加、修改还是删除了文件
    2)文件类型:每个变化发生在哪种文件类型上
    3)文件属性:文件的修改时间、用户ID、组ID、RWX权限等
  • chainID的计算方法
    1)如果该镜像层是最底层(没有父镜像层),该层的 diffID 便是 chainID。
    2)该镜像层的 chainID 计算公式为 chainID(n)=SHA256(chain(n-1) diffID(n)),也就是根据父镜像层的 chainID 加上一个空格和当前层的 diffID,再计算 SHA256 校验码。
 /var/lib/docker/image/aufs/layerdb/sha256/67b5e1df1b671d4751794767b20f6973d77e91528ab475ee1118ce8dab796193# ls
cache-id  diff  parent  size  tar-split.json.gz
  • chche-id: 本机随机生成的uuid用来标识在本机器上唯一
  • diff: diffId
  • parent: 父layer的chainId
  • size: 这个layer的大小
layer的diffId和digest的对应关系
  • digest可以再docker pull的时候查看到
tree -d /var/lib/docker/image/aufs/distribution/
/var/lib/docker/image/aufs/distribution/
├── diffid-by-digest
│   └── sha256
└── v2metadata-by-diffid
    └── sha256

① diffid-by-digest: 存放digest到diffid的对应关系
② v2metadata-by-diffid: 存放diffid到digest的对应关系

layer数据

位置:/var/lib/docker/<graph_driver>

结束语

本篇介绍了image在本地的存储方式,包括了/var/lib/docker/image和/var/lib/docker/aufs这两个目录,但/var/lib/docker/image下面有两个目录没有涉及:

  • /var/lib/docker/image/aufs/imagedb/metadata:里面存放的是本地image的一些信息,从服务器上pull下来的image不会存数据到这个目录,

  • /var/lib/docker/image/aufs/layerdb/mounts: 创建container时,docker会为每个container在image的基础上创建一层新的layer,里面主要包含/etc/hosts、/etc/hostname、/etc/resolv.conf等文件,创建的这一层layer信息就放在这里。