先说结论,容器镜像分层存储图示
欢迎关注
实验环境准备
当前实验docker版本24.0.7如下,当前docker版本使用overlay2机制存储镜像
Client: Docker Engine - Community
Version: 24.0.7
API version: 1.43
Go version: go1.20.10
Git commit: afdd53b
Built: Thu Oct 26 09:07:41 2023
OS/Arch: linux/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 24.0.7
API version: 1.43 (minimum version 1.12)
Go version: go1.20.10
Git commit: 311b9ff
Built: Thu Oct 26 09:07:41 2023
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.25
GitCommit: d8f198a4ed8892c764191ef7b3b06d8a2eeb5c7f
runc:
Version: 1.1.10
GitCommit: v1.1.10-0-g18a0cb0
docker-init:
Version: 0.19.0
GitCommit: de40ad0
$ sudo docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
1f7ce2fa46ab: Pull complete
424de2a10000: Pull complete
6d9a0131505f: Pull complete
5728e491734b: Pull complete
20d3235e84ad: Pull complete
Digest: sha256:04551bc91cc03314eaab20d23609339aebe2ae694fc2e337d0afad429ec22c5a
Status: Downloaded newer image for httpd:latest
docker.io/library/httpd:latest
# sudo docker image inspect httpd
"Id": "sha256:a6ca7b52a41549f13f7de6aeac4ab6ffebd41585fbf2273f00fd6e82bb721949",
"RepoTags": [
"httpd:latest"
],
"RepoDigests": [
"httpd@sha256:04551bc91cc03314eaab20d23609339aebe2ae694fc2e337d0afad429ec22c5a"
],
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/766833b310bfa8245d270ce831a9e73ab27e5e0f61525d108ef519e4868f9cfb/diff:/var/lib/docker/overlay2/76c5547a9f06e06ba12ae92f54211a9c4397bc6c002e3a81aa4a4d7903f451c2/diff:/var/lib/docker/overlay2/aab89c771d1be31e61bce7d83df529043b1b0cd4283ed1d9275a97617c114743/diff:/var/lib/docker/overlay2/32b5b0acbd61d9bbfb362d26d450e6412bfa6749677acf0694f32930e82432b2/diff",
"MergedDir": "/var/lib/docker/overlay2/88aaff0c6f4e196d1a79386b30b9460a2603824b6ebb9efacdfd1fd11000477f/merged",
"UpperDir": "/var/lib/docker/overlay2/88aaff0c6f4e196d1a79386b30b9460a2603824b6ebb9efacdfd1fd11000477f/diff",
"WorkDir": "/var/lib/docker/overlay2/88aaff0c6f4e196d1a79386b30b9460a2603824b6ebb9efacdfd1fd11000477f/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:92770f546e065c4942829b1f0d7d1f02c2eb1e6acf0d1bc08ef0bf6be4972839",
"sha256:fad7e2250d8fefbd8524054837403921b468448b6bae1ee439bb793e254b6725",
"sha256:27496babc700a08c0a14777dd4bb818273e8cc9f77d526fcb4554043cff291d9",
"sha256:28f18e7dc61c56a068f00ec92dde62cf9de21cbce0eb9a8395c6617e2fef9463",
"sha256:b9bf93af811f6900f445158ab648fd4868b69b770908142b897772e6bb8c2a2f"
]
}
overlay2文件系统
overlayfs通过三个目录来实现:lower目录、upper目录、以及work目录。三种目录合并出来的目录称为merged目录
- lower目录:可以是多个,是处于最底层的目录,作为只读层
- upper目录:只有一个,作为读写层
- work目录:为工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见
- merged目录:为最后联合挂载完成给用户呈现的统一视图,也就是说merged目录里面本身并没有任何实体文件,给我们展示的只是参与联合挂载的目录里面文件而已,真正的文件还是在lower和upper中。所以,在merged目录下编辑文件,或者直接编辑lower或upper目录里面的文件都会影响到merged里面的视图展示。
docker image inspect httpd
的结果中LowerDir
的值是实际存储文件系统数据的目录
MergedDir
,UpperDir
,WorkDir
也都是放在/var/lib/docker/overlay2/
目录下
上面
inspect
命令结果中"Id": "sha256:a6ca7b52a41549f13f7de6aeac4ab6ffebd41585fbf2273f00fd6e82bb721949",
,这一部分其实就是repository元数据,用于找到镜像image元数据/var/lib/docker/image/overlay2/imagedb/content/sha256(Id值)/
上面
inspect
命令结果中RootFS
的Layers
是layer元数据
,其中的每个sha256
的值就是diff_id
docker镜像分层存储原理
docker镜像的元数据分为repository
,image
,layer
三部分
由于docker镜像分层存储,所以repository
和image
没有对应的物理镜像文件,而每个layer
元数据都有具体的物理镜像
层文件与之对应,
并且layer
元数据和镜像文件
分开存储,二者基于内容寻址
(即基于文件内容索引镜像和镜像层)存储机制进行关联
repository元数据
repository
是某个镜像所有迭代版本的集合,它在本地的持久化信息就存储在/var/lib/docker/image/overlay2/repositories.json
中,其中记录了所有repository
的名字以及每个repository
中所有版本镜像的名字。
这里的sh256的值和
docker pull
时的sha256值一样
root@ubuntu20:/var/lib/docker/image/overlay2# cat repositories.json
{"Repositories":{
"httpd":{
"httpd:latest":"sha256:a6ca7b52a41549f13f7de6aeac4ab6ffebd41585fbf2273f00fd6e82bb721949",
"httpd@sha256:04551bc91cc03314eaab20d23609339aebe2ae694fc2e337d0afad429ec22c5a":"sha256:a6ca7b52a41549f13f7de6aeac4ab6ffebd41585fbf2273f00fd6e82bb721949"
}
}}
image元数据
image
元数据的本地文件位于/var/lib/docker/image/overlay2/imagedb/content/sha256/
,包括以下信息:镜像架构、操作系统、镜像配置、Docker版本和镜像层信息rootfs。
其中rootfs包含该镜像所有镜像层的id,即diff_id
。其中的diff_ids
从上到下依次为nginx
镜像底层到顶层的diff_id
。
镜像层的id为
diff_id
# cat /var/lib/docker/image/overlay2/imagedb/content/sha256/a6ca7b52a41549f13f7de6aeac4ab6ffebd41585fbf2273f00fd6e82bb721949
.....
"rootfs":{
"type":"layers","diff_ids":[
"sha256:92770f546e065c4942829b1f0d7d1f02c2eb1e6acf0d1bc08ef0bf6be4972839",
"sha256:fad7e2250d8fefbd8524054837403921b468448b6bae1ee439bb793e254b6725",
"sha256:27496babc700a08c0a14777dd4bb818273e8cc9f77d526fcb4554043cff291d9",
"sha256:28f18e7dc61c56a068f00ec92dde62cf9de21cbce0eb9a8395c6617e2fef9463",
"sha256:b9bf93af811f6900f445158ab648fd4868b69b770908142b897772e6bb8c2a2f"
]
}
layer元数据
用户拉取了某个镜像层后,Docker会基于该镜像层的文件和image
元数据生成layer
元数据,
主要包括该镜像层的diff_id(diff)
,如果该镜像层没有父镜像层,那么该层的diff_id
就是chain_id
父镜像层的chain_id(parent)
、
该镜像层的大小(size
)、
镜像层文件的索引(cache-id
)等,
这些元数据存储在 /var/lib/docker/image/overlay2/layerdb/sha256/
路径下
root@ubuntu20:/var/lib/docker/image/overlay2/layerdb/sha256# tree -L 2
.
├── 16d494768121f45ba050ad50a669400818478131cb930bb0c8b5d2f908057142
│ ├── cache-id
│ ├── diff
│ ├── parent
│ ├── size
│ └── tar-split.json.gz
├── 29b181ca372e770053cc03410789236d7a96171f1f77110741d0f3f068a29c67
│ ├── cache-id
│ ├── diff
│ ├── parent
│ ├── size
│ └── tar-split.json.gz
├── 92770f546e065c4942829b1f0d7d1f02c2eb1e6acf0d1bc08ef0bf6be4972839
│ ├── cache-id
│ ├── diff
│ ├── size
│ └── tar-split.json.gz
├── cdfabdae5172d05eb5141bb77aaa300a195570b0befd780b6e2fc7bd20ea2cd5
│ ├── cache-id
│ ├── diff
│ ├── parent
│ ├── size
│ └── tar-split.json.gz
└── e8254408730a345e2ccf863e19c850e7ee78b59dfd9e4aa265b0107dd4301be5
├── cache-id
├── diff
├── parent
├── size
└── tar-split.json.gz
cache-id
是Docker随机生成的UUID,用于标识和索引具体的镜像文件;
chainID
是image
元数据与layer
元数据关联的索引ID,根据当前层的diffID
和父镜像层的chainID
得来,具体计算方法如下:
- 如果该镜像层是最底层(没有父镜像层),该层的 diff_id 便是 chain_id。
- 该镜像层的 chain_id 计算公式为
chainID=sha256(父层chain_id+" "+本层diff_id)
,也就是根据父镜像层的 chain_id 加上一个空格和当前层的 diff_id,再计算 SHA256 校验码。
镜像文件存储目录
镜像层元数据与镜像层文件分开存储,并通过cache-id(overlay2目录下的文件夹的名字)
进行关联。具体的镜像层文件存储在/var/lib/docker/overlay2
文件夹下
root@ubuntu20:/var/lib/docker/overlay2# tree -L 2
.
├── 32b5b0acbd61d9bbfb362d26d450e6412bfa6749677acf0694f32930e82432b2
│ ├── committed
│ ├── diff
│ └── link
├── 766833b310bfa8245d270ce831a9e73ab27e5e0f61525d108ef519e4868f9cfb
│ ├── committed
│ ├── diff
│ ├── link
│ ├── lower
│ └── work
├── 76c5547a9f06e06ba12ae92f54211a9c4397bc6c002e3a81aa4a4d7903f451c2
│ ├── committed
│ ├── diff
│ ├── link
│ ├── lower
│ └── work
├── 88aaff0c6f4e196d1a79386b30b9460a2603824b6ebb9efacdfd1fd11000477f
│ ├── diff
│ ├── link
│ ├── lower
│ └── work
├── aab89c771d1be31e61bce7d83df529043b1b0cd4283ed1d9275a97617c114743
│ ├── committed
│ ├── diff
│ ├── link
│ ├── lower
│ └── work
└── l
├── J6BYBF6ADSYX4HKH65NQSXLNAD -> ../76c5547a9f06e06ba12ae92f54211a9c4397bc6c002e3a81aa4a4d7903f451c2/diff
├── L2DROY4Y7V2ZGUIOFOLSARZO3T -> ../aab89c771d1be31e61bce7d83df529043b1b0cd4283ed1d9275a97617c114743/diff
├── U2HWCDVI7VIS6QKAUANBZSIC4L -> ../32b5b0acbd61d9bbfb362d26d450e6412bfa6749677acf0694f32930e82432b2/diff
├── V2RW2JJO2BYNGIFOMQJ74TF5C3 -> ../766833b310bfa8245d270ce831a9e73ab27e5e0f61525d108ef519e4868f9cfb/diff
└── WUF77DAMALFDYBOPZYWKS7WOIG -> ../88aaff0c6f4e196d1a79386b30b9460a2603824b6ebb9efacdfd1fd11000477f/diff
子目录l
记录了很多长26位的软连接,指向本地缓存目录下的diff
目录。(容器运行时会用到,具体参见文章开头的链接)
其他7个子目录是镜像层的目录,代表Nginx镜像共有7个镜像层,每个镜像层目录都由一串UUID命名,这个UUID即layer
元数据部分介绍的cache-id
,用于将镜像元数据和镜像文件关联起来。
其中diff
目录保存了镜像层的实际内容;
link
保存了当前镜像层的短ID,与 l
目录互为引用;
lower
目录保存了当前镜像层的所有父镜像层的短ID集合,用于挂载时直接读取;
work
目录为overlay2驱动的写时复制目录。
镜像索引关系
repositories.json
文件存储了repository
元数据,其中包括imageID
;
由imageID
可以索引到image
元数据,其中包括该镜像每层的 ID
,即 diff_id
;
根据当前层diff_id
和父镜像层的chainID
可以计算出当前层的chainID
,就可以索引到当前层的layer
元数据;
当前层的layer
元数据文件夹下包括cacheID
,从而索引到存储在/var/lib/docker/overlay2
路径下的镜像文件存储数据。
/var/lib/docker/ /overlay2/ # 存放实际的镜像或容器文件 /image/overlay2/ distribution/ imagedb/ # image元数据 content/sha256/ # image元数据 metadata/ layerdb/ # layer元数据 mounts/ # 运行的容器的挂载元数据 sha256/ # layer元数据 repositories.json # repository元数据
容器启动
⼀个容器完整的层应由三个部分组成
- 镜像层:也称为rootfs,提供容器启动的⽂件系统。镜像层属于
roLayer
- init层:⽤于修改容器中⼀些⽂件如/etc/hostname,/etc/hosts,/etc/resolv.conf等。init层属于
mountedLayer
- 容器层:使⽤联合挂载统⼀给⽤⼾提供的可读写⽬录。容器层属于
mountedLayer
启动容器时会在/var/lib/docker/image/overlay2/layerdb/mounts/
下创建container_id文件夹
,其中包括
mount-id
:存储在/var/lib/docker/overlay2/(镜像文件存储目录)
的⽬录名称。(容器层,联合挂载⽽来,mount到docker/overlay2/⽬录下)init-id
:initID是在mountID后加了⼀个-init,同时initID就是存储 在/var/lib/docker/overlay2/的⽬录名称。parent
:容器所基于的镜像的最上层的chain_id。
多架构下的镜像信息
可以使用 docker manifest inspect $image
命令来查看,manifest为docker的体验特性,在Linux系统下开启
最好的方式为开启docker daemon的特性,修改 /etc/docker/daemon.json
文件
{
"experimental": true
}
配置镜像加速时配置阿里镜像地址也是通过这个文件来做的