ljzsdut
GitHubToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage
Edit page

Docker Registry镜像存储格式

转载:https://www.kancloud.cn/pshizhsysu/docker/1835364

概述

img

目录分为两层:blobs和repositories。

  • blobs:镜像所有内容的实际存储,包括了镜像层和镜像元信息manifest。
  • repositories是镜像元信息存储的地方,name代表仓库名称
    • 每一个仓库下面又分为_layers、_manifests两个部分
    • _layers负责记录该仓库引用了哪些镜像层文件
    • _manifests负责记录镜像的元信息
      • revisions包含了仓库下曾经上传过的所有版本的镜像元信息
      • tags包含了仓库中的所有标签
        • current记录了当前标签指向的镜像
        • index目录则记录了标签指向的历史镜像
镜像其实就是一系列的由静态文件组成的层(layer),registry是如何存储镜像的呢?

registry会把与镜像有关的全部内容存到一个根目录下,根目录又分为两个目录,一个叫blobs,一个叫repositories。

先看blobs目录。在registry中,blobs可分为三类,一类是组成镜像的层(layer),一类是镜像的manifest文件,一类是镜像的manifest list文件。每一个文件都会计算出其sha256编码,然后用编码的前64位作为目录名建立一个目录,目录中只有一个名为data的文件,该文件中存储的就是相应的数据。把所有的64位编码的目录放在同一个根目录下就可以了,但是为了便于索引,再提取前两位,建立更高一层的目录。这样在所有的64位编码的目录中,前两位重复的就自然而然的放到同一个目录下。blobs目录的一个示例如下图所示:

img

然后是repositories目录,该目录的结构要比blobs目录复杂的多,repositories目录下首先是各个仓库组成的目录,每个仓库一个目录,目录的名字就是仓库的名字。

然后在每一个仓库下面都有三个目录,分别是:_layers,_manifests,_uploads 。

_uploads目录不用过多关注,当我们向registry上传镜像时,该目录会用来存放正在上传的镜像数据,上传结束后,所有数据会移动到blobs目录下,_uplpads目录就为空了。

而_layers,_manifests目录下全部都是link文件,这些link文件链接到blobs目录下的对应文件。之前说过,blobs目录下的文件分为三类:layer文件、manifest文件、manifest list文件。_layers目录下的link文件,与blobs目录下保存的属于该仓库镜像的layer文件一一对应。而_manifests目录又可分为两个子目录,一个是tags,一个是revisions,revisions目录下保存的就是所有版本的manifest文件和所有版本的manifest list文件的link文件。而tags目录则把该仓库按照镜像的不同版本进行分类(比如ubuntu仓库有20.04和18.04两个版本),每一个版本一个目录,每一个版本的目录下又有两个目录,一个是current,一个是index,current目录下保存的是当前版本的manifest文件的link文件,链接到blobs目录下的相应manifest文件。index目录是为了支持删除操作的,保存了当前版本的所有manifest文件的链接,当执行删除操作时,通过index目录可以将与该tag相关的所有blob进行删除。repositories目录的一个示例如下:

img

本地存储

环境

  • 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`下有多个目录,与镜像相关的有两个:`image`与`overlay2
$ 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标识。diffi-ds数组中,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-digestlayer-diffid的映射关系
  • v2metadata-by-diffid: 存放layer-diffidlayer-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.gzipe110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
    

    发现,哈希值就是第一层的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

Registry中的存储

一、环境

与上篇文章使用同一台主机

二、安装Harbor

首先,使用的是harbor-offline-installer-0.5.0.tgz安装的一个Harbor,数据目录设置为/data,然后把library/registry:2.5.0镜像上传到harbor中

三、查看registry的目录结构

镜像仓库挂载到本地的根目录为/data/registry,我们查看上传library/registry:2.5.0镜像后的目录树:

