docker 在本地如何管理 image(镜像)?


探索 image 的获取和存储方式

docker 在本地如何管理 image(镜像)?

探索 image 的获取和存储方式

Mon Apr 2, 2018

4100 Words|Read in about 9 Min|本文总阅读量
Tags: docker  

docker 里面可以通过 docker pulldocker builddocker commitdocker loaddocker import 等方式得到一个 image,得到 image 之后 docker 在本地是怎么存储的呢?本篇将以 docker pull 为例,简述 image 的获取和存储方式。

1. 镜像相关的配置


docker 里面和 image 有关的目录为 /var/lib/docker,里面存放着 image 的所有信息,可以通过下面这个 dockerd 的启动参数来修改这个目录的路径。

--graph, -g /var/lib/docker Root of the Docker runtime

2. 镜像的引用方式


在需要引用 image 的时候,比如 docker pull 的时候,或者运行容器的时候,都需要指定一个image名称,引用一个镜像有多种方式,下面以 alpine 为例进行说明.

Note


由于 sha256 码太长,所以用 abcdef... 来表示完整的 sha256,节约空间

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/[email protected]:abcdef...

docker hub 上的非官方(个人)镜像

引用方式和官方镜像一样,唯一不同的是需要在镜像名称前面带上用户前缀,如:

  • user1/alpine: 由 user1 提供的最新 alpine 镜像, 对应的完整名称为 docker.io/user1/alpine:latest

user1/alpine:3.7user1/alpine:@sha256:abcdef... 这两种方式也是和上面一样,等同于 docker.io/user1/alpine:3.7docker.io/user1/alpine:@sha256:abcdef...

自己搭建的 registry 里的镜像

引用方式和 docker hub 一样,唯一不同的是需要在镜像名称最前面带上地址,如:

  • localhost:5000/alpine: 本地自己搭建的 registry(localhost:5000)里面的官方 alpine 的最新镜像,对应的完整名称为 localhost:5000/library/alpine:latest
  • localhost:5000/user1/[email protected]:a123def…: 本地自己搭建的 registry(localhost:5000)里面由用户 user1 提供的 digest 为 sha256:a123def 的 alpine 镜像

其它的几种情况和上面的类似。

为什么需要镜像的 digest?

对于某些 image 来说,可能在发布之后还会做一些更新,比如安全方面的,这时虽然镜像的内容变了,但镜像的名称和 tag 没有变,所以会造成前后两次通过同样的名称和 tag 从服务器得到不同的两个镜像的问题,于是 docker 引入了镜像的 digest 的概念,一个镜像的 digest 就是镜像的 manifes 文件的 sha256 码,当镜像的内容发生变化的时候,即镜像的 layer 发生变化,从而 layersha256 发生变化,而 manifest 里面包含了每一个 layersha256,所以 manifestsha256 也会发生变化,即镜像的 digest 发生变化,这样就保证了 digest 能唯一的对应一个镜像。

3. 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 服务器发请求(里面包含配置文件的 sha256media type),拿到 image 的配置文件(Image Config
  • 根据配置文件中的 diff_ids(每个 diffid 对应一个 layer tar 包的 sha256,tar 包相当于 layer 的原始格式),在本地找对应的 layer 是否存在
  • 如果 layer 不存在,则根据 manifest 里面 layer 的 sha256media type 去服务器拿相应的 layer(相当去拿压缩格式的包)。
  • 拿到后进行解压,并检查解压后 tar 包的 sha256 能否和配置文件(Image Config)中的 diff_id 对的上,对不上说明有问题,下载失败
  • 根据 docker 所用的后台文件系统类型,解压 tar 包并放到指定的目录
  • 等所有的 layer 都下载完成后,整个 image 下载完成,就可以使用了

Note


对于 layer 来说,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

4. image 本地存放位置


这里以 ubuntu 的 image 为例,展示 docker 的 image 存储方式。

先看看 ubuntu 的 image iddigest,然后再分析 image 数据都存在哪里。

$ docker images --digests

REPOSITORY                                           TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
ubuntu                                               latest              sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6   f975c5035748        3 weeks ago         112MB
......

Note


对于本地生成的镜像来说,由于没有上传到 registry 上去,所以没有 digest,因为镜像的 manifest 由 registry 生成。

repositories.json

repositories.json 中记录了和本地 image 相关的 repository 信息,主要是 nameimage id 的对应关系,当 image 从 registry 上被 pull 下来后,就会更新该文件:

#这里目录中的 overlay2 为 docker 后台所采用的存储文件系统名称,
#如果是其他的文件系统的话,名字会是其他的,比如btrfs、aufs、devicemapper等。
$ cat /var/lib/docker/image/overlay2/repositories.json|jq .

{
    "Repositories": {
        "ubuntu": {
            "ubuntu:latest": "sha256:f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232",
            "[email protected]:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6": "sha256:f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232"
        }
        ......
    }
}
  • ubuntu: repository 的名称,前面没有服务器信息的表示这是官方 registry(docker hub) 里面的 repository,里面包含的都是 image 标识和 image ID 的对应关系

  • ubuntu:latest 和 [email protected]: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: 存放 digestdiffid 的对应关系
  • v2metadata-by-diffid: 存放 diffiddigest 的对应关系
#这里以最底层 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…)作为示例:

Note


计算 chainid 时,用到了所有祖先 layer 的信息,从而能保证根据 chainid 得到的 rootfs 是唯一的。比如我在 debian 和 ubuntu 的 image 基础上都添加了一个同样的文件,那么 commit 之后新增加的这两个 layer 具有相同的内容,相同的 diffid,但由于他们的父 layer 不一样,所以他们的 chainid 会不一样,从而根据 chainid 能找到唯一的 rootfs。计算 chainid 的方法请参考 image spec

#计算 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

5. 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/
...

Note


注意:由于 docker 所采用的文件系统不同,/var/lib/docker/ 目录下的目录结构及组织方式也会不一样,要具体文件系统具体分析,本文只介绍 overlay2 这种情况。 关于 aufs 和 btrfs 的相关特性可以参考 Linux 文件系统之 aufsBtrfs 文件系统之 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 文件,该文件的内容表示父层镜像的符号链接,根据这个文件可以索引构建出整个镜像的层次结构。同时还包含 mergedwork 目录。

  • 每当启动一个容器时,会将 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/

6. manifest文件去哪了?


从前面介绍 docker pull 的过程中得知,docker 是先得到 manifest,然后根据 manifest 得到 config 文件和 layer

前面已经介绍了 config 文件和 layer 的存储位置,但唯独不见 manifest,去哪了呢?

manifest 里面包含的内容就是对 config 和 layer 的 sha256 + media type 描述,目的就是为了下载 config 和 layer,等 image 下载完成后,manifest 的使命就完成了,里面的信息对于 image 的本地管理来说没什么用,所以 docker 在本地没有单独的存储一份 manifest 文件与之对应。

7. 结束语


本篇介绍了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 信息就放在这里,后续在介绍容器的时候,会专门介绍这个目录的内容。

8. 参考



-----------他日江湖相逢 再当杯酒言欢-----------

「真诚赞赏,手留余香」

杨传胜

真诚赞赏,手留余香

使用微信扫描二维码完成支付

See Also

隐藏