Docker镜像在OverlayFs下的存储细节

之前一直对Docker镜像存储的细节不太清楚,甚至以为Docker ImageId 就等同于Digest,最近看到几篇博文,结合自己测试环境的实操,进行如下记录:

Docker镜像拉取的过程

Docker镜像在OverlayFs下的存储细节_第1张图片

Docker Registry Api可参考 Docker Registry HTTP API V2

为什么要拉取两种类型的配置文件?

  • manifest 是针对registry服务端的配置信息
  • Image config 是 针对本地镜像的描述

为什么要有Digest:

  • digest是对 manifest文件的 sha256,如果镜像名称没变,tag没变,只是增加了一层的话,仅仅从镜像名+tag上是无法得知这个镜像是否变更
  • 所以镜像变化了,manifest内容也会变化,从而digest sha256这个单向算法导致的结果也会变化,从digest变化可以知道仓库中的镜像更新了
  • digest和imageId是两个不同的属性,是一一对应的关系,而digest必须是通过registry拿到的镜像才会生成,本地的导入的镜像没有此属性

Docker镜像在本地的存储

在装有docker-cli的机器上执行 docker info 可以看到 Docker Root Dir: /var/lib/docker(如果没有修改过Docker 数据存储家目录的话)

docker目录结构

# ll /var/lib/docker
total 48
drwx------   2 root root    24 May 20 17:12 builder
drwx--x--x   4 root root    92 May 20 17:12 buildkit
drwx------  43 root root  8192 Jun  9 08:41 containers
drwx------   3 root root    22 May 20 17:12 image
drwxr-x---   3 root root    19 May 20 17:12 network
drwx------ 325 root root 32768 Jun  9 08:41 overlay2
drwx------   4 root root    32 May 20 17:12 plugins
drwx------   2 root root     6 Jun  8 13:56 runtimes
drwx------   2 root root     6 May 20 17:12 swarm
drwx------   2 root root     6 Jun  8 13:57 tmp
drwx------   2 root root     6 May 20 17:12 trust
drwx------   2 root root    25 May 20 17:12 volumes

和镜像存储有关的信息如下:

  • overlay2: 镜像和容器的层信息
  • image:存储镜像元相关信息

image目录

# tree -L 1 image/overlay2/
image/overlay2/
├── distribution
├── imagedb
├── layerdb
└── repositories.json

repositories.json

由于本地的文件比较长,选择其中一段进行示例:

~> docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
d51af753c3d3: Pull complete
fc878cd0a91c: Pull complete
6154df8ff988: Pull complete
fee5db0ff82f: Pull complete
Digest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

 ~> docker images --digests --no-trunc
REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID                                                                  CREATED             SIZE
ubuntu              latest              sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7   sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01   6 weeks ago         73.9MB


docker-desktop:/var/lib/docker/image/overlay2# cat repositories.json
{
  "Repositories": {
    "ubuntu": {
      "ubuntu:latest": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01",
      "ubuntu@sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01"
    }
  }
}

repositories.json就是存储镜像信息,主要是name和image id的对应,digest和image id的对应。当pull镜像的时候会更新这个文件。

docker images --no-trunc  --digests | grep kubectl   #查看一个本地镜像
REPOSITORY                                                             TAG                            DIGEST                                                                    IMAGE ID 
10.6.6.197:5000/kubesphere/kubectl                                     v1.0.0                         sha256:e16a56d8c79e76c775ea2d2d2559e4c5d2e78f750f6e075e183948a8dd6e6ebb   sha256:7f81664a09d02c3ee209254d7fdf8559a63c1e492a30c02171c14f82fcd5bff2   7 months ago        82.1MB
kubesphere/kubectl                                                     v1.0.0                         <none>                                                                    sha256:7f81664a09d02c3ee209254d7fdf8559a63c1e492a30c02171c14f82fcd5bff2   7 months ago        82.1MB

可以看到,本地导入的那个镜像没有digest,所以也证明了digest是由registry生成的,但是镜像id都是确定唯一的

在harbor registry中调用 registry v2 api获取manifest并验证

注意每个token对应的都是具体的镜像和操作,一旦有所修改就要重新更换token

