Docker镜像文件的本地存储
本文描述了Docker镜像文件在本地的分层存储方式
众所周知,docker的image是按层存储的,关于layer的存储方式,做一个说明。 其整体上,layer有这些概念:
1
layerID -> diffID -> chainID -> cacheID
digest(layerID)
就是 pull 镜像时的 hash ID,拉取是 镜像层文件是压缩文件,压缩态,tar.gzdiffID
是 docker inspect 查看到的 镜像层 hash ID,此时 镜像层文件是解压缩的,解压缩态,tarchainID
用来在layerdb目录下保存具体的层信息cacheID
用来在overlay2目录下存储具体的层信息
docker的默认工作目录在 /var/lib/docker
,我们重点关注image
和 overlay2
这两个文件夹,分别存放镜像信息和具体的layer文件。
/var/lib/docker/image/overlay2
1
2
3
4
5
6
root@linuc:/var/lib/docker/image/overlay2# ls -l
total 16
drwx------ 4 root root 4096 Nov 16 14:17 distribution
drwx------ 4 root root 4096 Nov 16 14:03 imagedb
drwx------ 5 root root 4096 Nov 16 14:20 layerdb
-rw------- 1 root root 873 Dec 22 10:13 repositories.json
repositories.json
保存了当前的docker daemon维护的镜像信息
1
2
3
4
5
6
root@linuc:/var/lib/docker/image/overlay2# jq . repositories.json
……
"img": {
"img:latest": "sha256:21adcd7be0556b5576e627f47c753b5113a8d91067aa94008769558462a482be",
"img:tree": "sha256:4c68ba802076e7390e635e53e724179412b77aade7e36f072bf57ad0869101e8"
},
结果显示,当前的docker daemon维护了两个img镜像,tag分别是latest和tree,我们以latest为例,记录此镜像的imageID 21adcd7be0556...
。
distribution 目录
diffid-by-digest
保存了digest(layerID)->diffID
的映射关系 v2metadata-by-diffid
保存了diffid -> (digest,repository)
的映射关系
imagedb 目录
保存具体的镜像元数据信息,进入/var/lib/docker/image/overlay2/imagedb/content/sha256
下。 该目录下的文件都以sha256为名,我们可以看见上面找到的 img:latest-imageID: 21adcd7be0556...
。
1
2
3
4
5
6
root@linuc:/var/lib/docker/image/overlay2/imagedb/content/sha256# ls
21adcd7be0556b5576e627f47c753b5113a8d91067aa94008769558462a482be
4c68ba802076e7390e635e53e724179412b77aade7e36f072bf57ad0869101e8
62753ecb37c4e3c5bf7b6c8d02fe88b543f553e92492fca245cded98b0d364dd
e6a0117ec169eda93dc5ca978c6ac87580e36765a66097a6bfb6639a3bd4038a
f215ff3bfefbdfb038a0a1aaa35bf8f5dcb708a2116fdc1848c812ff9bdada7d
这些文件都是json文件,我们查看这些文件,实质上跟用docker inspect
的效果是一样的。
1
2
3
4
5
6
7
8
9
10
11
12
root@linuc:/var/lib/docker/image/overlay2/imagedb/content/sha256# jq . 21adcd7be0556b5576e627f47c753b5113a8d91067aa94008769558462a482be
……
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:20312b5745843a1d409495e32b3cb4aaf48fc5f02eba6ae34ec2dcee82a5886a",
"sha256:4b96e5616c0fb8259bebd63045a9a61da52fa92d22b6bed78d91a02145a9ba73",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
"sha256:99cef7be727bcfa4c8efe34747f1752b59d3fa39b64cdfbb9d17569799b816b7",
"sha256:a2bd4f4d39eab016e1dedf197a88d659085c41d301bbf859144e583c6642be50"
]
}
在rootfs字段中看到的diff_ids,是每一层的diffID
。对每层文件,docker把起打包成tar包,对tar包计算sha256得到的就是diffID
。
layerdb 目录
layer具体的文件系统存储在/var/lib/docker/overlay2
中,但并不是以上述提到的diffID
直接存储的,而是使用其cacheID
来存储的,layerdb目录描述了从diffID->cacheID
的映射关系。 但是从diffID->cacheID
也不是直接映射的,而是有chainID
参与。
layerdb目录下所有文件夹的名称是chainID。
具体而言,layer.ChainID只用本地,根据layer.DiffID计算,并用于layerdb的目录名称(cacheID)。 chainID唯一标识了一组(像糖葫芦一样的串的底层)diffID的hash值,包含了这一层和它的父层(底层),当然这个糖葫芦可以有一颗山楂,也就是chainID(layer0)==diffID(layer0)
;对于多颗山楂的糖葫芦,
ChainID(layerN) = SHA256hex(ChainID(layerN-1) + " " + DiffID(layerN))
。
再看:
1
2
3
4
5
6
7
"diff_ids": [
"sha256:20312b5745843a1d409495e32b3cb4aaf48fc5f02eba6ae34ec2dcee82a5886a",
"sha256:4b96e5616c0fb8259bebd63045a9a61da52fa92d22b6bed78d91a02145a9ba73",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
"sha256:99cef7be727bcfa4c8efe34747f1752b59d3fa39b64cdfbb9d17569799b816b7",
"sha256:a2bd4f4d39eab016e1dedf197a88d659085c41d301bbf859144e583c6642be50"
]
这里面记录了每一层的diffID,第一个是最底层(其没有父层)。 这种情况下,其diffID=chainID.
1
2
3
4
5
6
7
# 第一层,其diffID就是chainID
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256# ls | grep 20312b5745*
20312b5745843a1d409495e32b3cb4aaf48fc5f02eba6ae34ec2dcee82a5886a
# 第二层,其diffID不是chainID,直接找diffID是找不到的,因为chainID不是直接diffID
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256# ls | grep 4b96e5616c*
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256#
针对高层layer(其有父层),
ChainID(layerN) = SHA256hex(ChainID(layerN-1) + “ “ + DiffID(layerN))。
我们用第一层的diffID=chainID和第二层diffID来计算第二层的chainID
1
2
3
4
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256# echo -n "sha256:20312b5745843a1d409495e32b3cb4aaf48fc5f02eba6ae34ec2dcee82a5886a sha256:4b96e5616c0fb8259bebd63045a9a61da52fa92d22b6bed78d91a02145a9ba73" | sha256sum | awk '{print $1}'
b4533429e3cf7b89e83d45befd513af96d2777564dcdeff3752786a193535a9c
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256# ls | grep b4533429e*
b4533429e3cf7b89e83d45befd513af96d2777564dcdeff3752786a193535a9c
可见我们计算第二层的chainID是正确的。 我们进入任意一层的chainID文件夹中,有如下文件:
1
2
3
4
5
6
7
root@linuc:/var/lib/docker/image/overlay2/layerdb/sha256/b4533429e3cf7b89e83d45befd513af96d2777564dcdeff3752786a193535a9c# tree
.
├── cache-id
├── diff
├── parent
├── size
└── tar-split.json.gz
其中,cache-id保存了这层的文件系统在 /var/lib/docker/overlay2
下具体的位置。
脚本自动化探测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/bin/bash
# 检查是否输入了镜像名称和输出模式
if [ "$#" -ne 2 ]; then
echo "使用方法: $0 <镜像名称> <输出模式>"
echo "输出模式可以是 'class' 或 'layer'"
exit 1
fi
# 获取镜像名称和输出模式
image_name=$1
output_mode=$2
# 执行 docker inspect 命令
output=$(docker inspect $image_name)
# 使用 jq 工具解析 JSON 输出,并获取 RootFS.Layers 数组中的所有值
layers=$(echo $output | jq -r '.[0].RootFS.Layers[]')
# 初始化 chainID 为第一层的 layerID
chainID=$(echo $layers | cut -d' ' -f1)
if [ "$output_mode" = "class" ]; then
# 输出所有的 layerID
echo "layerIDs:"
index=1
for layerID in $layers; do
echo " layer ${index}: $layerID"
index=$((index+1))
done
# 输出所有的 chainID
echo "chainIDs:"
chainID=$(echo $layers | cut -d' ' -f1)
echo -n " layer 1: "
echo $chainID
index=2
for layerID in $(echo $layers | cut -d' ' -f2-); do
chainID=$(echo -n "$chainID $layerID" | sha256sum | awk '{print $1}')
chainID="sha256:$chainID"
echo -n " layer ${index}: "
echo $chainID
index=$((index+1))
done
# 输出所有的 cache-id 文件的内容
echo "cacheIDs:"
chainID=$(echo $layers | cut -d' ' -f1)
cache_id=$(cat /var/lib/docker/image/overlay2/layerdb/sha256/${chainID:7}/cache-id)
echo " layer 1: $cache_id"
index=2
for layerID in $(echo $layers | cut -d' ' -f2-); do
chainID=$(echo -n "$chainID $layerID" | sha256sum | awk '{print $1}')
chainID="sha256:$chainID"
cache_id=$(cat /var/lib/docker/image/overlay2/layerdb/sha256/${chainID:7}/cache-id)
echo " layer ${index}: $cache_id"
index=$((index+1))
done
elif [ "$output_mode" = "layer" ]; then
index=1
for layerID in $layers; do
echo "layer ${index}:"
echo " layerID: $layerID"
if [ "$index" -eq 1 ]; then
chainID=$layerID
else
chainID=$(echo -n "$chainID $layerID" | sha256sum | awk '{print $1}')
chainID="sha256:$chainID"
fi
echo " chainID: $chainID"
cache_id=$(cat /var/lib/docker/image/overlay2/layerdb/sha256/${chainID:7}/cache-id)
echo " cacheID: $cache_id"
index=$((index+1))
done
else
echo "无效的输出模式: $output_mode"
echo "输出模式可以是 'class' 或 'layer'"
exit 1
fi