能否从Docker Registry V2获取镜像ID?

7

当一个镜像被推送到V2仓库时,镜像ID是否也会被推送到仓库中呢?是否有可能从V2仓库获取某个仓库的镜像ID呢?

3个回答

16

如果使用Docker客户端1.10或更高版本推送镜像,则可以通过以下方式从注册表中获取镜像ID:

docker inspect registry.example.com/myimage:mytag | jq -r '.[0].Id'

GET /v2/<image>/manifests/<tag> 

您的请求必须包含标题(header)

Accept: application/vnd.docker.distribution.manifest.v2+json

响应中,镜像ID将在Content-Docker-Digest响应头中。


谢谢,那就是我现在正在做的。 - Haoming Zhang
你好,能否提供一个格式化的curl请求作为示例?我在使用v2 API时遇到了问题。 - deanshanahan
下面@fightingdu的回答中有一个很好的curl请求。你只需要替换$server$repo$tag的值即可。 - drs
4
Content-Docker-Digest不包含镜像ID。 镜像ID是镜像配置的摘要,参见https://github.com/docker/distribution/issues/1662#issuecomment-213079540 ... 在更新的注册协议版本中,您可以从config.digest中获取它。 - André B.

7

[2020-11更新的答案]

在使用Docker镜像时,需要处理许多sha。每个文件系统层都有一个唯一的sha,在该层被压缩并存储在注册表中时会更新。在表示图像配置的JSON对象上计算出一个sha,当您在本地查找图像ID时,就会看到它:

$ docker inspect busybox:latest --format '{{ .ID }}'
sha256:f0b02e9d092d905d0d87a8455a1ae3e9bb47b4aa3dc125125ca5cd10d6441c9f

注册表上有一个单平台清单的sha。这个清单包括指向图像配置和各个层的指针。还有一个多平台清单的sha,指向每个单独的平台。让我们深入了解一下。首先,这是我将用来使用匿名请求查询Docker Hub注册表的脚本:

$ cat manifest-v2.sh
#!/bin/sh

ref="${1:-library/ubuntu:latest}"
sha="${ref#*@}"
if [ "$sha" = "$ref" ]; then
  sha=""
fi
wosha="${ref%%@*}"
repo="${wosha%:*}"
tag="${wosha##*:}"
if [ "$tag" = "$wosha" ]; then
  tag="latest"
fi
api="application/vnd.docker.distribution.manifest.v2+json"
apil="application/vnd.docker.distribution.manifest.list.v2+json"
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
        | jq -r '.token')
curl -H "Accept: ${api}" -H "Accept: ${apil}" \
     -H "Authorization: Bearer $token" \
     -s "https://registry-1.docker.io/v2/${repo}/manifests/${sha:-$tag}" | jq .

下一步,让我们拉取最新标签的清单:
$ ./manifest-v2.sh library/busybox:latest
{
  "manifests": [
    {
      "digest": "sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 527
    },
    {
      "digest": "sha256:a7c572c26ca470b3148d6c1e48ad3db90708a2769fdf836aa44d74b83190496d",
      "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v5"
      },
      "size": 527
    },
...

结果是一个清单列表,让我们仅获取amd64平台的sha值:
$ ./manifest-v2.sh library/busybox@sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1493,
    "digest": "sha256:f0b02e9d092d905d0d87a8455a1ae3e9bb47b4aa3dc125125ca5cd10d6441c9f"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 764619,
      "digest": "sha256:9758c28807f21c13d05c704821fdd56c0b9574912f9b916c65e1df3e6b8bc572"
    }
  ]
}

这里我们有一个单层和配置对象,该配置对象上的sha与application/vnd.docker.container.image.v1+json媒体类型匹配我们的镜像id sha256:f0b02e9d092d9...。这在从注册表获取v2输出时更容易识别,这就是为什么清单脚本插入Accept头的原因。

我要提醒的是,我不认为这是保证匹配的,你的经验可能有所不同,我不是律师等等。我可以预见到docker添加/删除字段将被不同版本的docker引擎忽略。因此,我会避免对这种行为进行任何硬依赖。

请注意,此sha与用于拉取图像的注册表上唯一标识图像的sha不同。对于这个,您需要清单sha,如果有清单列表,您应该使用它。当解析标签(使用curl的-I参数)时,您可以只使用HEAD请求查看此sha:

$ cat manifest-v2-head.sh 
#!/bin/sh

ref="${1:-library/ubuntu:latest}"
sha="${ref#*@}"
if [ "$sha" = "$ref" ]; then
  sha=""
fi
wosha="${ref%%@*}"
repo="${wosha%:*}"
tag="${wosha##*:}"
if [ "$tag" = "$wosha" ]; then
  tag="latest"
fi
# echo "Looking up repo $repo, ${sha:-$tag}"
# api="application/vnd.oci.image.index.v1+json"
# api="application/vnd.oci.image.manifest.v1+json"
api="application/vnd.docker.distribution.manifest.v2+json"
apil="application/vnd.docker.distribution.manifest.list.v2+json"
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
        | jq -r '.token')
curl -H "Accept: ${api}" -H "Accept: ${apil}" \
     -H "Authorization: Bearer $token" \
     -I -s "https://registry-1.docker.io/v2/${repo}/manifests/${sha:-$tag}" 