/data/registry/
└── docker
    └── registry
        └── v2
            ├── blobs
            │    └── sha256
            │        ├── 06
            │        │   └── 06ba8e23299fcf9dd9efb3c5acd4c9d03badac5392953001c75d38197113a63a
            │        │       └── data
            │        ├── 2e
            │        │   └── 2ee5ed28ffa762104505295c3c256c52a87fe8af0114b9e0198e9036495e10b8
            │        │       └── data
            │        ├── 51
            │        │   └── 51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6
            │        │       └── data
            │        ├── 80
            │        │   └── 802d2a9c64e8f556e510b4fe6c5378b9d49d8335a766d156ef21c7aeac64c9d6
            │        │       └── data
            │        ├── c6
            │        │   └── c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98
            │        │       └── data
            │        ├── d1
            │        │   └── d1562c23a8aa4913a2fc720a3c478121f45d26597b58bbf9a29238276ca420a7
            │        │       └── data
            │        └── e1
            │            └── e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
            │                └── data
            └── repositories
                └── library
                    └── registry
                        ├── _layers
                        │   └── sha256
                        │       ├── 06ba8e23299fcf9dd9efb3c5acd4c9d03badac5392953001c75d38197113a63a
                        │       │   └── link
                        │       ├── 2ee5ed28ffa762104505295c3c256c52a87fe8af0114b9e0198e9036495e10b8
                        │       │   └── link
                        │       ├── 802d2a9c64e8f556e510b4fe6c5378b9d49d8335a766d156ef21c7aeac64c9d6
                        │       │   └── link
                        │       ├── c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98
                        │       │   └── link
                        │       ├── d1562c23a8aa4913a2fc720a3c478121f45d26597b58bbf9a29238276ca420a7
                        │       │   └── link
                        │       └── e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58
                        │           └── link
                        ├── _manifests
                        │   ├── revisions
                        │   │   └── sha256
                        │   │       └── 51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6
                        │   │           └── link
                        │   └── tags
                        │       └── 2.5.0
                        │           ├── current
                        │           │   └── link
                        │           └── index
                        │               └── sha256
                        │                   └── 51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6
                        │                       └── link
                        └── _uploads

数据都在/data/registry/docker/registry/v2下,接下来我们使用的相对路径都是在该目录下。

3.1 blobs

3.1.1 镜像层文件

blobs目录下存储着所有的layer压缩包。根据《镜像的本地存储》一文可以知道,library/registry:2.5.0只有五个layer,分别是blobs目录下的(注意如果docker的版本不一样,五个镜像层的命字会不一样)

06ba8e23299fcf9dd9efb3c5acd4c9d03badac5392953001c75d38197113a63a
2ee5ed28ffa762104505295c3c256c52a87fe8af0114b9e0198e9036495e10b8
802d2a9c64e8f556e510b4fe6c5378b9d49d8335a766d156ef21c7aeac64c9d6
d1562c23a8aa4913a2fc720a3c478121f45d26597b58bbf9a29238276ca420a7
e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58

这五个目录下的data文件是二进制文件。对这些二进制文件的内容做sha256哈希,得到的哈希值就是目录的名字。比如:

$ sha256sum blobs/sha256/e1/e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58/data
e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58

对这些data文件解压,得到的就是该layer的文件系统(目录与文件),比如:

