Docker 图片清单中的图片 ID 是如何计算的?

How is computed the image id in the Docker image manifest?

声明 documentation Docker 图像 ID 计算为图像配置的 sha256 校验和 json。

例如,对于经典的 hello-world 图片,我可以看到以下图片 id

» docker images --no-trunc
reg.ngrok.io/library/hello-world   latest   sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412   4 months ago    13.3kB

然而,在图像中tar我可以看到以下图像配置json内容

{
  "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":[
        "/hello"
     ],
     "Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d",
     "Volumes":null,
     "WorkingDir":"",
     "Entrypoint":null,
     "OnBuild":null,
     "Labels":null
  },
  "container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72",
  "container_config":{
     "Hostname":"8746661ca3c2",
     "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 [\"/hello\"]"
     ],
     "Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d",
     "Volumes":null,
     "WorkingDir":"",
     "Entrypoint":null,
     "OnBuild":null,
     "Labels":{
        
     }
  },
  "created":"2021-09-23T23:47:57.442225064Z",
  "docker_version":"20.10.7",
  "history":[
     {
        "created":"2021-09-23T23:47:57.098990892Z",
        "created_by":"/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "
     },
     {
        "created":"2021-09-23T23:47:57.442225064Z",
        "created_by":"/bin/sh -c #(nop)  CMD [\"/hello\"]",
        "empty_layer":true
     }
  ],
  "os":"linux",
  "rootfs":{
     "type":"layers",
     "diff_ids":[
        "sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"
     ]
  }
}

以上文档包含与预期图像 ID 不匹配的以下属性:

"Image":"sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d"
"container":"8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72",

这些属性是如何计算的?找不到这方面的任何文档。

config.Image、container、container_config都可以忽略,用buildkit等其他工具构建时甚至看不到这些字段。它们来自经典构建器如何通过为每个构建步骤创建悬挂图像来创建图像,这显示了一些与前面的步骤相关联的 ID 和随机生成的数字。

因为我没有你的图片,我将通过一个例子来工作,将 busybox 克隆到我的本地注册表:

$ docker image ls --no-trunc localhost:5000/library/busybox
REPOSITORY                       TAG       IMAGE ID                                                                  CREATED        SIZE
localhost:5000/library/busybox   inspect   sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a   4 weeks ago    1.24MB
localhost:5000/library/busybox   latest    sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a   4 weeks ago    1.24MB
localhost:5000/library/busybox   <none>    sha256:cabb9f684f8ba3edb303d578bfd7d709d853539ea1b420a3f6c81a08e85bb3d7   3 months ago   1.24MB

我要找的ID是sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a。我在图片中看到 inspect:

$ docker inspect localhost:5000/library/busybox
[
    {
        "Id": "sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a",
        "RepoTags": [
            "busybox:latest",
            "localhost:5000/library/busybox:inspect",
            "localhost:5000/library/busybox:latest"
        ],
        "RepoDigests": [
            "busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678",
            "localhost:5000/library/busybox@sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678",
            "localhost:5000/library/busybox@sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2021-12-30T19:19:41.006954958Z",
        "Container": "a0007fa726185ffbcb68e90f8edabedd79a08949f32f4f0bcc6e5fed713a72c8",
        "ContainerConfig": {
            "Hostname": "a0007fa72618",
            "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:da658412c37aa24e561eb7e16c61bc82a9711340d8fb5cf1a8f39d8e96d7f723",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.7",
        "Author": "",
        "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:da658412c37aa24e561eb7e16c61bc82a9711340d8fb5cf1a8f39d8e96d7f723",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 1239820,
        "VirtualSize": 1239820,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/home/docker/overlay2/558763c3b913289624792cf7f43a9b22a30f65eaab714b26d7da2a977070070a/merged",
                "UpperDir": "/home/docker/overlay2/558763c3b913289624792cf7f43a9b22a30f65eaab714b26d7da2a977070070a/diff",
                "WorkDir": "/home/docker/overlay2/558763c3b913289624792cf7f43a9b22a30f65eaab714b26d7da2a977070070a/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374"
            ]
        },
        "Metadata": {
            "LastTagTime": "2022-01-30T19:48:56.850389545-05:00"
        }
    }
]

为了计算配置摘要,我将查看注册表中的图像。那里的配置应该是一样的:

