容器镜像下载流程模拟

最近在研究 containerd 中下载镜像的流程,具体的代码没看太懂,但是通过打 log 的方式把大致的流程和 API 摸清了,记录一下

API

  • /v2/[namespace]/[image]/manifests/[tag] 获取镜像配置
  • /v2/[namespace]/[image]/blobs/[ID] 获取相关的文件

例如从官方仓库中下载 Nginx 的 alpine 版本,则 namespace 为 library ,image 为 nginx ,tag 为 alpine

例如下载用户 person1 提交的镜像 nodeserver ,版本号为 1.2,则 namespace 为 person1,image 为 nodeserver,tag 为 1.2

流程

以从 docker 官方仓库下载 nginx:alpine 为例

首先,获取 token

1
curl https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nginx:pull

返回值为(略去了部分字段):

1
{"token": "ey ...."}

之后的请求需要带上这个 token ,这里就默认保存在环境变量 TOKEN

随后,发起请求获取 manifest

1
2
3
4
curl -vL \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*" \
https://registry-1.docker.io/v2/library/nginx/manifests/alpine

得到的 response 的 header 中,字段 Docker-Content-Digest 记录着 manifest 对应的 sha256 哈希值,我觉得可以将其看作是它的 ID,为 sha256:1e9c503db9913a59156f78c6420f6e2f01c8a3b71ceeeddcd7f604c4db0f045e ,body 的字段为该镜像对应的所有平台版本的列表,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{
"manifests": [
{
"digest": "sha256:210a2ddbc64ef162913f6e1d81fdc29efed14f35aa77716ab5e952959833c831",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "linux"
},
"size": 1360
},
{
"digest": "sha256:002cf20d155af9d2a260c3c0d46451c3e0d856567d8fa5c81177d70b5b55d493",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
},
"size": 1360
},
{
"digest": "sha256:de0842553f09b128efbd5689e1849526240efe9ba7f1e90438e19434e23e4a44",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
},
"size": 1360
},
{
"digest": "sha256:748256f755585f3f57c5025e286edf985d119801be4c6b1a0e3ce37aa9033bd8",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
},
"size": 1360
},
{
"digest": "sha256:cede6d0e08f7e6502570bf5d9b1ebafc52e2442e22162b416c953c0f27bcff9e",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "386",
"os": "linux"
},
"size": 1360
},
{
"digest": "sha256:131ecbff056156d911365a786f29e93bb64cde6421ff49028c4c40955225ccb4",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"size": 1360
},
{
"digest": "sha256:72e3d8ed256c895c34c1f786ea04748cf86a4f35b24cb7eb6f14dc35ae79b7d2",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "s390x",
"os": "linux"
},
"size": 1360
}
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}

当然,如果你使用它的 sha256 的值代替 alpine ,会得到相同的结果。请求代码如下:

1
2
3
4
curl -L \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*" \
https://registry-1.docker.io/v2/library/nginx/manifests/sha256:1e9c503db9913a59156f78c6420f6e2f01c8a3b71ceeeddcd7f604c4db0f045e

得到这个列表之后,从中选取合适的平台,比如说第一个,linux/amd64 ,则选取它的 sha256 的值 sha256:210a2ddbc64ef162913f6e1d81fdc29efed14f35aa77716ab5e952959833c831 发起类似于上一个的请求:

1
2
3
4
curl -L \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*" \
https://registry-1.docker.io/v2/library/nginx/manifests/sha256:210a2ddbc64ef162913f6e1d81fdc29efed14f35aa77716ab5e952959833c831

得到的结果如下。包含有镜像的配置信息和镜像各个层的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"config": {
"digest": "sha256:98ab35023fd67311434b73434d860138a203ab5851fcc9a7161510d5c43fc755",
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 7989
},
"layers": [
{
"digest": "sha256:188c0c94c7c576fff0792aca7ec73d67a2f7f4cb3a6e53a84559337260b36964",
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2796860
},
{
"digest": "sha256:617561f33ec6e3db8e3e3135d62308845388849202c8e4ea57495ab3266f422d",
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 6892796
},
{
"digest": "sha256:7d856acdaa9cd61932982e9abb8e4601a45f9751eda9d30c631948a4c44b7012",
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 602
},
{
"digest": "sha256:a0d3c6e28e6d50fb214598ea50b8dc07a693aa80842e0f31dcf8e5943a6fc665",
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 903
},
{
"digest": "sha256:af69a9b963c864e2093e939c58f1dbce0795041c16da149356fa69a604d91255",
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 667
}
],
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"schemaVersion": 2
}

接下来要做的就是将这些文件一一下载即可,这里选取第一个做示例:

1
2
3
4
curl -L \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*" \
https://registry-1.docker.io/v2/library/nginx/blobs/sha256:98ab35023fd67311434b73434d860138a203ab5851fcc9a7161510d5c43fc755

部分结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"architecture": "amd64",
"config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
"Domainname": "",
"Entrypoint": [
"/docker-entrypoint.sh"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.19.5",
"NJS_VERSION=0.4.4",
"PKG_RELEASE=1"
],
"ExposedPorts": {
"80/tcp": {}
},
// ....