curl -iksL -X GET -u admin:123456 http://10.6.6.197/service/token?account=admin\&service=harbor-registry\&scope=repository:library/ubuntu:pull
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 09 Jun 2020 06:20:16 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 1197
Connection: keep-alive
Set-Cookie: beegosessionID=146e3af327ebbb82523dd4730061540e; Path=/; HttpOnly

{
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjVDQkU6Mk5SVDpVSkhDOjVHQUY6S1lVVzpFNVlLOkgzTDY6UUJMMjo3UlpDOllSWk06U1ZIVDpXSlkzIn0.eyJpc3MiOiJoYXJib3ItdG9rZW4taXNzdWVyIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJoYXJib3ItcmVnaXN0cnkiLCJleHAiOjE1OTE2ODU0MTYsIm5iZiI6MTU5MTY4MzYxNiwiaWF0IjoxNTkxNjgzNjE2LCJqdGkiOiIwWkNLQk1zMG1YOUtVQWJqIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJsaWJyYXJ5L3VidW50dSIsImFjdGlvbnMiOlsicHVzaCIsIioiLCJwdWxsIl19XX0.TatY48UpXGFeHNyKRn047kmke_tyi2qGCGKPkdeNSMm8Tihfd8jZicgysTXnT6m-OhWVpSiMkiiy83wDgHGlLHYWu9_0nGv42tA6xz7LOzp1GSr-7jlqc_qHfZ0seUfDhlYTYA5FWxAIaXYk026PYT5-ZhH_s0IbIndnfhHf1Gcjp79cr0dsyoWAe1wpo9jFjXyNceejtI9Z-XT0fWbu71wTdgQ_0vA9ANPh3qfbi_EEFQdYvIMXcLdZINV6yxtCNudY7mKq_nyXRnUgNaMcbMRXVw-Eq3suiwyxyuAd0KiagvsMSMh5dQ7tEJSkBi0HePT__nIbobeUuepT5nQr2WDX8vqev26b_30kW6c6UebBXUw3_k0EAxpg30TGWbcBgVDR2dMlYQsNmA_3Nnb6rLTNpKl-nR8XHlw8v4N5F0RaYDPYpeMnu_m8mS8KF8PbK9JrHyZs5K-1YsLTYflW5Z3aOFRVwjOfe2nDvMgSfNYcukvncp8KGrlCeb5A3obPZF02o1mbSrxwieSpvUomNVatFJZ9FeP9-pIel3TrI2WSoj0tsQkCyz_E5563PWzpW6A7Mf9GUcKeww_ocBUU1wZScKYAudayuwC53PBbQ70ojGPLzrXJucbZT7O1FBJ-fEQj5-Gg8rzurj2PXJDiKnjVwPzwGpr632T3y1Iqu-o",
  "expires_in": 1800,
  "issued_at": "2020-06-09T06:20:16Z"
}

