Docker Registry镜像存储格式
转载:https://www.kancloud.cn/pshizhsysu/docker/1835364

目录分为两层: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目录的一个示例如下图所示:

然后是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目录的一个示例如下:

- 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记录了本地repository相关的信息,主要是repository:tag到iamgeID的映射关系,如下:
其中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
每一个镜像(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文件重命名然后再做哈希,得到的哈希值还是一样的,也就是说是其实是对文件内容做了哈希
image/overlay2/distribution下存储了layer-diffid与layer-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的元数据都存储在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的文件系统数据都存放在/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 的操作。
在没有启动容器的时候,这两个目录下都是空的。
前面已经介绍了 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命令或者参数不对。
[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
与上篇文章使用同一台主机
首先,使用的是harbor-offline-installer-0.5.0.tgz安装的一个Harbor,数据目录设置为/data,然后把library/registry:2.5.0镜像上传到harbor中
镜像仓库挂载到本地的根目录为/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下,接下来我们使用的相对路径都是在该目录下。
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命令不一样。
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打包文件的哈希值
我们上传镜像library/registry:2.5.0后,便会在repositories目录下生成library/registry目录。接下来我们都以**repositories/library/registry**作为当前路径。
该目录下有三个目录 _layers、_manifests、_uploads。
_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},根据路径参数repository与layer-digest,就可以在repositories/{repository}/_layers目录下搜索layer-digest,然后去blobs目录下下载。
_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上的镜像,内容是否一样呢(镜像层一样)?本文将介绍如何判断两个镜像,内容是否一样。
有人说,我们在本地有两个不同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: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不一样,那么这两个镜像肯定不是同一个镜像。
假如我们在两台不同的主机上下载registry:2.5.0,那么它的ImageID是否一样呢?两台主机上下载下来的镜像是否是同一个呢?
参考上文的Digest中的“相同Registry,不同主机”
- 如果两个镜像的ImageID一样,那么这两个镜像一样,不管两个镜像在同一台主机上,或者不是同一台主机上(一般对于同一个镜像,如果两台主机的存储驱动一样,这两个镜像的ImageID一般是一样的)
在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,再重新发起请求
当registry每pull或push一个layer或manifest的时候,都发产生一个Event。Event是一个json结构体,如下:
registry发送给notification server是一个信封,信封中包含一个或多个Event,这些Event不一定有什么关联,信封的json格式如下:
{
"events" : [ ... ]
}
harbor中notification server的代码如下:
to be continued