阅读前提: 浏览一下网页
docker manifest usage:
https://docs.docker.com/engine/reference/commandline/manifest/
manifest V2 description:
https://docs.docker.com/registry/spec/manifest-v2-2/
docker pull 流程
docker发送image的名称+tag(或者digest)给registry服务器,服务器根据收到的image的名称+tag(或者digest),找到相应image的manifest,然后将manifest返回给docker
docker得到manifest后,读取里面image配置文件的digest(sha256),这个sha256码就是image的ID
根据ID在本地找有没有存在同样ID的image,有的话就不用继续下载了
如果没有,那么会给registry服务器发请求(里面包含配置文件的sha256和media type),拿到image的配置文件(Image Config)
根据配置文件中的diff_ids(每个diffid对应一个layer tar包的sha256,tar包相当于layer的原始格式),在本地找对应的layer是否存在
如果layer不存在,则根据manifest里面layer的sha256和media type去服务器拿相应的layer(相当去拿压缩格式的包)。
拿到后进行解压,并检查解压后tar包的sha256能否和配置文件(Image Config)中的diff_id对的上,对不上说明有问题,下载失败
根据docker所用的后台文件系统类型,解压tar包并放到指定的目录
等所有的layer都下载完成后,整个image下载完成,就可以使用了
具体到存储目录(以hello-world为例子)
首先pull 一下hello-world的image:
docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:c3b4ada4687bbaa170745b3e4dd8ac3f194ca95b2d0518b417fb47e5879d9b5f
Status: Image is up to date for hello-world:latest
docker.io/library/hello-world:latest
其实docker会先查询manifest信息, 我们可以用docker 命令来获取:
docker manifest inspect -v hello-world
会返回如下信息:
[mcong@sessiondbproxy-ci ~]$ docker manifest inspect -v hello-world
[
{
"Ref": "docker.io/library/hello-world:latest@sha256:92c7f9c92844bbbb5d0a101b22f7c2a7949e40f8ea90c8b3bc396879d95e899a",
"Descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:92c7f9c92844bbbb5d0a101b22f7c2a7949e40f8ea90c8b3bc396879d95e899a",
"size": 524,
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
"SchemaV2Manifest": {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 977,
"digest": "sha256:1b930d010525941c1d56ec53b97bd057a67ae1865eebf042686d2a2d18271ced"
}
]
}
},
{
"Ref": "docker.io/library/hello-world:latest@sha256:1e44d8bca6fb0464794555e5ccd3a32e2a4f6e44a20605e4e82605189904f44d",
"Descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:1e44d8bca6fb0464794555e5ccd3a32e2a4f6e44a20605e4e82605189904f44d",
"size": 525,
"platform": {
"architecture": "arm",
Docker pull 自前会检查自身的OS类型,然后下载相应的image。
可以看出, config中的digest 就是我们的imageID
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1510,
"digest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
当执行docker pull后,docker会首先检查是否有该image:
docker会查询如下文件:/var/lib/docker/image/overlay2/repositories.json
我们会发现这个文件有如下的记录:
"hello-world": {
"hello-world:latest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e",
"hello-world@sha256:c3b4ada4687bbaa170745b3e4dd8ac3f194ca95b2d0518b417fb47e5879d9b5f": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
},
其实这个文件记录了manifest里边的digest<->imageID 的映射关系。
fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e
就是我们的image的ID
docker images |grep fce2
hello-world latest fce289e99eb9 9 months ago 1.84kB
本地的image如何存储?
现在我们有了imageID。 Docker会首先会把image的信息存储在:/var/lib/docker/image/overlay2/imagedb/content/sha256/image id中
[root@sessiondbproxy-ci sha256]# cat fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e|python -mjson.tool
{
"architecture": "amd64",
...
"os": "linux",
"rootfs": {
"diff_ids": [
"sha256:af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3"
],
"type": "layers"
}
在这里, 记录的layer信息,由于hello-world只有一层,这里就只有一层信息。
那么这一层具体内容存储在哪里?
overlay会首先读取如下目录:
/var/lib/docker/image/overlay2/layerdb/sha256/af0b15c8625bb1938f1d7b17081031f649fd14e6b233688eea3c5483994a66a3
这个目录有如下文件:
cache-id diff size tar-split.json.gz
note:由于hello-world只有一层,所以这里没有parent信息。
其中cache-id指向的内容就是我们具体存储的文件。
我们看看这个文件的内容:
717dd86fff6348916d3701afc60701be5fd149257a3dd838b498ed44dda677df
这又是什么?
其实这个就是overlay存储的标识, 我们可以到这个目录下看:
/var/lib/docker/overlay2/717dd86fff6348916d3701afc60701be5fd149257a3dd838b498ed44dda677df
这个目录有如下文件:
diff link
note:还是由于hello-world只有一层,所以没有parent信息。
我们想要的内用就在link里边, 我们看看他的内容:
MUUP2FO443P7AQESYZFASVSCRQ
这又是什么鬼?看过以前文章的人可能一下就看出来了:
也就是这个目录
/var/lib/docker/overlay2/l/MUUP2FO443P7AQESYZFASVSCRQ
看看里边有啥:
[root@sessiondbproxy-ci MUUP2FO443P7AQESYZFASVSCRQ]# ls
hello
终于找到我们想要的东西了
多层架构的镜像
Docker 最值得称赞的一点就是使用方便。例如,运行一个应用就像拉取镜像并运行容器这么简单。无须担心安装、依赖或者配置的问题。开箱即用。
但是,随着 Docker 的发展,事情开始变得复杂——尤其是在添加了新平台和架构之后,例如 Windows、ARM 以及 s390x。
这是会突然发现,在拉取镜像并运行之前,需要考虑镜像是否与当前运行环境的架构匹配,这破坏了 Docker 的流畅体验。
多架构镜像(Multi-architecture Image)的出现解决了这个问题!
Docker(镜像和镜像仓库服务)规范目前支持多架构镜像。这意味着某个镜像仓库标签(repository:tag)下的镜像可以同时支持 64 位 Linux、PowerPC Linux、64 位 Windows 和 ARM 等多种架构。
简单地说,就是一个镜像标签之下可以支持多个平台和架构。下面通过实操演示该特性。
为了实现这个特性,镜像仓库服务 API 支持两种重要的结构:Manifest 列表(新)和 Manifest。
Manifest 列表是指某个镜像标签支持的架构列表。其支持的每种架构,都有自己的 Mainfest 定义,其中列举了该镜像的构成。
下图使用 Golang 官方镜像作为示例。图左侧是 Manifest 列表,其中包含了该镜像支持的每种架构。
Manifest 列表的每一项都有一个箭头,指向具体的 Manifest,其中包含了镜像配置和镜像层数据。
Golang官方镜像
在具体操作之前,先来了解一下原理。
假设要在 Raspberry Pi(基于 ARM 架构的 Linux)上运行 Docker。
在拉取镜像的时候,Docker 客户端会调用 Docker Hub 镜像仓库服务相应的 API 完成拉取。
如果该镜像有 Mainfest 列表,并且存在 Linux on ARM 这一项,则 Docker Client 就会找到 ARM 架构对应的 Mainfest 并解析出组成该镜像的镜像层加密 ID。
然后从 Docker Hub 二进制存储中拉取每个镜像层。
下面的示例就展示了多架构镜像是如何在拉取官方 Golang 镜像(支持多架构)时工作的,并且通过一个简单的命令展示了 Go 的版本和所在主机的 CPU 架构。
需要注意的是,两个例子都使用相同的命令 docker container run。不需要告知 Docker 具体的镜像版本是 64 位 Linux 还是 64 位 Windows。
示例中只运行了普通的命令,选择当前平台和架构所需的正确镜像版本是有由 Docker 完成的。
64 位 Linux 示例如下。
$ docker container run --rm golang go version
Unable to find image ‘golang:latest’ locally
latest: Pulling from library/golang
723254a2c089: Pull complete
39cd5f38ffb8: Pull complete
Digest: sha256:947826b5b6bc4…
Status: Downloaded newer image for golang:latest
go version go1.9.2 linux/amd64
64 位 Windows 示例如下。
PS> docker container run --rm golang go version
Using default tag: latest
latest: Pulling from library/golang
3889bb8d808b: Pull complete
8df8e568af76: Pull complete
9604659e3e8d: Pull complete
9f4a4a55f0a7: Pull complete
6d6da81fc3fd: Pull complete
72f53bd57f2f: Pull complete
6464e79d41fe: Pull complete
dca61726a3b4: Pull complete
9150276e2b90: Pull complete
cd47365a14fb: Pull complete
1783777af4bb: Pull complete
3b8d1834f1d7: Pull complete
7258d77b22dd: Pull complete
Digest: sha256:e2be086d86eeb789…e1b2195d6f40edc4
Status: Downloaded newer image for golang:latest
go version go1.9.2 windows/amd64
前面的操作包括从 Docker Hub 拉取 Golang 镜像,以容器方式启动,执行 go version 命令,并且输出 Go 的版本和主机 OS / CPU 架构信息。
每个示例的最后一行都展示了 go version 命令的输出内容。可以看到两个示例使用了完全相同的命令,但是 Linux 示例中拉取的是 linux/amd64 镜像,而 Windows 示例中拉取的是 windows/amd64 镜像。
所有官方镜像都支持 Manifest 列表。但是,全面支持各种架构的工作仍在推进当中。
创建支持多架构的镜像需要镜像的发布者做更多的工作。同时,某些软件也并非跨平台的。在这个前提下,Manifest 列表是可选的——在没有 Manifest 列表的情况下,镜像仓库服务会返回普通的 Manifest。
总结一下
/var/lib/docker/image/overlay2/repositories.json
里边存储了digest<->imageID/var/lib/docker/image/overlay2/imagedb
这个目录里边存储了image相关的信息/var/lib/docker/image/overlay2/layerdb/
这个目录里边存储了layer信息/var/lib/docker/overlay2
这个目录存储了具体image的东西