docker 镜像存储分析

2022-04-05  本文已影响0人  wangfeiq

镜像在远端镜像仓库和本地的存储方式不同。在镜像仓库中,按层压缩后存储,因为需要考虑拉取、推送的效率;在本地是解压后存储的,因为需要考虑快速起容器,通过联合挂载的方式构造rootfs(联合文件系统UnionFS)。下面对两种存储方式分别进行介绍。

几个名词解释

首先对后面要用到的几个名词做个简单解释。

镜像在远端仓库存储

在本地起一个registry服务,然后推送三个镜像到镜像仓库。可以得到registry中的文件内容如下所示。registry中包含三个镜像: xxx/library/debian:latest,xxx/repo:tag和xxx/busybox:v1

└── registry
    └── v2
        ├── blobs
        │   └── sha256
        │       ├── 0d
        │       │   └── 0d96da54f60b86a4d869d44b44cfca69d71c4776b81d361bc057d6666ec0d878
        │       │       └── data
        │       ├── 34
        │       │   └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413
        │       │       └── data
        │       ...
        └── repositories
            ├── busybox
            │   ├── _layers
            │   │   └── sha256
            │   │       ├── 7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14
            │   │       │   └── link
            │   │       └── e685c5c858e36338a47c627763b50dfe6035b547f1f75f0d39753db71e319016
            │   │           └── link
            │   ├── _manifests
            │   │   ├── revisions
            │   │   │   └── sha256
            │   │   │       └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413
            │   │   │           └── link
            │   │   └── tags
            │   │       └── v1
            │   │           ├── current
            │   │           │   └── link
            │   │           └── index
            │   │               └── sha256
            │   │                   └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413
            │   │                       └── link
            │   └── _uploads
            ├── library
            │   └── debian
            │       ├── _layers
            │       │   └── sha256
            │       │       ├── 41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80
            │       │       │   └── link
            │       │       ├── 67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1
            │       │       │   └── link
            │       │       ...
            │       ├── _manifests
            │       │   ├── revisions
            │       │   │   └── sha256
            │       │   │       └── 57c1e4ff150e2782a25c8cebb80b574f81f06b74944caf972f27e21b76074194
            │       │   │           └── link
            │       │   └── tags
            │       │       └── latest
            │       │           ├── current
            │       │           │   └── link
            │       │           └── index
            │       │               └── sha256
            │       │                   └── 57c1e4ff150e2782a25c8cebb80b574f81f06b74944caf972f27e21b76074194
            │       │                       └── link
            │       └── _uploads
            └── repo
                ├── _layers
                │   └── sha256
                │       ├── 0d96da54f60b86a4d869d44b44cfca69d71c4776b81d361bc057d6666ec0d878
                │       │   └── link
                │       ├── 3790aef225b922bc97aaba099fe762f7b115aec55a0083824b548a6a1e610719
                │       │   └── link
                │       ...
                ├── _manifests
                │   ├── revisions
                │   │   └── sha256
                │   │       └── 36cb5b157911061fb610d8884dc09e0b0300a767a350563cbfd88b4b85324ce4
                │   │           └── link
                │   └── tags
                │       └── tag
                │           ├── current
                │           │   └── link
                │           └── index
                │               └── sha256
                │                   └── 36cb5b157911061fb610d8884dc09e0b0300a767a350563cbfd88b4b85324ce4
                │                       └── link
                └── _uploads

将上面的结构稍加整理,可以得到如下图所示结构

image.png
registry有两个目录,分别为blobs和repositories,其中blobs保存的是镜像的manifest文件、config文件和layer文件内容,文件名字均为data,每个文件可能是manifest、config、layer中的一种。repositories保存的是镜像的repo、tag、layer摘要等信息。其中的_manifests文件夹下包含着镜像的 tags 和 revisions 信息,每一个镜像的每一个 tag 对应 tag 名相同的目录。每个 tag名目录下面有 current 目录和 index 目录, current 目录下的 link 文件保存了该 tag 目前的 manifest 文件的 sha256 编码,对应在 blobs 中的 sha256 目录下的 data 文件,而 index 目录则列出了该 tag 历史上传的所有版本的 sha256 编码信息。_revisions 目录里存放了该 repository 历史上上传版本的所有 sha256 编码信息。