curl -ikL -X GET -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjVDQkU6Mk5SVDpVSkhDOjVHQUY6S1lVVzpFNVlLOkgzTDY6UUJMMjo3UlpDOllSWk06U1ZIVDpXSlkzIn0.eyJpc3MiOiJoYXJib3ItdG9rZW4taXNzdWVyIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJoYXJib3ItcmVnaXN0cnkiLCJleHAiOjE1OTE2ODU0MTYsIm5iZiI6MTU5MTY4MzYxNiwiaWF0IjoxNTkxNjgzNjE2LCJqdGkiOiIwWkNLQk1zMG1YOUtVQWJqIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJsaWJyYXJ5L3VidW50dSIsImFjdGlvbnMiOlsicHVzaCIsIioiLCJwdWxsIl19XX0.TatY48UpXGFeHNyKRn047kmke_tyi2qGCGKPkdeNSMm8Tihfd8jZicgysTXnT6m-OhWVpSiMkiiy83wDgHGlLHYWu9_0nGv42tA6xz7LOzp1GSr-7jlqc_qHfZ0seUfDhlYTYA5FWxAIaXYk026PYT5-ZhH_s0IbIndnfhHf1Gcjp79cr0dsyoWAe1wpo9jFjXyNceejtI9Z-XT0fWbu71wTdgQ_0vA9ANPh3qfbi_EEFQdYvIMXcLdZINV6yxtCNudY7mKq_nyXRnUgNaMcbMRXVw-Eq3suiwyxyuAd0KiagvsMSMh5dQ7tEJSkBi0HePT__nIbobeUuepT5nQr2WDX8vqev26b_30kW6c6UebBXUw3_k0EAxpg30TGWbcBgVDR2dMlYQsNmA_3Nnb6rLTNpKl-nR8XHlw8v4N5F0RaYDPYpeMnu_m8mS8KF8PbK9JrHyZs5K-1YsLTYflW5Z3aOFRVwjOfe2nDvMgSfNYcukvncp8KGrlCeb5A3obPZF02o1mbSrxwieSpvUomNVatFJZ9FeP9-pIel3TrI2WSoj0tsQkCyz_E5563PWzpW6A7Mf9GUcKeww_ocBUU1wZScKYAudayuwC53PBbQ70ojGPLzrXJucbZT7O1FBJ-fEQj5-Gg8rzurj2PXJDiKnjVwPzwGpr632T3y1Iqu-o" http://10.6.6.197/v2/library/ubuntu/manifests/latest
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 09 Jun 2020 06:20:41 GMT
Content-Type: application/vnd.docker.distribution.manifest.v2+json
Content-Length: 1152
Connection: keep-alive
Docker-Content-Digest: sha256:5747316366b8cc9e3021cd7286f42b2d6d81e3d743e2ab571f55bcd5df788cc8
Docker-Distribution-Api-Version: registry/2.0
Etag: "sha256:5747316366b8cc9e3021cd7286f42b2d6d81e3d743e2ab571f55bcd5df788cc8"
Set-Cookie: beegosessionID=bb4472acd714353d1677ed24896bdb97; Path=/; HttpOnly

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 3408,
      "digest": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 28556247,
         "digest": "sha256:d51af753c3d3a984351448ec0f85ddafc580680fd6dfce9f4b09fdb367ee1e3e"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 32304,
         "digest": "sha256:fc878cd0a91c7bece56f668b2c79a19d94dd5471dae41fe5a7e14b4ae65251f6"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 847,
         "digest": "sha256:6154df8ff9882934dc5bf265b8b85a3aeadba06387447ffa440f7af7f32b0e1d"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 163,
         "digest": "sha256:fee5db0ff82f7aa5ace63497df4802bbadf8f2779ed3e1858605b791dc449425"
      }
   ]
}

# 不显示头信息后,输出sha256,确实等于 10.6.6.197:5000/library/ubuntu 的digest
curl  -X GET -H "Accept: application/vnd.docker.distribution.manifest.v2+json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjVDQkU6Mk5SVDpVSkhDOjVHQUY6S1lVVzpFNVlLOkgzTDY6UUJMMjo3UlpDOllSWk06U1ZIVDpXSlkzIn0.eyJpc3MiOiJoYXJib3ItdG9rZW4taXNzdWVyIiwic3ViIjoiYWRtaW4iLCJhdWQiOiJoYXJib3ItcmVnaXN0cnkiLCJleHAiOjE1OTE2ODU0MTYsIm5iZiI6MTU5MTY4MzYxNiwiaWF0IjoxNTkxNjgzNjE2LCJqdGkiOiIwWkNLQk1zMG1YOUtVQWJqIiwiYWNjZXNzIjpbeyJ0eXBlIjoicmVwb3NpdG9yeSIsIm5hbWUiOiJsaWJyYXJ5L3VidW50dSIsImFjdGlvbnMiOlsicHVzaCIsIioiLCJwdWxsIl19XX0.TatY48UpXGFeHNyKRn047kmke_tyi2qGCGKPkdeNSMm8Tihfd8jZicgysTXnT6m-OhWVpSiMkiiy83wDgHGlLHYWu9_0nGv42tA6xz7LOzp1GSr-7jlqc_qHfZ0seUfDhlYTYA5FWxAIaXYk026PYT5-ZhH_s0IbIndnfhHf1Gcjp79cr0dsyoWAe1wpo9jFjXyNceejtI9Z-XT0fWbu71wTdgQ_0vA9ANPh3qfbi_EEFQdYvIMXcLdZINV6yxtCNudY7mKq_nyXRnUgNaMcbMRXVw-Eq3suiwyxyuAd0KiagvsMSMh5dQ7tEJSkBi0HePT__nIbobeUuepT5nQr2WDX8vqev26b_30kW6c6UebBXUw3_k0EAxpg30TGWbcBgVDR2dMlYQsNmA_3Nnb6rLTNpKl-nR8XHlw8v4N5F0RaYDPYpeMnu_m8mS8KF8PbK9JrHyZs5K-1YsLTYflW5Z3aOFRVwjOfe2nDvMgSfNYcukvncp8KGrlCeb5A3obPZF02o1mbSrxwieSpvUomNVatFJZ9FeP9-pIel3TrI2WSoj0tsQkCyz_E5563PWzpW6A7Mf9GUcKeww_ocBUU1wZScKYAudayuwC53PBbQ70ojGPLzrXJucbZT7O1FBJ-fEQj5-Gg8rzurj2PXJDiKnjVwPzwGpr632T3y1Iqu-o" http://10.6.6.197/v2/library/ubuntu/manifests/latest|sha256sum 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1152  100  1152    0     0  30644      0 --:--:-- --:--:-- --:--:-- 31135
5747316366b8cc9e3021cd7286f42b2d6d81e3d743e2ab571f55bcd5df788cc8