$ mkdir layer
$ tar xzf blobs/sha256/e1/e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58/data -C layer
$ ll layer/
total 12
drwxr-xr-x. 2 root root 4096 Jun 24 2016 bin
drwxr-xr-x. 4 root root 40 Jun 24 2016 dev
drwxr-xr-x. 14 root root 4096 Jun 24 2016 etc
drwxr-xr-x. 2 root root 6 Jun 24 2016 home
drwxr-xr-x. 5 root root 188 Jun 24 2016 lib
lrwxrwxrwx. 1 root root 12 Jun 24 2016 linuxrc -> /bin/busybox
drwxr-xr-x. 5 root root 44 Jun 24 2016 media
drwxr-xr-x. 2 root root 6 Jun 24 2016 mnt
drwxr-xr-x. 2 root root 6 Jun 24 2016 proc
drwx------. 2 root root 6 Jun 24 2016 root
drwxr-xr-x. 2 root root 6 Jun 24 2016 run
drwxr-xr-x. 2 root root 4096 Jun 24 2016 sbin
drwxr-xr-x. 2 root root 6 Jun 24 2016 srv
drwxr-xr-x. 2 root root 6 Jun 24 2016 sys
drwxrwxrwt. 2 root root 6 Jun 24 2016 tmp
drwxr-xr-x. 7 root root 66 Jun 24 2016 usr
drwxr-xr-x. 12 root root 125 Jun 24 2016 var