下面通过例子来说明下几个文件的关系。

cat docker/registry/docker/registry/v2/repositories/busybox/_manifests/tags/v1/current/link
sha256:34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413

可以看到link中返回的是一个digest值。
根据该digest值,我们到blobs中查看其中保存的数据:

cat  docker/registry/docker/registry/v2/blobs/sha256/34/34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413/data
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 1456,
      "digest": "sha256:7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 772792,
         "digest": "sha256:e685c5c858e36338a47c627763b50dfe6035b547f1f75f0d39753db71e319016"
      }
   ]

可以看出来,这是一个manifest文件,里面包含了config和layer的digest值,且config文件的digest值就是执行docker images看到的镜像的image ID。下面分别查看两个文件的内容。
首先是config文件:

 cat docker/registry/docker/registry/v2/blobs/sha256/71/7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14/d
{
  "architecture": "amd64",
  "config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "sh"
    ],
    "Image": "sha256:d39a5c18a94ca076b3f9fad5b104d1b5555697280b61cbabd1eec6d89908b1b6",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
  "container": "8afe392526b6fa99a3498001c95812b187123968e5a14802c9e837e1cd06d02b",
  "container_config": {
    "Hostname": "8afe392526b6",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sh",
      "-c",
      "#(nop) ",
      "CMD [\"sh\"]"
    ],
    "Image": "sha256:d39a5c18a94ca076b3f9fad5b104d1b5555697280b61cbabd1eec6d89908b1b6",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {}
  },
  "created": "2021-11-11T19:19:37.862545075Z",
  "docker_version": "20.10.7",
  "history": [
    {
      "created": "2021-11-11T19:19:37.680254655Z",
      "created_by": "/bin/sh -c #(nop) ADD file:10aef872700b72808327a02dd1b22ca1ac9d3e1058cb35cfec1fcfcd1b465ab4 in / "
    },
    {
      "created": "2021-11-11T19:19:37.862545075Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"sh\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
   "sha256:d94c78be13527d00673093f9677f9b43d7e3a02ae6fa0ec74d3d98243b5b40e4"
    ]
  }
}

可以看出,其中包含了容器的镜像的架构、默认配置,启动的容器,镜像构建命令,操作系统、diff_ids等信息。其中的diff_ids是镜像每一层解压后的digest值,在拉取镜像时,可以用来校验本地是否已经存在该层。镜像层本地保存路径为/var/lib/docker/image/overlay2/layerdb/sha256/\${diff_id}
最后看下镜像的layer文件:

 file docker/registry/docker/registry/v2/blobs/sha256/e6/e685c5c858e36338a47c627763b50dfe6035b547f1f75f0d39753db71e319016/data
docker/registry/docker/registry/v2/blobs/sha256/e6/e685c5c858e36338a47c627763b50dfe6035b547f1f75f0d39753db71e319016/data: gzip compressed data

该文件是一个gzip的压缩包,从前面的manifest文件中可以知道文件类型为:application/vnd.docker.image.rootfs.diff.tar.gzip。

镜像下载流程

最后,从上面的分析中,我们可以推测到镜像拉取的大致流程为:

  1. docker client发送镜像的tag到registry。
  2. registry根据镜像tag,得到镜像的manifest文件,返回给docker client。
  3. docker client拿到manifest文件后,根据其中的config的digest,也就是image ID,检查下镜像在本地是否存在。
  4. 如果镜像不存在,则下载config文件,并根据config文件中的diff_ids得到镜像每一层解压后的digest。
  5. 然后根据每层解压后的digest文件,检查本地是否存在,如果不存在,则通过manifest文件中的layer的digest下载该层并解压,然后校验解压后digest是否匹配。
  6. 下载完所有层后,镜像就下载完毕。

