> For the complete documentation index, see [llms.txt](https://pshizhsysu.gitbook.io/docker/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://pshizhsysu.gitbook.io/docker/jing-xiang-de-ben-di-cun-chu.md).

# 镜像存储

## 环境

* 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标识。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-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>

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