值得注意的是,虽然我们可以用tar命令把layer压缩包解压,但是解压后我们用tar命令重新压缩tar czvf layer.tar.gzip layer/*,再对压缩后的layer.tar.gzip做sha256哈希,得到的hash值与直接对data做哈希得到的值不一样。实验证明不是文件名的问题,对data文件重命名,哈希值都是一样的,因为哈希是对文件内容做的,与文件名无关。猜测是data的打包压缩方式与直接用tar命令不一样。

3.1.2 manifest与config文件

blobs目录下除了上述五个目录外,还有另外两个:

c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98
51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6

如果我们在某台主机上下载该镜像,会发现二者分别是镜像library/registry:2.5.0的image-id及digest

c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98 : image-id,51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6 : digest。

可以在主机的执行如下的命令验证:

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

image-id是镜像的config文件的哈希值:

$ sha256sum blobs/sha256/c6/c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98/data
c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98

我们可以查看镜像的config文件的内容(不要用cat命令,用jq命令)

$ jq . blobs/sha256/c6/c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98/data
{
   输出省略...
}

从config文件可以看出,library/registry:2.5.0有五个layer。不过在该文件里,使用的是layer的diffid。

image-digest是镜像的manifest文件的哈希值:

$ sha256sum blobs/sha256/51/51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6/data
51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6

查看digest文件的内容如下(与查看config文件一样的命令查看):

从manifest文件也同样可以看出,镜像library/registry:2.5.0有五个layer。只不过在该文件里,使用的是layer的digest。从mediaType字段可以看出,blobs目录下面layer的data文件是经过tar及gzip算法进行了打包与压缩的。

这里讲一下layer的diffid与digest的区别:

  • layer-diffid:未压缩的layer打包文件的哈希值
  • layer-digest:压缩后的layer打包文件的哈希值

3.2 repositories

我们上传镜像library/registry:2.5.0后,便会在repositories目录下生成library/registry目录。接下来我们都以**repositories/library/registry**作为当前路径

该目录下有三个目录 _layers_manifests_uploads

3.2.1 _layers

_layers目录下存储了该repository的所有layer的索引(layer-digest)以及所有的image的索引(image-id)。例如,我们查看某个layer下link文件的内容及image-id下link文件的内容如下:

$ cat _layers/sha256/e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58/link
sha256:e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58

$ cat _layers/sha256/c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98/link
sha256:c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98

link文件的内容指向blobs中的实际数据。

registry的API-v2中,下载layer的接口为GET /v2/{repository}/blobs/{layer-digest},根据路径参数repositorylayer-digest,就可以在repositories/{repository}/_layers目录下搜索layer-digest,然后去blobs目录下下载。

3.2.2 _manifests

_manifests目录下存储的实际是镜像的digest以及镜像的tag到digest的映射关系。revisions/下存储的就是image-digest,tags/下就是tag到image-digest的映射。

目录树如下:

$ tree _manifests
_manifests/
├── revisions
│   └── sha256
│       └── 51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6
│           └── link
└── tags
    └── 2.5.0
        ├── current
        │   └── link
        └── index
            └── sha256
                └── 51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6
                    └── link

上面三个link文件的内容全部是sha256:51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6,就是镜像library/registry:2.5.0的image-digest,指向实际的manifest文件blobs/sha256/51/51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6

在registry的API-v2中,下载镜像的manifest文件的接口为GET /v2/{repository}/mainfests/{tag_or_digest}。如果提供是的是tag 2.5.0,根据tags/2.5.0/current/link文件的内容,得到的manifest文件为blobs/sha256/51/51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6;如果提供是的digest sha256:51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6,根据revisions/sha256/51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6/link文件的内容,得到的manifest文件还是blobs/sha256/51/51d8869caea35f58dd6a2309423ec5382f19c4e649b5d2c0e3898493f42289d6

如何判断两个镜像是否是同一个

有时候,我们需要从google下载镜像,但是由于网络原因又下载不下来。此时,网上有教程说,阿里云同步了google的镜像,从阿里云下载就可以了。

然而,我们担心,阿里云上的镜像与google上的镜像,内容是否一样呢(镜像层一样)?本文将介绍如何判断两个镜像,内容是否一样。

根据Digest

同一台主机,不同Registry

有人说,我们在本地有两个不同Registry来源的镜像,能不能根据Digest来判断两个镜像内容是否一样?答案为“否”。

我们回顾前两节的内容,镜像的Digest是镜像的Manifest文件哈希值。Manifest文件在本地是没有存储的,只在Registry端才有存储。如果再回顾Digest文件的内容,大致如下:

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 3017,
      "digest": "sha256:c6c14b3960bdf9f5c50b672ff566f3dabd3e450b54ae5496f326898513362c98"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 2310286,
         "digest": "sha256:e110a4a1794126ef308a49f2d65785af2f25538f06700721aad8283b81fdfa58"
      },
      ...
   ]
}

根据上面的内容可以发现,镜像的Manifest文件在Registry端的存储是有版本(schemaVersion)和类型(mediaType)的。

也就是说,对于同一个镜像,上传到不同的Registry,其Manifest文件的内容在不同的Registry中可能不一样。即同一个镜像,在不同的Registry中的Digest可能不一样

我们可以做如下的实验,首先我们从docker hub下载一个镜像registry:2.5.0

$ docker pull registry:2.5.0
2.5.0: Pulling from library/registry
e110a4a17941: Pull complete 
2ee5ed28ffa7: Pull complete 
d1562c23a8aa: Pull complete 
06ba8e23299f: Pull complete 
802d2a9c64e8: Pull complete 
Digest: sha256:1b68f0d54837c356e353efb04472bc0c9a60ae1c8178c9ce076b01d2930bcc5d
Status: Downloaded newer image for registry:2.5.0

$ docker images --digests
REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
registry            2.5.0               sha256:1b68f0d54837c356e353efb04472bc0c9a60ae1c8178c9ce076b01d2930bcc5d   c6c14b3960bd        3 years ago         33.3MB

接着,我们把这个镜像重新打一个tag

$ docker tag registry:2.5.0 10.142.232.151:8021/library/registry:2.5.0

$ docker images --digests
REPOSITORY                             TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
10.142.232.151:8021/library/registry   2.5.0               <none>                                                                    c6c14b3960bd        3 years ago         33.3MB
registry                               2.5.0               sha256:1b68f0d54837c356e353efb04472bc0c9a60ae1c8178c9ce076b01d2930bcc5d   c6c14b3960bd        3 years ago         33.3MB

此时,我们竟然发现,重新打上tag的镜像10.142.232.151:8021/library/registry:2.5.0竟然没有Digest。

接着,我们把这个镜像上传到我们自已搭建的Harbor仓库10.142.232.151:8021

$ docker push 10.142.232.151:8021/library/registry:2.5.0
The push refers to repository [10.142.232.151:8021/library/registry]
3bb5bc5ad373: Pushed 
35039a507f7a: Pushed 
d00444e19d65: Pushed 
aa3a31ee27f3: Pushed 
4fe15f8d0ae6: Pushed 
2.5.0: digest: sha256:bf0b4fc3833f908017d650af94071d62a12390020b30658dd623b98b80af81ed size: 1363

根据push的输出日志,我们看到Harbor返回了一个Digest,值为sha256:bf0b4fc3833f908017d650af94071d62a12390020b30658dd623b98b80af81ed,与registry:2.5.0的digest不一样

$ docker images --digests
docker images --digests
REPOSITORY                             TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
10.142.232.151:8021/library/registry   2.5.0               sha256:bf0b4fc3833f908017d650af94071d62a12390020b30658dd623b98b80af81ed   c6c14b3960bd        3 years ago         33.3MB
registry                               2.5.0               sha256:1b68f0d54837c356e353efb04472bc0c9a60ae1c8178c9ce076b01d2930bcc5d   c6c14b3960bd        3 years ago         33.3MB

也说是说,同一个镜像,存储在不同的Registry端,其Digest是不一样的。

相同Registry,不同主机

假如我们在两台不同的主机上下载registry:2.5.0,那么它的Digest是否一样呢?两台主机上下载下来的镜像是否是同一个呢?

答案是,不同主机从同一个Registry下载相同名字的镜像,两台主机上的镜像的Digest可能是一样的,如果一样,也不能表明两台主机上下载的是同一个镜像。

比如我们在amd主机上和arm主机上下载镜像centos:7,我们会发现,两台主机上镜像的Digest是一样的,但ImageID与镜像大小不一样

[root@amd] $ docker images --digests
REPOSITORY                             TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
centos                                 7                   sha256:4a701376d03f6b39b8c2a8f4a8e499441b0d567f9ab9d58e4991de4472fb813c   5e35e350aded        6 weeks ago         203MB
[root@arm] $ docker images --digests
REPOSITORY                    TAG                     DIGEST                                                                    IMAGE ID            CREATED             SIZE
centos                        7                       sha256:4a701376d03f6b39b8c2a8f4a8e499441b0d567f9ab9d58e4991de4472fb813c   4dfd99be812b        6 weeks ago         273MB

根据ImageID

同一台主机,不同镜像名

如果两个镜像(名字不同)在同一台主机上的ImageID是一样的,那么这两个镜像就是同一个镜像(内容是一样的)。如果在同一台主机上,两个名字的镜像的ImageID不一样,那么这两个镜像肯定不是同一个镜像。

相同Registry,不同主机

假如我们在两台不同的主机上下载registry:2.5.0,那么它的ImageID是否一样呢?两台主机上下载下来的镜像是否是同一个呢?

参考上文的Digest中的“相同Registry,不同主机”

总结

可以确定两个镜像一样的方法

  • 如果两个镜像的ImageID一样,那么这两个镜像一样,不管两个镜像在同一台主机上,或者不是同一台主机上(一般对于同一个镜像,如果两台主机的存储驱动一样,这两个镜像的ImageID一般是一样的)

Registry Notification

配置

在registry的配置文件中,notification的配置示例如下:

notifications:
    endpoints:
      - name: alistener
        url: https://mylistener.example.com/event
        headers:
          Authorization: [Bearer <your token, if needed>]
        timeout: 500ms
        threshold: 5
        backoff: 1s
  • timeout:向notification发起的请求500ms超时
  • threshold、backoff:请求连续失败5次后,停止1s,再重新发起请求

Events

当registry每pull或push一个layer或manifest的时候,都发产生一个Event。Event是一个json结构体,如下:

Envelope

registry发送给notification server是一个信封,信封中包含一个或多个Event,这些Event不一定有什么关联,信封的json格式如下:

{
   "events" : [ ... ]
}

harbor

harbor中notification server的代码如下:

to be continued

Registy Auth

https://www.kancloud.cn/pshizhsysu/docker/1835408

Registy API

https://www.kancloud.cn/pshizhsysu/docker/1835411