NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
[TOC] ## **环境** * 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: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 ``` ## **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-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的元数据** 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](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