$ ./manifest-v2-head.sh library/busybox:latest
HTTP/1.1 200 OK
Content-Length: 2080
Content-Type: application/vnd.docker.distribution.manifest.list.v2+json
Docker-Content-Digest: sha256:a9286defaba7b3a519d585ba0e37d0b2cbee74ebfe590960b0b1d6a5e97d1e1d
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:a9286defaba7b3a519d585ba0e37d0b2cbee74ebfe590960b0b1d6a5e97d1e1d"
Date: Wed, 18 Nov 2020 14:38:59 GMT
Strict-Transport-Security: max-age=31536000
RateLimit-Limit: 250;w=21600
RateLimit-Remaining: 250;w=21600

从上面的内容可以看出,您可以使用docker pull busybox@sha:a9286d...命令。
这里的所有命令都是特定于Docker Hub的。如果要使用其他注册表,请使用我regclient项目中提供的regctl命令处理其他注册表的身份验证和API调用。
$ regctl image manifest busybox
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1493,
    "digest": "sha256:f0b02e9d092d905d0d87a8455a1ae3e9bb47b4aa3dc125125ca5cd10d6441c9f"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 764619,
      "digest": "sha256:9758c28807f21c13d05c704821fdd56c0b9574912f9b916c65e1df3e6b8bc572"
    }
  ]
}

$ regctl image digest busybox --list
sha256:a9286defaba7b3a519d585ba0e37d0b2cbee74ebfe590960b0b1d6a5e97d1e1d

$ regctl image digest busybox
sha256:c9249fdf56138f0d929e2080ae98ee9cb2946f71498fc1484288e6a935b5e5bc

原始回答:

镜像ID本身不存储在任何可访问的注册表API位置,以下是使用本地注册表的示例:

bash$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
...

bash$ docker tag busybox localhost:5000/busybox

bash$ docker push localhost:5000/busybox
The push refers to a repository [localhost:5000/busybox]
5f70bf18a086: Pushed 
...

bash$ curl http://localhost:5000/v2/busybox/tags/list
{"name":"busybox","tags":["latest"]}

bash$ curl http://localhost:5000/v2/busybox/manifests/latest
{
   "schemaVersion": 1,
   "name": "busybox",
   "tag": "latest",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:385e281300cc6d88bdd155e0931fbdfbb1801c2b0265340a40481ee2b733ae66"
      }
   ],
   "history": [
      {
         "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"156e10b83429\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"sh\"],\"Image\":\"56ed16bd6310cca65920c653a9bb22de6b235990dcaa1742ff839867aed730e5\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"container\":\"5f8098ec29947b5bea80483cd3275008911ce87438fed628e34ec0c522665510\",\"container_config\":{\"Hostname\":\"156e10b83429\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"sh\\\"]\"],\"Image\":\"56ed16bd6310cca65920c653a9bb22de6b235990dcaa1742ff839867aed730e5\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"created\":\"2016-03-18T18:22:48.810791943Z\",\"docker_version\":\"1.9.1\",\"id\":\"437595becdebaaaf3a4fc3db02c59a980f955dee825c153308c670610bb694e1\",\"os\":\"linux\",\"parent\":\"920777304d1d5e337bc59877253e946f224df5aae64c72538672eb74637b3c9e\"}"
      },
      {
         "v1Compatibility": "{\"id\":\"920777304d1d5e337bc59877253e946f224df5aae64c72538672eb74637b3c9e\",\"created\":\"2016-03-18T18:22:48.262403239Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:47ca6e777c36a4cfffe3f918b64a445c8f32300deeb9dfa5cc47261bd7b75d21 in /\"]}}"
      }
   ],
   "signatures": [
      {
         "header": {
            "jwk": {
               "crv": "P-256",
               "kid": "FIFX:SJRD:AQHW:MCFX:M6WC:LXI2:3VO2:4LFW:UHDZ:QUN7:OLX4:6WGD",
               "kty": "EC",
               "x": "Xm8wJTzw3nb--rGoD3dxjKffikj7Snb9dHW-qGbqSAM",
               "y": "GnATS--7lVcA_-jQGuDKTtjhmnGgvBrx8rLdlPOJV3U"
            },
            "alg": "ES256"
         },
         "signature": "f8NVzOF6ujm_0COedniGCGL_q3KsTfKFM9T8ZZDf2MSIMJ3TYoR_s795NqdEy8yWaoLuT2LoI0BCEsuOTZUhCw",
         "protected": "eyJmb3JtYXRMZW5ndGgiOjE5MTQsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wNi0xMVQwMToxMzoyMVoifQ"
      }
   ]

bash$ curl -I http://localhost:5000/v2/busybox/manifests/latest
HTTP/1.1 200 OK
Content-Length: 2561
Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
Docker-Content-Digest: sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:e45f25b1760f616e65f106b424f4ef29185fbd80822255d79dabc73b8eb715ad"
X-Content-Type-Options: nosniff
Date: Sat, 11 Jun 2016 01:21:26 GMT

在所有的API调用中,我都找不到本地看到的所需的47bcc53...图像ID。
bash$ docker inspect busybox:latest
[
    {
        "Id": "sha256:47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb",
        "RepoTags": [
            "busybox:latest",
            "localhost:5000/busybox:latest"
        ],
...

this image spec可以看出,图像id是可重现的哈希值,并且我在不同系统中看到相同的图像id值。

ImageID 每个图像的ID由其配置JSON的SHA256哈希给出。它表示为256位的十六进制编码,例如, sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9。 由于被哈希的配置JSON引用了图像中每个层的哈希,因此这种ImageID的表述使图像具有内容寻址能力。

因此,如果您可以从API调用中重现配置JSON,则可能能够自己生成图像ID。


1
谢谢!这个答案符合我的研究预期...似乎获取存储库镜像的图像ID的唯一方法是尝试获取配置JSON,并通过Docker Daemon相同的哈希算法重新生成图像ID。 - Haoming Zhang

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接