可以看到,manifest中包含了 imageId,而上面 layers中的每一个digest,就是每一层的digest

这也从侧面印证了一开始图中的流程:先从仓库中拿manifest,取出其中的imageId来本地查找,没有的话,去要image config,然后从中找到自己不存在的layer

查看image-config

cat imagedb/content/sha256/1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f",
      "sha256:9e53fd4895597d04f8871a68caea4c686011e1fbd0be32e57e89ada2ea5c24c4",
      "sha256:2a19bd70fcd4ce7fd73b37b1b2c710f8065817a9db821ff839fe0b4b4560e643",
      "sha256:8891751e0a1733c5c214d17ad2b0040deccbdea0acebb963679735964d516ac2"
    ]
  }
  #这里不等于digest,这里是tar.gz 解压为tar 后生成的id

而 我们Pull镜像的时候,下载的每一层layer 都有自己的digest,在 tar.gz压缩格式下,这两个值不相等

在pull镜像的时候显示的是各个layer的digest信息,在image config存的是diffid。要区分这两个,还要先回答为什么manifest的layer的表达和image config的layer的表达中不是一个东西

当我们去registry上拉layer的时候,拉什么格式的呢,是根据请求中的media type决定的,因为layer存在本地的时候未压缩的,或者说是解压过的。

为了在网络上传输的更加快呢,所有media type一般会指定压缩格式的,比如gzip的,具体有哪些格式

结合我最开始说的(manifest对应registry服务端的配置,image config针对本地存储端的),其实也就不难理解了。

当docker发现本地不存在某个layer的时候,**就会通过manifest里面的digest + mediaType(一般是"application/vnd.docker.image.rootfs.diff.tar.gzip")去registry拉对应的layer。

然后在image id存的对应的diff id就是上面拿到的tar.gz包解压为tar包的id

也就是说,拉取时候的layer显示digest其实是 tar.gz格式的sha256,而本地的diff-id其实是tar格式的sha256

同一个镜像,传到不同的registry的不同repo下,生成的digest都不同。

/var/lib/docker/image/overlay2/distribution 保存了layer digest和diff id的互相对应关系:

# tree -L 2 image/overlay2/distribution/
image/overlay2/distribution/
├── diffid-by-digest  # 通过digest得到diffid
│   └── sha256
└── v2metadata-by-diffid  # 通过diffid得到digest
    └── sha256

layerdb

根据这个名字我们就知道这个目录下肯定是和layer有关系,但是又不是直接存储layer具体数据的地方

ll image/overlay2/layerdb/sha256/7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f/
总用量 20
-rw-r--r-- 1 root root  64 12月  6 23:31 cache-id  # 真正对应的layer数据那个目录
-rw-r--r-- 1 root root  71 12月  6 23:31 diff  # 该层的diffid
-rw-r--r-- 1 root root  71 12月  6 23:31 parent  # 上一层的chainid
-rw-r--r-- 1 root root   1 12月  6 23:31 size  # 该层的大小
-rw-r--r-- 1 root root 324 12月  6 23:31 tar-split.json.gz # tar-split.json.gz,layer压缩包的split文件,通过这个文件可以还原layer的tar包,https://github.com/vbatts/tar-split

