本地存储

环境

  • os: centos 7.3-1611

  • kernel: 4.16.13

  • docker-engine: 1.12.6

  • backend-filesystem: xfs(ftype=1)

  • storage-driver: overlay2

镜像准备

首先从docker官网拉取镜像 library/registry:2.5.0,然后用其搭建一个私有镜像仓库 192.168.1.103:8021,然后再把该镜像上传到私有镜像仓库中

目录树

我们在主机A上pull镜像192.168.1.103:8021/library/registry:2.5.0,接下来,我们看这台主机上镜像的存储结构。主机A上docker的安装目录为/app/docker

/app/docker下有多个目录,与镜像相关的有两个:imageoverlay2

$ tree -L 1 /app/docker
/app/docker
├── containers
├── image
├── network
├── overlay2
├── swarm
├── tmp
├── trust
└── volumes
  • image的目录树如下:

$ tree image
image
└── overlay2
    ├── distribution
    │   ├── diffid-by-digest
    │   │   └── sha256
    │   │       ├── 06ba8e23299fcf9dd9efb3c5acd4c9d03badac5392953001c75d38197113a63a
    │   │       ├── 2ee5ed28ffa762104505295c3c256c52a87fe8af0114b9e0198e9036495e10b8
    │   │       ├── 802d2a9c64e8f556e510b4fe6c5378b9d49d8335a766d156ef21c7aeac64c9d6
    │   │       ├── d1562c23a8aa4913a2fc720a3c478121f45d26597b58bbf9a29238276ca420a7
    │   │       └── e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
    │   └── v2metadata-by-diffid
    │       └── sha256
    │           ├── 35039a507f7ae2cb74fd2405e6230036ee912588fcaac4d3c561774817590e97
    │           ├── 3bb5bc5ad373d4855414158babfedcd81a8e27cca04a861a5640c7ec9079bcfb
    │           ├── 4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f
    │           ├── aa3a31ee27f3d041998258e135f623696d2c21a63ddf798ae206322c7d518247
    │           └── d00444e19d6513efe0e586094adb85fe5fc1c425d48e5b94263c65860a75d989
    ├── imagedb
    │   ├── content
    │   │   └── sha256
    │   │       └── c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98
    │   └── metadata
    │       └── sha256
    ├── layerdb
    │   ├── sha256
    │   │   ├── 273edac7c3ab13711e95ed35a4eb397e10ae9b69c896c9ad28b64cb9097be327
    │   │   │   ├── cache-id
    │   │   │   ├── diff
    │   │   │   ├── parent
    │   │   │   ├── size
    │   │   │   └── tar-split.json.gz
    │   │   ├── 3d9b8d55844ef4dc948d650855a2be52c6193502ba13b9afea9169495f254a03
    │   │   │   ├── cache-id
    │   │   │   ├── diff
    │   │   │   ├── parent
    │   │   │   ├── size
    │   │   │   └── tar-split.json.gz
    │   │   ├── 4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f
    │   │   │   ├── cache-id
    │   │   │   ├── diff
    │   │   │   ├── size
    │   │   │   └── tar-split.json.gz
    │   │   ├── aff0ec55a7b1c314b647de027c36c25688f9784fee9ca34cbee0de56309fd5ea
    │   │   │   ├── cache-id
    │   │   │   ├── diff
    │   │   │   ├── parent
    │   │   │   ├── size
    │   │   │   └── tar-split.json.gz
    │   │   └── e553e3aa34103ab20e92e15af09af55aab8a3c8b1608a2f86c2ec3ee38b7ea45
    │   │       ├── cache-id
    │   │       ├── diff
    │   │       ├── parent
    │   │       ├── size
    │   │       └── tar-split.json.gz
    │   └── tmp
    └── repositories.json
  • overlay2