$ regctl image inspect localhost:5000/library/busybox --format raw-body | jq .
{
  "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:da658412c37aa24e561eb7e16c61bc82a9711340d8fb5cf1a8f39d8e96d7f723",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
  "container": "a0007fa726185ffbcb68e90f8edabedd79a08949f32f4f0bcc6e5fed713a72c8",
  "container_config": {
    "Hostname": "a0007fa72618",
    "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:da658412c37aa24e561eb7e16c61bc82a9711340d8fb5cf1a8f39d8e96d7f723",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {}
  },
  "created": "2021-12-30T19:19:41.006954958Z",
  "docker_version": "20.10.7",
  "history": [
    {
      "created": "2021-12-30T19:19:40.833034683Z",
      "created_by": "/bin/sh -c #(nop) ADD file:6db446a57cbd2b7f4cfde1f280177b458390ed5a6d1b54c6169522bc2c4d838e in / "
    },
    {
      "created": "2021-12-30T19:19:41.006954958Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"sh\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374"
    ]
  }
}

并且 运行 通过 sha256sum 的原始配置(没有 jq 格式),您会看到所需的“图像 ID”值:

$ regctl image inspect localhost:5000/library/busybox --format raw-body | sha256sum
beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a  -

图像清单中可以看到相同的摘要。图像由内容可寻址存储组成,因此在推送清单之前计算每个组件的摘要并将其推送到注册表:

$ regctl image manifest localhost:5000/library/busybox
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1456,
    "digest": "sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 772788,
      "digest": "sha256:5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa"
    }
  ]
}

那个清单本身有一个摘要,你可以通过计算它本身的 sha256sum 来计算它,你会在 multi-platform 个图像的清单列表中看到相同的摘要:

$ regctl image manifest localhost:5000/library/busybox --format raw-body | sha256sum
62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee  -

$ regctl image manifest --list localhost:5000/library/busybox --format '{{ jsonPretty . }}'
{
  "manifests": [
    {
      "digest": "sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 527
    },
    {
      "digest": "sha256:ca038f83e1a3a6a08b539830ca3beefb503a3989cc1f19c265ae4e624a45a9cc",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v5"
      },
      "size": 527
    },
    {
      "digest": "sha256:b27cc98025245c0e746b201d5c773faff99869ae58585090182e18d7c5e8a5e2",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v6"
      },
      "size": 527
    },
    {
      "digest": "sha256:4ca297c4a8fdaf9806239ddcaf7c91266614c15d2c50b1acc96c0401ed18e544",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v7"
      },
      "size": 527
    },
    {
      "digest": "sha256:a77fe109c026308f149d36484d795b42efe0fd29b332be9071f63e1634c36ac9",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      },
      "size": 527
    },
    {
      "digest": "sha256:839f94220ea4ab84e1b6364f7c3f311085a51904d4f5d76d022aead017fe2e1a",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "386",
        "os": "linux"
      },
      "size": 527
    },
    {
      "digest": "sha256:04b9b3684bf435766a3ec5f31f7db0b27ace0c13f4f9d514724432e96b0e7ccd",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "mips64le",
        "os": "linux"
      },
      "size": 527
    },
    {
      "digest": "sha256:d70e38f76482b3e5b1be06dbfe7aaf9cac00bb00678931b6e14785bee41caf3d",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "ppc64le",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:22b3bb958147afeb0db0122c91cb7d8b3a9d691b0d4e532e0cf79078cc80916f",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "riscv64",
        "os": "linux"
      },
      "size": 527
    },
    {
      "digest": "sha256:6b52cb58f1bfdd3b6068cc91febb82668f2ee16939e926fa67bab898863b6139",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "s390x",
        "os": "linux"
      },
      "size": 528
    }
  ],
  "mediaType": "application\/vnd.docker.distribution.manifest.list.v2+json",
  "schemaVersion": 2
}

甚至那个 multi-platform 清单也有一个摘要,这个摘要和平台特定的摘要都出现在图像检查的 RepoDigests 中:

$ regctl image manifest --list localhost:5000/library/busybox --format raw-body | sha256sum
5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678  -

注意这里的regctl命令是我自己的,来自regclient,类似的工具可以从Google的crane和RedHat的skopeo中找到。

有关图像规范的更多详细信息,我建议查看 OCI image spec