主要看sha256这个目录(mount目录在后面镜像存储的时候具体说)。 对应着刚才的diff id,我们会发现这个里面其实只有diff id的最底层,也就是7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f这个。那这个里面到底是啥意思呢,这个里面是chainid,这个是因为chainid的一层是依赖上一层的,这就导致最后算出来的rootfs是统一的。

一个完整的实验结果

以下使用一个空的docker环境,只有一个ubuntu镜像来计算,避免出现干扰

~> docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
d51af753c3d3: Pull complete
fc878cd0a91c: Pull complete
6154df8ff988: Pull complete
fee5db0ff82f: Pull complete
Digest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

 ~> docker images --digests --no-trunc
REPOSITORY          TAG                 DIGEST                                                                    IMAGE ID                                                                  CREATED             SIZE
ubuntu              latest              sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7   sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01   6 weeks ago         73.9MB


docker-desktop:/var/lib/docker/image/overlay2# cat repositories.json
{
  "Repositories": {
    "ubuntu": {
      "ubuntu:latest": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01",
      "ubuntu@sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01"
    }
  }
}

cat imagedb/content/sha256/1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f",
      "sha256:9e53fd4895597d04f8871a68caea4c686011e1fbd0be32e57e89ada2ea5c24c4",
      "sha256:2a19bd70fcd4ce7fd73b37b1b2c710f8065817a9db821ff839fe0b4b4560e643",
      "sha256:8891751e0a1733c5c214d17ad2b0040deccbdea0acebb963679735964d516ac2"
    ]
  }

#通过distribution中,diffid --> 每一layer的digest
cat distribution/v2metadata-by-diffid/sha256/7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f  #第一层,正好与pull的时候信息对上,其他的不举例子了
[{"Digest":"sha256:d51af753c3d3a984351448ec0f85ddafc580680fd6dfce9f4b09fdb367ee1e3e","SourceRepository":"docker.io/library/ubuntu","HMAC":""}]
#反过来也可以
cat distribution/diffid-by-digest/sha256/d51af753c3d3a984351448ec0f85ddafc580680fd6dfce9f4b09fdb367ee1e3e
sha256:7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f


cd layerdb/sha256/
ll -al 
#发现只有最底层7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f,但是这个镜像一共四层
drwx------    2 root     root          4096 Jun  9 02:42 279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3
drwx------    2 root     root          4096 Jun  9 02:42 39865913f677c50ea236b68d81560d8fefe491661ce6e668fd331b4b680b1d47
drwx------    2 root     root          4096 Jun  9 02:42 7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f
drwx------    2 root     root          4096 Jun  9 02:42 cac81188485e011e56459f1d9fc9936625a1b62cacdb4fcd3526e5f32e280387

#使用第一层和第二层做sha256,可以算出下一个值
echo -n "sha256:7789f1a3d4e9258fbe5469a8d657deb6aba168d86967063e9b80ac3e1154333f sha256:9e53fd4895597d04f8871a68caea4c686011e1fbd0be32e57e89ada2ea5c24c4" |sha256sum
cac81188485e011e56459f1d9fc9936625a1b62cacdb4fcd3526e5f32e280387  -

#使用上面的结果,跟第三层2a19bd70fcd4ce7fd73b37b1b2c710f8065817a9db821ff839fe0b4b4560e643 做sha256
echo -n "sha256:cac81188485e011e56459f1d9fc9936625a1b62cacdb4fcd3526e5f32e280387 sha256:2a19bd70fcd4ce7fd73b37b1b2c710f8065817a9db821ff839fe0b4b4560e643" |sha256sum
39865913f677c50ea236b68d81560d8fefe491661ce6e668fd331b4b680b1d47
#最后用上面结果跟第四层做 sha256
echo -n "sha256:39865913f677c50ea236b68d81560d8fefe491661ce6e668fd331b4b680b1d47 sha256:8891751e0a1733c5c214d17ad2b0040deccbdea0acebb963679735964d516ac2" |sha256sum
279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3

#查看最后一个chainId下的内容,parent就是上一个chainId
cat 279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3/parent
sha256:39865913f677c50ea236b68d81560d8fefe491661ce6e668fd331b4b680b1d47

#查看diff信息,发现其对应的是 第四层的 diffId 8891751e0a1733c5c214d17ad2b0040deccbdea0acebb963679735964d516ac2,也就是说这里可以看到chainId和diffId的对应关系
cat 279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3/diff
sha256:8891751e0a1733c5c214d17ad2b0040deccbdea0acebb963679735964d516ac2