overlay2的目录树如下(注意:这里只显示两层

$ tree -L 2 overlay2
overlay2
├── 0f3f3124f1b247e8f20973ea11218589ec3f93e7acbc531acd9420029928dedf
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── 15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── 3b14865a1ffd6e2926786e44da9176dfc0d98ce9ffffe23b8ed1f40993700960
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── 9d8ccd3c87b4c56076c0e8924d8313ca77f5b2dcff5c8701bae7c91ee115e13c
│   ├── diff
│   ├── link
│   ├── lower
│   ├── merged
│   └── work
├── d388553d01e501540f5866e98bb3949ecfea24704bc900c01272e3fc74353753
│   ├── diff
│   └── link
└── l
    ├── IDKNGIJXKXKT55FYGI7Z6SMRI6 -> ../9d8ccd3c87b4c56076c0e8924d8313ca77f5b2dcff5c8701bae7c91ee115e13c/diff
    ├── KVYTHEQMEN2OKZWGWBCT5FSDUZ -> ../0f3f3124f1b247e8f20973ea11218589ec3f93e7acbc531acd9420029928dedf/diff
    ├── N62LB6PKCXTZOY7MNOFDZKVRL7 -> ../d388553d01e501540f5866e98bb3949ecfea24704bc900c01272e3fc74353753/diff
    ├── OBXTURUR4WON6PIOZ66VAOSWRP -> ../3b14865a1ffd6e2926786e44da9176dfc0d98ce9ffffe23b8ed1f40993700960/diff
    └── YOEFSN7RITY7NUQM33XC3ZIY5N -> ../15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/diff

repositories.json

repositories.json记录了本地repository相关的信息,主要是repository:tagiamgeID的映射关系,如下:

其中c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98就是image-id,51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6是image-digest。如下:

$ docker images --digests
REPOSITORY                            TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
192.168.1.103:8021/library/registry   2.5.0               sha256:51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6   c6c14b3960bd        2 years ago         33.31 MB

ImageConfig

每一个镜像(ImageID)都会有一个配置文件,比如上面的镜像c6c14b3960bd的配置文件就是image/overlay2/imagedb/content/sha256/c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98,查看该文件的内容如下:

在镜像的配置文件中,有该镜像所包含的所有layer,每一个layer用diff-id标识。diff_ids数组中,diff-ids[n]diff-ids[n+1]的parent,diff-ids[0]是最底层,它没有parent,diff-ids[size-1]表示最顶层。

image-id其实就是根据镜像的配置文件做哈希得到的,比如对上面的配置文件的内容做哈希,会发现哈希值就是image-id:

$ sha256sum image/overlay2/imagedb/content/sha256/c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98
c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98

注意:这里我们可以对imageConfig文件重命名然后再做哈希,得到的哈希值还是一样的,也就是说是其实是对文件内容做了哈希

layer-diffid 与 layer-digest的映射关系

image/overlay2/distribution下存储了layer-diffidlayer-digest的映射关系,distribution的目录树如下

distribution
├── diffid-by-digest
│   └── sha256
│       ├── 06ba8e23299fcf9dd9efb3c5acd4c9d03badac5392953001c75d38197113a63a
│       ├── 2ee5ed28ffa762104505295c3c256c52a87fe8af0114b9e0198e9036495e10b8
│       ├── 802d2a9c64e8f556e510b4fe6c5378b9d49d8335a766d156ef21c7aeac64c9d6
│       ├── d1562c23a8aa4913a2fc720a3c478121f45d26597b58bbf9a29238276ca420a7
│       └── e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
└── v2metadata-by-diffid
    └── sha256
        ├── 35039a507f7ae2cb74fd2405e6230036ee912588fcaac4d3c561774817590e97
        ├── 3bb5bc5ad373d4855414158babfedcd81a8e27cca04a861a5640c7ec9079bcfb
        ├── 4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f
        ├── aa3a31ee27f3d041998258e135f623696d2c21a63ddf798ae206322c7d518247
        └── d00444e19d6513efe0e586094adb85fe5fc1c425d48e5b94263c65860a75d989
  • diffid-by-digest: 存放layer-digest到layer-diffid的映射关系

  • v2metadata-by-diffid: 存放layer-diffid到layer-digest的映射关系

查看第一层的layer-digest与layer-diffid的映射关系

$ cat diffid-by-digest/sha256/e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
sha256:4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f

$ jq . v2metadata-by-diffid/sha256/4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f
[
  {
    "Digest": "sha256:e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58",
    "SourceRepository": "192.168.1.103:8021/library/registry"
  }
]

layer的元数据

layer的元数据都存储在image/overlay2/layerdb目录下,见目录树。每一个layer对应着一个sha256下的一个目录,目录的名字为layer的chainid。

第一层的chainid就为diffid,第N层的chainid的算法如下:

chainid(layerN) = sha256("chainid(layer1) chainid(layer2) ... chainid(layerN-1) diffid(layerN)")

我们来算一下第二层的chainid

$ echo -n "sha256:4fe15f8d0ae69e169824f25f1d4da3015a48feeeeebb265cd2e328e15c6a869f sha256:aa3a31ee27f3d041998258e135f623696d2c21a63ddf798ae206322c7d518247" | sha256sum
aff0ec55a7b1c314b647de027c36c25688f9784fee9ca34cbee0de56309fd5ea  -

在每一层对应的目录下有以下五个文件(第一层只有四个):

  • cache-id:docker下载layer的时候在本地生成的一个随机uuid

  • diff:diff 文件存放layer的diffid

  • parent:parent文件存放当前layer的父layer的diffid,注意第一层没有父layer故没有该文件

  • size:该层的大小,单位为字节

  • tar-split.json.gz:layer压缩包的split文件,通过这个文件可以还原layer的tar包,详情可参考 https://github.com/vbatts/tar-split

layer的数据

所有layer的文件系统数据都存放在/app/docker/overlay2目录下,查看[目录树]。每一个layer都对应着该目录下的一个目录,目录的名子就是layer的cache-id。

另外还有一个l目录,在本地有多少个layer,该目录下就有多少个符号链接文件。符号链接文件指向layer的文件系统目录。如下:

lrwxrwxrwx. 1 root root 72 Aug 27 16:34 IDKNGIJXKXKT55FYGI7Z6SMRI6 -> ../9d8ccd3c87b4c56076c0e8924d8313ca77f5b2dcff5c8701bae7c91ee115e13c/diff
lrwxrwxrwx. 1 root root 72 Aug 27 16:34 KVYTHEQMEN2OKZWGWBCT5FSDUZ -> ../0f3f3124f1b247e8f20973ea11218589ec3f93e7acbc531acd9420029928dedf/diff
lrwxrwxrwx. 1 root root 72 Aug 27 16:34 N62LB6PKCXTZOY7MNOFDZKVRL7 -> ../d388553d01e501540f5866e98bb3949ecfea24704bc900c01272e3fc74353753/diff
lrwxrwxrwx. 1 root root 72 Aug 27 16:34 OBXTURUR4WON6PIOZ66VAOSWRP -> ../3b14865a1ffd6e2926786e44da9176dfc0d98ce9ffffe23b8ed1f40993700960/diff
lrwxrwxrwx. 1 root root 72 Aug 27 16:34 YOEFSN7RITY7NUQM33XC3ZIY5N -> ../15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/diff

接下来我们来看layer对应目录下的内容(第一层与其他层的内容会不一样,第一层只有diff目录与link文件)

我们看第二层目录下的内容

ll 15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/
total 8
drwxr-xr-x. 6 root root 50 Aug 27 16:34 diff
-rw-r--r--. 1 root root 26 Aug 27 16:34 link
-rw-r--r--. 1 root root 28 Aug 27 16:34 lower
drwx------. 2 root root  6 Aug 27 16:34 merged
drwx------. 2 root root  6 Aug 27 16:34 work
  • diff

diff目录下是该layer的文件系统,如下:

ll 15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/diff
total 0
drwxr-xr-x. 5 root root 79 Jun 24  2016 etc
drwxr-xr-x. 3 root root 61 Jun 24  2016 lib
drwxr-xr-x. 7 root root 66 Jun 24  2016 usr
drwxr-xr-x. 3 root root 19 Jun 24  2016 var
  • link

link文件的内容就是该layer的符号链接文件的名字:

$ cat 15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/link 
YOEFSN7RITY7NUQM33XC3ZIY5N
  • lower

该文件的内容是父layer的符号链接文件的名字,根据这个文件可以索引构建出整个镜像的层次结构

$ cat 15e3398435f9e280ce76c0006ebb9c01c717e1cf9c167d06cf9ea24fc6f6632d/lower
l/N62LB6PKCXTZOY7MNOFDZKVRL7
  • merged和work 目录

每当启动一个容器时,会将 link 指向的镜像层目录以及 lower 指向的镜像层目录联合挂载到 merged 目录,因此,容器内的视角就是 merged 目录下的内容。

而 work 目录则是用来完成如 copy-on-write 的操作。

在没有启动容器的时候,这两个目录下都是空的。

manifest

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

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

不过我们可以看一下manifest文件长什么样。通过命令curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" 192.168.1.103:8021/v2/library/registry/manfiests/2.5.0下载library/registry:2.5.0的manifest,内容如下:

发现,config.digest就是镜像的image-digest。layers数组中就是各层的layer-digest;layers[0]是第一层,layers[N-1]是最顶层。

镜像的下载过程

  • 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 下载完成,就可以使用了

附录

接下来总结一下各种id

  • image-id

    image-id是imageConfig的sha256的哈希值。

  • image-digest

    image-digest是manifest的sha256的哈希值。

  • layer-diffid

    layer-diffid是对layer的未压缩的tar包的内容做sha256得到的哈希值。

    我们可以通过registry的API下载到某个layer的压缩后的tar包layer.tar.gzip,但是实验中发现手动用tar xzvf layer.tar.gzip -C layer/先解压,然后再用tar cvf layer.tar layer/*打包,对layer.tar做sha256sum layer.tar,得到的哈希值并不是layer的diffid。猜测layer的解压与打包用shell的命令不对或打包时有些参数不对。

  • layer-digest

    layer-digest是对layer的压缩后的tar包的内容做sha256得到的哈希值。

    我们可以通过registry的API GET /v2/{repository}/blobs/{layer-digest} 下载压缩后的layer,然后对该文件内容做sha256哈希,得到的哈希值就是layer-digest。

    比如,我们下载镜像library/registry:2.5.0的第一层:

    curl 192.168.1.103:8021/v2/library/registry/blobs/sha256:e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58 -o layer1.tar.gzip

    下载得到的文件是经过gizp压缩算法压缩的,接下来我们对该layer做哈希(文件名可以随便取,没有影响):

    $ sha256sum layer1.tar.gzip
    e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58

    发现,哈希值就是第一层的layer-digest。

    不过,如果我们尝试用先用tar xzvf layer1.tar.gzip -C layer1/解压,然后再用命令tar czvf layer1.tar.gizp layer1/*压缩,然后再对我们自已压缩过的文件做哈希,得到的哈希值与layer-digest不一致。猜测是不能使用shell的tar命令或者参数不对。

Reference

[1] https://www.yangcs.net/posts/how-manage-image/

[2] https://segmentfault.com/a/1190000009309347

[3] https://segmentfault.com/a/1190000009730986

[4] https://yq.aliyun.com/articles/57752

[5] https://windsock.io/explaining-docker-image-ids/

[6] https://docs.docker.com/registry/spec/api/#pulling-an-image

Last updated