docker 里面可以通过 docker pull、docker build、docker commit、docker load、docker import 等方式得到一个 image,得到 image 之后 docker 在本地是怎么存储的呢?本篇将以 docker pull 为例,简述 image 的获取和存储方式。
镜像相关的配置#
docker 里面和 image 有关的目录为 /var/lib/docker,里面存放着 image 的所有信息,可以通过下面这个 dockerd 的启动参数来修改这个目录的路径。
--graph, -g /var/lib/docker Root of the Docker runtime
镜像的引用方式#
在需要引用 image 的时候,比如 docker pull 的时候,或者运行容器的时候,都需要指定一个image名称,引用一个镜像有多种方式,下面以 alpine 为例进行说明.
docker hub 上的官方镜像#
- alpine: 官方提供的最新 alpine 镜像,对应的完整名称为
docker.io/library/alpine:latest - alpine:3.7: 官方提供的 alpine 3.7 镜像,对应的完整名称为
docker.io/library/alpine:3.7 - alpine:@sha256:abcdef…: 官方提供的 digest 码为 sha256:abcdef… 的 alpine 镜像,对应的完整名称为
docker.io/library/alpine@sha256:abcdef...
docker hub 上的非官方(个人)镜像#
引用方式和官方镜像一样,唯一不同的是需要在镜像名称前面带上用户前缀,如:
- user1/alpine: 由 user1 提供的最新 alpine 镜像, 对应的完整名称为
docker.io/user1/alpine:latest
user1/alpine:3.7 和 user1/alpine:@sha256:abcdef... 这两种方式也是和上面一样,等同于 docker.io/user1/alpine:3.7 和 docker.io/user1/alpine:@sha256:abcdef...
自己搭建的 registry 里的镜像#
引用方式和 docker hub 一样,唯一不同的是需要在镜像名称最前面带上地址,如:
- localhost:5000/alpine: 本地自己搭建的 registry(localhost:5000)里面的官方 alpine 的最新镜像,对应的完整名称为
localhost:5000/library/alpine:latest - localhost:5000/user1/alpine@sha256:a123def…: 本地自己搭建的 registry(localhost:5000)里面由用户 user1 提供的 digest 为 sha256:a123def 的 alpine 镜像
其它的几种情况和上面的类似。
为什么需要镜像的 digest?#
对于某些 image 来说,可能在发布之后还会做一些更新,比如安全方面的,这时虽然镜像的内容变了,但镜像的名称和 tag 没有变,所以会造成前后两次通过同样的名称和 tag 从服务器得到不同的两个镜像的问题,于是 docker 引入了镜像的 digest 的概念,一个镜像的 digest 就是镜像的 manifes 文件的 sha256 码,当镜像的内容发生变化的时候,即镜像的 layer 发生变化,从而 layer 的 sha256 发生变化,而 manifest 里面包含了每一个 layer 的 sha256,所以 manifest 的 sha256 也会发生变化,即镜像的 digest 发生变化,这样就保证了 digest 能唯一的对应一个镜像。
docker pull的大概过程#
如果对 Image manifest,Image Config 和 Filesystem Layers 等概念不是很了解,请先参考 image(镜像)是什么。
取 image 的大概过程如下:
- 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 下载完成,就可以使用了
config 文件中 diffid 是 layer 的 tar 包的 sha256,而 manifest 文件中的 digest 依赖于 media type,比如 media type 是 tar+gzip,那 digest 就是 layer 的 tar 包经过 gzip 压缩后的内容的 sha256,如果 media type 就是 tar 的话,diffid 和 digest 就会一样。dockerd 和 registry 服务器之间的协议为 Registry HTTP API V2。
image 本地存放位置#
这里以 ubuntu 的 image 为例,展示 docker 的 image 存储方式。
先看看 ubuntu 的 image id 和 digest,然后再分析 image 数据都存在哪里。
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
ubuntu latest sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6 f975c5035748 3 weeks ago 112MB
......
repositories.json#
repositories.json 中记录了和本地 image 相关的 repository 信息,主要是 name 和 image id 的对应关系,当 image 从 registry 上被 pull 下来后,就会更新该文件:
#这里目录中的 overlay2 为 docker 后台所采用的存储文件系统名称,
#如果是其他的文件系统的话,名字会是其他的,比如btrfs、aufs、devicemapper等。
$ cat /var/lib/docker/image/overlay2/repositories.json|jq .
{
"Repositories": {
"ubuntu": {
"ubuntu:latest": "sha256:f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232",
"ubuntu@sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6": "sha256:f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232"
}
......
}
}
ubuntu:
repository的名称,前面没有服务器信息的表示这是官方 registry(docker hub) 里面的 repository,里面包含的都是 image 标识和 image ID 的对应关系ubuntu:latest 和 ubuntu@sha256:e348fbb…: 他们都指向同一个image(
sha256:f975c5...)
配置文件(image config)#
docker 根据后台所采用的文件系统不同,在 /var/lib/docker 目录下创建了不同的子目录,对于 CentOS 来说,默认文件系统是 overlay2。本文以 CentOS 为例。
docker 根据第一步得到的 manifest,从 registry 拿到 config 文件,然后保存在 image/overlay2/imagedb/content/sha256/ 目录下,文件名称就是文件内容的 sha256 码,即 image id:
$ sha256sum /var/lib/docker/image/overlay2/imagedb/content/sha256/f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232
f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232 /var/lib/docker/image/overlay2/imagedb/content/sha256/f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232
#这里我们只关注这个 image 的 rootfs,
#从 diff_ids 里可以看出 ubuntu:latest 这个 image 包含了 5 个 layer,
#从上到下依次是从底层到顶层,a94e0d...是最底层,db584c...是最顶层
$ cat /var/lib/docker/image/overlay2/imagedb/content/sha256/f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232|jq .
......
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:a94e0d5a7c404d0e6fa15d8cd4010e69663bd8813b5117fbad71365a73656df9",
"sha256:88888b9b1b5b7bce5db41267e669e6da63ee95736cb904485f96f29be648bfda",
"sha256:52f389ea437ebf419d1c9754d0184b57edb45c951666ee86951d9f6afd26035e",
"sha256:52a7ea2bb533dc2a91614795760a67fb807561e8a588204c4858a300074c082b",
"sha256:db584c622b50c3b8f9b8b94c270cc5fe235e5f23ec4aacea8ce67a8c16e0fbad"
]
}
......
layer 的 diff_id 和 digest 的对应关系#
layer 的 diff_id 存在 image 的配置文件中,而 layer 的 digest 存在 image 的 manifest 中,他们的对应关系被存储在了 image/overlay2/distribution 目录下:
$ tree -d /var/lib/docker/image/overlay2/distribution
/var/lib/docker/image/overlay2/distribution
├── diffid-by-digest
│ └── sha256
└── v2metadata-by-diffid
└── sha256
- diffid-by-digest : 存放
digest到diffid的对应关系 - v2metadata-by-diffid : 存放
diffid到digest的对应关系
#这里以最底层 layer(a94e0d...) 为例,查看其 digest 信息
$ cat /var/lib/docker/image/overlay2/distribution/v2metadata-by-diffid/sha256/db584c622b50c3b8f9b8b94c270cc5fe235e5f23ec4aacea8ce67a8c16e0fbad|jq .
[
{
"Digest": "sha256:b78396653dae2bc0d9c02c0178bd904bb12195b2b4e541a92cd8793ac7d7d689",
"SourceRepository": "docker.io/library/ubuntu",
"HMAC": ""
}
]
#根据 digest 得到 diffid
$ cat /var/lib/docker/image/overlay2/distribution/diffid-by-digest/sha256/b78396653dae2bc0d9c02c0178bd904bb12195b2b4e541a92cd8793ac7d7d689
sha256:db584c622b50c3b8f9b8b94c270cc5fe235e5f23ec4aacea8ce67a8c16e0fbad
layer 的元数据#
layer 的属性信息都放在了 image/overlay2/layerdb 目录下,目录名称是 layer 的 chainid,由于最底层的 layer 的 chainid 和 diffid 相同,所以这里我们用第二层(fe9a3f…)作为示例:
#计算 chainid
#这里 88888b... 是第二层的 diffid,而 a94e0d... 是 88888b... 父层的 chainid,
#由于a94e0d...是最底层,它没有父层,所以 a94e0d... 的 chainid 就是 a94e0d...
$ echo -n "sha256:a94e0d5a7c404d0e6fa15d8cd4010e69663bd8813b5117fbad71365a73656df9 sha256:88888b9b1b5b7bce5db41267e669e6da63ee95736cb904485f96f29be648bfda"|sha256sum -
14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20 -
#根据 chainid 来看看相应目录的内容
$ ll /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20
total 20K
-rw-r--r-- 1 root root 64 Apr 1 22:16 cache-id
-rw-r--r-- 1 root root 71 Apr 1 22:16 diff
-rw-r--r-- 1 root root 71 Apr 1 22:16 parent
-rw-r--r-- 1 root root 3 Apr 1 22:16 size
-rw-r--r-- 1 root root 1.5K Apr 1 22:16 tar-split.json.gz
#每个 layer 都有这样一个对应的文件夹
#cache-id 是 docker 下载 layer 的时候在本地生成的一个随机 uuid,
#指向真正存放 layer 文件的地方
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/cache-id
658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb
#diff 文件存放 layer 的 diffid
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/diff
sha256:88888b9b1b5b7bce5db41267e669e6da63ee95736cb904485f96f29be648bfda
#parent 文件存放当前 layer 的父 layer 的 diffid,
#注意:对于最底层的 layer 来说,由于没有父 layer,所以没有这个文件
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/parent
sha256:a94e0d5a7c404d0e6fa15d8cd4010e69663bd8813b5117fbad71365a73656df9
#当前 layer 的大小,单位是字节
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/size
745
#tar-split.json.gz,layer 压缩包的 split 文件,通过这个文件可以还原 layer 的 tar 包,
#在 docker save 导出 image 的时候会用到
#详情可参考 https://github.com/vbatts/tar-split
$ ll /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/tar-split.json.gz
-rw-r--r-- 1 root root 1.5K Apr 1 22:16 /var/lib/docker/image/overlay2/layerdb/sha256/14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20/tar-split.json.gz
layer数据#
以 CentOS 为例,所有 layer 的文件都放在了 /var/lib/docker/overlay2 目录下。
$ tree -d -L 2 /var/lib/docker/overlay2
├── 658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb
│ ├── diff
│ └── work
├── 66ce99b5da081f65afea7ebaf612229179b620dc728b7407adcb44a51a27ae24
│ ├── diff
│ └── work
...
└── l
├── DYWQJVCIPQ2P2VFWZ4KBCV2JFW -> ../658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb/diff
├── 27O3MGWIL6SIN7K4GLVU4DLPSQ -> ../9028bae38f520a09220f67fbcf698aae2326c8318390a1d6005457d51ad97369/diff
...
”l“ 目录包含一些符号链接作为缩短的层标识符. 这些缩短的标识符用来避免挂载时超出页面大小的限制。
$ ll /var/lib/docker/overlay2/l/
total 0
lrwxrwxrwx 1 root root 72 Mar 29 06:25 27O3MGWIL6SIN7K4GLVU4DLPSQ -> ../9028bae38f520a09220f67fbcf698aae2326c8318390a1d6005457d51ad97369/diff/
lrwxrwxrwx 1 root root 72 Mar 21 00:55 2AYPFAXSXLNCCEA6WRFCAPFPX3 -> ../23eb8415aec245a0291cca62d2da322de241263b8cbdfc690c0a77b353530b10/diff/
lrwxrwxrwx 1 root root 72 Mar 29 02:55 2H2XLZTCOYYSDT3XU2BDJRC2SB -> ../6b27128471bdfc742696ff9820bdfcdda73020753c26efeecea29b98096f0c5d/diff/
lrwxrwxrwx 1 root root 77 Mar 29 05:44 2JQ3OQVJBRYD75J4WTG4CSWA4Z -> ../641300d147b30f162167fed340cebcaae25f46db608939f6af09dbdb7078dcd4-init/diff/
lrwxrwxrwx 1 root root 72 Mar 21 00:55 2TCUOOM7Y7HMGIERRS4CX4YHVA -> ../cd3a3bd11269dc846ee9f79fca86c05336b8dd475d5ca8151991dc5d9fd7261f/diff/
lrwxrwxrwx 1 root root 77 Mar 29 06:24 36WQQRTYLT4P3J7DYLQAUMUPJE -> ../7ee9cc176abeb603ab0461650edd87890d167c579011813d0e864b7524f9fe24-init/diff/
...
/var/lib/docker/ 目录下的目录结构及组织方式也会不一样,要具体文件系统具体分析,本文只介绍 overlay2 这种情况。关于 aufs 和 btrfs 的相关特性可以参考 Linux 文件系统之 aufs 和 Btrfs 文件系统之 subvolume 与 snapshot
还是以刚才的第二层 layer(88888b…)为例,看看实际的数据:
最底层包含 link 文件(不包含 lower 文件,因为是最底层),在上面的结果中 a94e0d... 为最底层。 这个文件记录着作为标识符的更短的符号链接的名字、最底层还有一个 diff 目录(包含实际内容)。
# 查看底层 a94e0d... 的 layer 存放的地方
$ cat /var/lib/docker/image/overlay2/layerdb/sha256/a94e0d5a7c404d0e6fa15d8cd4010e69663bd8813b5117fbad71365a73656df9/cache-id
8feee71ff338d03a22ef090f8e5a49771ca8c1f418db345782ff0fb9b9fff3ce
$ ll /var/lib/docker/overlay2/8feee71ff338d03a22ef090f8e5a49771ca8c1f418db345782ff0fb9b9fff3ce/
total 4.0K
drwxr-xr-x 21 root root 224 Apr 1 22:16 diff/
-rw-r--r-- 1 root root 26 Apr 1 22:15 link
$ cat /var/lib/docker/overlay2/8feee71ff338d03a22ef090f8e5a49771ca8c1f418db345782ff0fb9b9fff3ce/link
3GQZNQYZNRAXT6X453L5O73Y5U
$ ll /var/lib/docker/overlay2/l/|grep 3GQZNQYZNRAXT6X453L5O73Y5U
lrwxrwxrwx 1 root root 72 Apr 1 22:15 3GQZNQYZNRAXT6X453L5O73Y5U -> ../8feee71ff338d03a22ef090f8e5a49771ca8c1f418db345782ff0fb9b9fff3ce/diff/
# diff 目录下面是层的内容
$ ll /var/lib/docker/overlay2/8feee71ff338d03a22ef090f8e5a49771ca8c1f418db345782ff0fb9b9fff3ce/diff/
total 16K
drwxr-xr-x 2 root root 4.0K Feb 28 14:14 bin/
drwxr-xr-x 2 root root 6 Apr 12 2016 boot/
drwxr-xr-x 4 root root 4.0K Feb 28 14:14 dev/
drwxr-xr-x 42 root root 4.0K Feb 28 14:14 etc/
drwxr-xr-x 2 root root 6 Apr 12 2016 home/
...
从第二层开始,每层镜像层包含 lower 文件,该文件的内容表示父层镜像的符号链接,根据这个文件可以索引构建出整个镜像的层次结构。同时还包含 merged 和 work 目录。
- 每当启动一个容器时,会将
link指向的镜像层目录以及lower指向的镜像层目录联合挂载到merged目录,因此,容器内的视角就是merged目录下的内容。 - 而 work 目录则是用来完成如
copy-on_write的操作。
$ tree -L 1 /var/lib/docker/overlay2/658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb
/var/lib/docker/overlay2/658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb
├── diff
├── link
├── lower
└── work
# 父层镜像的符号链接
$ cat /var/lib/docker/overlay2/658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb/lower
l/3GQZNQYZNRAXT6X453L5O73Y5U
$ ll /var/lib/docker/overlay2/658299560be0fd7eaf4a14b0927e134049d13eb31070a9902b0d275836a13cfb/diff
total 0
drwxr-xr-x 4 root root 29 Mar 6 17:17 etc/
drwxr-xr-x 2 root root 21 Mar 6 17:17 sbin/
drwxr-xr-x 3 root root 18 Feb 28 14:13 usr/
drwxr-xr-x 3 root root 17 Feb 28 14:14 var/
manifest文件去哪了?#
从前面介绍 docker pull 的过程中得知,docker 是先得到 manifest,然后根据 manifest 得到 config 文件和 layer。
前面已经介绍了 config 文件和 layer 的存储位置,但唯独不见 manifest,去哪了呢?
manifest 里面包含的内容就是对 config 和 layer 的 sha256 + media type 描述,目的就是为了下载 config 和 layer,等 image 下载完成后,manifest 的使命就完成了,里面的信息对于 image 的本地管理来说没什么用,所以 docker 在本地没有单独的存储一份 manifest 文件与之对应。
结束语#
本篇介绍了image在本地的存储方式,包括了 /var/lib/docker/image 和 /var/lib/docker/overlay2 这两个目录,但 /var/lib/docker/image 下面有两个目录没有涉及:
/var/lib/docker/image/overlay2/imagedb/metadata:里面存放的是本地 image 的一些信息,从服务器上 pull 下来的 image 不会存数据到这个目录,下次有机会再补充这部分内容。/var/lib/docker/image/overlay2/layerdb/mounts: 创建 container 时,docker 会为每个 container 在 image 的基础上创建一层新的 layer,里面主要包含 /etc/hosts、/etc/hostname、/etc/resolv.conf 等文件,创建的这一层 layer 信息就放在这里,后续在介绍容器的时候,会专门介绍这个目录的内容。