# 将ubuntu镜像推送到本地仓库,这里推送的每一层显示的其实是 diff-id 而不是digest
 ~> docker push 10.6.6.197:5000/library/ubuntu:latest
The push refers to repository [10.6.6.197:5000/library/ubuntu]
8891751e0a17: Pushed
2a19bd70fcd4: Pushed
9e53fd489559: Pushed
7789f1a3d4e9: Pushed
latest: digest: sha256:5747316366b8cc9e3021cd7286f42b2d6d81e3d743e2ab571f55bcd5df788cc8 size: 1152
# 最后仓库为这个镜像生成了新的Digest,有别于我们从docker.io上下载的ubuntu 的digest 747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7

# 查看现在的仓库信息
cat image/overlay2/repositories.json | jq .
{
  "Repositories": {
    "10.6.6.197:5000/library/ubuntu": {
      "10.6.6.197:5000/library/ubuntu:latest": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01",
      "10.6.6.197:5000/library/ubuntu@sha256:5747316366b8cc9e3021cd7286f42b2d6d81e3d743e2ab571f55bcd5df788cc8": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01"
    },
    "ubuntu": {
      "ubuntu:latest": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01",
      "ubuntu@sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7": "sha256:1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01"
    }
  }
}

# 对 image的 config文件 执行sha256 可以得到imageid
sha256sum image/overlay2/imagedb/content/sha256/
1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01
# 以下是结果
1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01  image/overlay2/imagedb/content/sha256/1d622ef86b138c7e96d4f797bf5e4baca3249f030c575b9337638594f2b63f01


overlay2目录

上面我们通过chainid知道了真的layer层的数据目录地址,/var/lib/docker/overlay2/就是layer数据存放的目录,比如每个chainid里面cache-id都回应这个目录下面的一个目录。

#以最后一层的cache_id为例,去找镜像数据真正的存储位置
cat 279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3/cache-id
1f0fcd529216a788411c18b157c8ccf54ac64e2f0aa98097d50891e3c4719c9d


cd /var/lib/docker/overlay2/
cd 1f0fcd529216a788411c18b157c8ccf54ac64e2f0aa98097d50891e3c4719c9d

ls -al
total 24
drwx------    4 root     root          4096 Jun  9 02:42 .
drwx------    7 root     root          4096 Jun  9 02:42 ..
drwxr-xr-x    3 root     root          4096 Jun  9 02:42 diff
-rw-r--r--    1 root     root            26 Jun  9 02:42 link
-rw-r--r--    1 root     root            86 Jun  9 02:42 lower
drwx------    2 root     root          4096 Jun  9 02:42 work

diff中存储的就是这一层额外修改的内容

容器在磁盘上的存储:

容器的文件系统分为三层:

  • - r/o层:也就是镜像层
  • - init层:启动容器时的参数
  • - r/w层:可读写层

前面我们知道镜像存储在/var/lib/docker/overlay2下面,这里不仅包含了只读(r/o)层,init层和r/w层都在这个里面。 现在我们启动一个容器看看:

# docker run -ti -d docker-search.4pd.io/ubuntu:18.04 top
88a8630bf2e3292c7abdeede99d1f639f7a155ea1f30e93b691960faa9c0402b

运行一个容器会返回一个容器id. 通过这个id,我们能在*/var/lib/docker/image/overlay2/layerdb/mounts*目录下找到该容器的信息:

# ll image/overlay2/layerdb/mounts/88a8630bf2e3292c7abdeede99d1f639f7a155ea1f30e93b691960faa9c0402b/
总用量 12
-rw-r--r-- 1 root root 69 12月  7 10:07 init-id  # init层的id
-rw-r--r-- 1 root root 64 12月  7 10:07 mount-id # r/w层的id
-rw-r--r-- 1 root root 71 12月  7 10:07 parent   # 基于哪个layer

测试

~> docker run -ti -d 10.6.6.197:5000/library/ubuntu:latest top
b36c99f3dd781d3e7ea441809595ada8df5f9b584c9c92e16282c7089f43ee5f

/var/lib/docker/image/overlay2/layerdb/mounts/b36c99f3dd781d3e7ea441809595ada8df5f9b584c9c92e16282c7089f43ee5f# ls
init-id   mount-id  parent