镜像推送流程

镜像下载流程和推送过程正好相反,首先...

本地镜像存储

我使用的存储驱动时overlay2,镜像在本地存储目录为/var/lib/docker/image/overlay2,查看下面的文件结构,得到结果如下:

tree -L 4 /var/lib/docker/image/overlay2/
/var/lib/docker/image/overlay2/
├── distribution
│   ├── diffid-by-digest
│   │   └── sha256
│   │       ├── 0240c3db9dedbfe40ec02d465375aa5b059bf8ac78dc249d1f1c91b9429fce44
│   │       ├── 41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80
│   │       ├── 4cdd12619cf5ed0ae43b41cd51f26fbdbd1f5ded860e4188822ec29158218263
│   │       ├── ...
│   └── v2metadata-by-diffid
│       └── sha256
│           ├── 00188c48b6d80656e2344142a77bccf6927123e7492baf43df68e280b2baf7f2
│           ├── 04fefa2a1a8fefaafde3b966f11d547e3bbaa2bb36bf90c58e33c1d305052fa9
│           ├── ...
├── imagedb
│   ├── content
│   │   └── sha256
│   │       ├── 7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14
│   │       ├── ...
│   └── metadata
│       └── sha256
│           ├── b8604a3fe8543c9e6afc29550de05b36cd162a97aa9b2833864ea8a5be11f3e2
│           └── dabbfbe0c57b6e5cd4bc089818d3f664acfad496dc741c9a501e72d15e803b34
├── layerdb
│   ├── mounts
│   │   ├── 2d534be7517fb3efd9c14248eefdb4781924095fe304f5aa0c848f2e76c6bf08
│   │   │   ├── init-id
│   │   │   ├── mount-id
│   │   │   └── parent
│   │   ├──...
│   ├── sha256
│   │   ├── 0e16a5a61bcb4e6b2bb2d746c2d6789d6c0b66198208b831f74b52198d744189
│   │   │   ├── cache-id
│   │   │   ├── diff
│   │   │   ├── parent
│   │   │   ├── size
│   │   │   └── tar-split.json.gz
│   │   ├── 0ee0aa554b8be64c963aaaf162df152784d868d21a7414146cb819a93e4bdb9e
│   │   │   ├── cache-id
│   │   │   ├── diff
│   │   │   ├── parent
│   │   │   ├── size
│   │   │   └── tar-split.json.gz
│   │   ├── ...
│   └── tmp
└── repositories.json

对上面的文件结构进行整理,可以得到如下图所示的结构:


image.png

此处我们主要关心imagedb、layerdb和repositories中的内容。

 cat /var/lib/docker/image/overlay2/repositories.json  | jq
{
    "Repositories":{
        "registry/busybox":{
            "registry/busybox:v1":"sha256:7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14",
            "registry/busybox@sha256:34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413":"sha256:7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14"
        }
        }
    }
}

可以看出repositories.json文件中保存的是镜像tag和镜像ID的对应关系,以及镜像manifest的digest值和镜像ID的对应关系。其实我们除了通过镜像tag拉取镜像外,也可以直接使用manifest的digest拉取镜像,如下:

docker pull registry/busybox@sha256:34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413

镜像索引

最后简单介绍下镜像索引。
从前面的config文件中可以知道,一个镜像只能在指定架构的机器上执行,如果要在不同架构的机器上运行,则需要拉取不同架构的镜像。以前我们通过uname -m命令获取机器架构信息,然后拉取不同架构的镜像,非常麻烦。因此,OCI推出了镜像索引,通过镜像索引,可以根据本地机器的架构,自动拉取对应架构的镜像。

image.png

如图所示,镜像索引包含了不同架构下镜像的manifest的digest。在拉取镜像的时候,就可以按照不同的OS架构拉取不同的镜像了。

上一篇下一篇

猜你喜欢

热点阅读