cat init-id
295937c62201fab6ea3826b5d128f4aab64c5068fe48b32d1e1de1b592aec9fb-init

cat mount-id
295937c62201fab6ea3826b5d128f4aab64c5068fe48b32d1e1de1b592aec9fb

cat parent
sha256:279e836b58d9996b5715e82a97b024563f2b175e86a53176846684f0717661c3
#可以看到,rw层和init名字就相差 '-init',并且parent就是 这个镜像的 diff-id chain中的最后一个节点的id
# 同时在/var/lib/docker/overlay2 可以找到这两个目录,并且写入的文件,都会在这个diff之下保存

Docker中的overlayFs

overlayFS的两个特征: - 上下合并 - 同名遮盖

1、 挂载一个overlayFS

我们来mount一个overlayfs看看: 首先我们先建立几个目录和文件,如下:

# tree -L 2
.
├── lower1
│   ├── common.txt
│   └── ower1.sh
├── lower2
│   ├── common.txt
│   └── ower2.sh
├── lower3
│   ├── common.txt
│   └── ower3.sh
├── merged
├── upper
│   ├── ower2.sh
│   └── up.txt
└── work
    └── work

common.txt分别存放不同的内容,比如lower1下面common.txt内容是lower1,lower2下面common.txt内容是lower2,lower3下面common.txt内容是lower3

upper目录有个和lower2/ower2.sh同名的目录,我们通过下面的命令进行挂载

mount -t overlay overlay -o lowerdir=lower1:lower2:lower3,upperdir=upper,workdir=work merged
  • lowerdir: 代表lower层,可以有多个,优先级依次降低,也就是说lower1 > lower2 > lower3
  • upperdir: 代表upper层,会覆盖lower层
  • workdir: 工作目录,用于存放临时文件
  • merged: 挂载点 我们看看操作之后的目录:

Docker镜像在OverlayFs下的存储细节_第2张图片

最下层是lower层,也是只读/镜像层

upper是容器的读写层,采用了CoW(写时复制)机制,只有对文件进行修改才会将文件拷贝到upper层,之后所有的修改操作都会对upper层的副本进行修改

upper并列还有workdir层,它的作用是充当一个中间层的作用,每当对upper层里面的副本进行修改时,会先当到workdir,然后再从workdir移动upper

最上层是mergedir,是一个统一图层,从mergedir可以看到lower,upper,workdir中所有数据的整合,整个容器展现出来的就是mergedir层.

[root@edu3 merged]# rm -rf up.txt 
[root@edu3 merged]# cd ..
[root@edu3 testol]# ls
lo1  lo2  lo3  merged  upper  work
[root@edu3 testol]# tree -L 3             
.
├── lo1
│   ├── common.txt
│   └── owner1.sh
├── lo2
│   ├── common.txt
│   └── owner2.sh
├── lo3
│   ├── common.txt
│   └── owner3.sh
├── merged
│   ├── common.txt
│   ├── owner1.sh
│   ├── owner2.sh
│   └── owner3.sh
├── upper
│   └── owner2.sh
└── work
    └── work

7 directories, 11 files
[root@edu3 testol]# rm -rf merged/owner3.sh 
[root@edu3 testol]# tree -L 3               
.
├── lo1
│   ├── common.txt
│   └── owner1.sh
├── lo2
│   ├── common.txt
│   └── owner2.sh
├── lo3
│   ├── common.txt
│   └── owner3.sh
├── merged
│   ├── common.txt
│   ├── owner1.sh
│   └── owner2.sh
├── upper
│   ├── owner2.sh   
│   └── owner3.sh #删除owner3后,upper层会有一个whiteout文件
└── work
    └── work

我们演示几个对挂载后的目录的操作:

  1. 删除的文件是upper的,并且这个文件在lower层不存在(up.txt) 直接删除就行了

  2. 删除的文件来自于lower层,upper层没有对应的文件(ower3.sh)overlayFS通过一种叫whiteout的机制。 这种机制是用于屏蔽底层的同名文件,在upper层创建一个主次设备号(mknod c 0 0)都是0的设备,当在merge层去找的时候,overlayFS会自动过滤掉和whiteout文件自身以及和他同名的lower层的文件,从而达到隐藏的目的

  3. 删除的是upper覆盖lower的文件(ower2.sh) 依然创建一个whiteout文件

  4. 创建一个upper和lower都没有的目录 直接在upper中新增一个

  5. 创建一个在lower层已经存在且在upper层有whiteout文件的同名文件 删了whiteout文件,重新创建一个

  6. 创建一个lower层存在并且upper层已经有对应white文件的目录(也就是之前先删了lower层的某个目录,导致upper层会有一个whiteout文件) 如果这个时候单纯的删除white文件,那么lower层对应目录里面的文件就会显示出来。 overlayFS引入了一种Opaque的属性,通过设置upper层上对应的目录上设置"trusted.overlay.opaque"为y来实现(前提是upper所在的文件系统支持xattr属性),overlayFS在读取上下层存在同名目录的时候,如果upper层的目录被设置了Opaque的属性,他会忽这个目录下层的所有同名目录项,来保证新建的是个空目录

可以查看我们刚才创建的容器的dir路径

对Workdir似乎没啥用?

overlay on /var/lib/docker/overlay2/0e526b624ca46d7bd04a117359c46ae2f40541076b9e3a49d84850e1f5bd41c0/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/YCR3MEEZBBQ3YWXD7GX2FBGN4N:/var/lib/docker/overlay2/l/AGXJVNPBYZS3AB575UKUOAV5ZC:/var/lib/docker/overlay2/l/5WR5R45SS5V44RB36DZ6YXE2EN:/var/lib/docker/overlay2/l/JGCQGNCDL2MTRJXDUBNI25SF3C:/var/lib/docker/overlay2/l/GAP2P6Z3M6BUGLRUEK64V2YG44,upperdir=/var/lib/docker/overlay2/0e526b624ca46d7bd04a117359c46ae2f40541076b9e3a49d84850e1f5bd41c0/diff,workdir=/var/lib/docker/overlay2/0e526b624ca46d7bd04a117359c46ae2f40541076b9e3a49d84850e1f5bd41c0/work)

不妨可以对照一下。 其中lowerdir就是image layer的四层加上init层,upperdir层是r/w层是diff目录也就是挂载点,workdir是挂载之后的工作目录。

可能会困惑YCR3MEEZBBQ3YWXD7GX2FBGN4N这些有是啥,这些是为了避免mount命令太长而导致的错误,所有故意搞短一点,我们看下*/var/lib/docker/overlay2/l/*就能明白了:

docker-desktop:/var/lib/docker/overlay2/l# ls -al
total 32
drwx------    2 root     root          4096 Jun  9 05:47 .
drwx------    9 root     root          4096 Jun  9 05:47 ..
lrwxrwxrwx    1 root     root            72 Jun  9 02:42 5WR5R45SS5V44RB36DZ6YXE2EN -> ../264ffdc07fa1761bd7482c3677790ee181c7299a5b236864043303f40d095e89/diff
lrwxrwxrwx    1 root     root            72 Jun  9 02:42 AGXJVNPBYZS3AB575UKUOAV5ZC -> ../1f0fcd529216a788411c18b157c8ccf54ac64e2f0aa98097d50891e3c4719c9d/diff
lrwxrwxrwx    1 root     root            72 Jun  9 02:42 GAP2P6Z3M6BUGLRUEK64V2YG44 -> ../e1fb63ee3607ee9c3b4d99e707b73a842a44bf2fb90d2d8029851ebef076494e/diff
lrwxrwxrwx    1 root     root            72 Jun  9 02:42 JGCQGNCDL2MTRJXDUBNI25SF3C -> ../45b314b26875cde7da956442ed19d7ec2f80820a1ed99ae42d59f34c23f70b28/diff
lrwxrwxrwx    1 root     root            72 Jun  9 05:47 S4HP5QOR5QOQDDOMU7VUVTEPOX -> ../0e526b624ca46d7bd04a117359c46ae2f40541076b9e3a49d84850e1f5bd41c0/diff
lrwxrwxrwx    1 root     root            77 Jun  9 05:47 YCR3MEEZBBQ3YWXD7GX2FBGN4N -> ../0e526b624ca46d7bd04a117359c46ae2f40541076b9e3a49d84850e1f5bd41c0-init/diff

l也即是link的缩写,这里面就是一坨指向真正层数据的软链。

参考文章:

浅谈docker中镜像和容器在本地的存储

你可能感兴趣的:(Linux,容器)