前面讲解了关于docker镜像存储之overlayfs,对docker的镜像、容器存储有了基本的了解。但不同的公司所用容器运行时的侧重点不同,一部分在使用containerd,一部分在使用cri-o/podman(podman和cri-o),cri-o/podman镜像存储使用的是同一个架构。我决定对这两种运行时的镜像存储做一个分析。本期先讲解cri-o/podman,当然他们的存储驱动依然使用overlayfs
什么是podman
正如podman官网所说,Podman是一个无守护进程的容器引擎,用于在Linux系统上运行和管理OCI兼容的容器和镜像。它可以作为root和非特权用户运行容器。最好的部分是命令行与Docker兼容,也可以使用Dockerfiles构建镜像。
源码链接和安装链接
镜像存储位置
当你运行 podman images 时,podman 会查找系统中已经拉取的镜像。podman 和 docker 的镜像存储在不同的位置。因此, podman images 不会列出之前 docker 拉取的镜像。podman 的镜像存储在 root 用户的 /var/lib/containers 中,非特权用户的 ~/.local/share/containers 中。存储位置与 docker ( /var/lib/docker ) 不同,因为它的存储结构是基于 OCI 标准的。docker 和 podman 的镜像是兼容的,但它们在系统中存储的方式不同。
Podman使用containers/storage作为存储后端,使用containers/image来操作镜像。
containers/storage的核心对象是store,它封装了存储结构,并存储着系统中的所有镜像和容器层(读写层copy-on-write)以及其他相关的元数据。image对象和layer对象分别封装了与j镜像和容器层相关的所有信息。
storage(podman 存储结构)
接下来在该部分讨论podman overlay驱动的存储结构。简单看一下/var/lib/containers/storage的示例结构。
为了易于理解,拉取的centos:latest镜像,该镜像只有一层,且环境未启动任何容器
root@test:/var/lib/containers/storage# tree -L 4
.
├── libpod
│ ├── bolt_state.db
│ └── defaultCNINetExists
├── mounts
├── overlay
│ ├── 2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859
│ │ ├── diff
│ │ │ ├── bin -> usr/bin
│ │ │ ├── dev
│ │ │ ├── etc
│ │ │ ├── home
│ │ │ ├── lib -> usr/lib
│ │ │ ├── lib64 -> usr/lib64
│ │ │ ├── lost+found
│ │ │ ├── media
│ │ │ ├── mnt
│ │ │ ├── opt
│ │ │ ├── proc
│ │ │ ├── root
│ │ │ ├── run
│ │ │ ├── sbin -> usr/sbin
│ │ │ ├── srv
│ │ │ ├── sys
│ │ │ ├── tmp
│ │ │ ├── usr
│ │ │ └── var
│ │ ├── empty
│ │ ├── link
│ │ ├── merged
│ │ └── work
│ └── l
│ └── 57MIHXCXZI4TMYKQRHKJAAIU2Q -> ../2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859/diff
├── overlay-containers
│ ├── containers.json
│ └── containers.lock
├── overlay-images
│ ├── 300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55
│ │ ├── =bWFuaWZlc3Qtc2hhMjU2OjgzMDFkMTAwMDIwZmZhZWRjOTNmNTdkOGM4YmIwZThlODgwMDY4NjJiY2I0OGViYzVkZmJlMWQxY2I4MzA2MGM=
│ │ ├── =bWFuaWZlc3Qtc2hhMjU2OmRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=
│ │ ├── =c2hhMjU2OjMwMGUzMTVhZGIyZjk2YWZlNWYwYjI3ODBiODdmMjhhZTk1MjMxZmUzYmRkMWUxNmI5YmE2MDYzMDc3MjhmNTU=
│ │ ├── =c2lnbmF0dXJlLWRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=
│ │ └── manifest
│ ├── images.json
│ └── images.lock
├── overlay-layers
│ ├── layers.json
│ └── layers.lock
├── storage.lock
├── tmp
└── userns.lock
讲讨论上面目录树上比较重要的几个目录。
**overlay:**镜像解压后的内容就存储在该目录中。对于每一层,其分解的rootfs存储在overlay/digest/diff中,其中的digest是该层未压缩内容的sha256摘要。对于每一层,其随机id存储在overlay/digest/link文件内。overlay/l 文件夹中包含指向 overlay//diff 目录中已解压的各个层的符号链接。符号链接的名称与存储在相应的 overlay//link 文件中的层的随机 ID 相同。
overlay/l 文件夹中的每个符号链接代表一个已解压的层,并且其名称与该层的随机 ID 相匹配。这些符号链接提供了对相应层的访问路径,而不需要访问原始的 overlay//diff 目录。
root@test:/var/lib/containers/storage# cd overlay
root@test:/var/lib/containers/storage/overlay# ls
2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859 l
root@test:/var/lib/containers/storage/overlay# ls ./l/
57MIHXCXZI4TMYKQRHKJAAIU2Q
root@test:/var/lib/containers/storage/overlay# ls -alh ./l/57MIHXCXZI4TMYKQRHKJAAIU2Q
lrwxrwxrwx 1 root root 72 Apr 9 09:05 ./l/57MIHXCXZI4TMYKQRHKJAAIU2Q -> ../2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859/diff
root@test:/var/lib/containers/storage/overlay# cat 2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859/link
overlay/digest/lower文件存存储了父层的随机 ID(以及父层的父层的随机 ID,包含该层的所有父层的随机id),并以冒号(:)分隔。
root@test:/var/lib/containers/storage/overlay/8fbe574b9abff4af73b9c2d1967fb4a5beec7f5b1ad67a266d2991979ab3c6b4# cat lower
l/2MKTBNDAEOVGZUHR5EVY7VO7ZM:l/JLCPV7U4QQOCICTU2OJWVX3ATP:l/XCK37LZAGQXCOSRJTK7WVMEBDQ:l/X4XV2WKDKGFEIKBUTA74AS2DCX:l/5MMB4TCVPUW2KPCTOPF4OS4KTT:l/OYNEDX46JG7AV4HGKT3NRVWEHY:l/O3
ZCLMX45E4DF35JVCEY7J5OYE:l/3IQ75FBG46YFQE5FWHQKWDDUGN:l/BEVMCWEIHRRJBJNM3ASJHVU4AC:l/YGU5H4ENWXYQ3N2X6UIS73RUSI:l/T3NDBMPOFOHZO7NH3A2YNRWKA5:l/LPCOWEU2TKSCJSVIXDTUCVXE23:l/RSVIMV
LI4AOOURAXKIOD3ADRZQ:l/G3HOZNNQA4JKGN22V2BDZO6K3R:l/6KKRJKXL6ZP7RV7SHNNXPZFTLF:l/57V4YRQLTWACKIQLR7HUINZJ2G:l/B6B4VTZ33TVPHDIMHLRBAG72AF:l/B35SNKWQ2KMVNYEIEDZLVOXMBY
overlay//(merge/upper/work) 目录则用于 overlay driver进行相关挂载操作。
overlay-containers目录存储正在运行的容器的读写层。
overlay-images 的文件夹用于存储本地系统中镜像的相关元数据。在 overlay-images 文件夹中,针对本地存储中的每个镜像,会在 overlay-images/ 目录下存储各种配置和清单文件,其中 是镜像的ID (ha256 sum of image)。通过将镜像的元数据存储在 overlay-images 文件夹中,可以方便地访问和管理本地系统中镜像的相关信息。这些元数据文件对于容器镜像的操作和管理非常重要,例如查找、验证、比较或删除镜像等。
root@test:/var/lib/containers/storage/overlay-images/300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55# ls
'=bWFuaWZlc3Qtc2hhMjU2OjgzMDFkMTAwMDIwZmZhZWRjOTNmNTdkOGM4YmIwZThlODgwMDY4NjJiY2I0OGViYzVkZmJlMWQxY2I4MzA2MGM='
'=bWFuaWZlc3Qtc2hhMjU2OmRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU='
'=c2hhMjU2OjMwMGUzMTVhZGIyZjk2YWZlNWYwYjI3ODBiODdmMjhhZTk1MjMxZmUzYmRkMWUxNmI5YmE2MDYzMDc3MjhmNTU='
'=c2lnbmF0dXJlLWRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU='
manifest
root@test:/var/lib/containers/storage/overlay-images/300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55# echo "bWFuaWZlc3Qtc2hhMjU2OjgzMDFkMTAwMDIwZmZhZWRjOTNmNTdkO
GM4YmIwZThlODgwMDY4NjJiY2I0OGViYzVkZmJlMWQxY2I4MzA2MGM=" |base64 -d
manifest-sha256:8301d100020ffaedc93f57d8c8bb0e8e88006862bcb48ebc5dfbe1d1cb83060c
root@test:/var/lib/containers/storage/overlay-images/300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55# echo "bWFuaWZlc3Qtc2hhMjU2OmRiYmFjZWNjNDliMDg4NDU4NzgxYzE2Z
jM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=" |base64 -d
manifest-sha256:dbbacecc49b088458781c16f3775f2a2ec7521079034a7ba499c8b0bb7f86875
root@test:/var/lib/containers/storage/overlay-images/300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55# echo "c2hhMjU2OjMwMGUzMTVhZGIyZjk2YWZlNWYwYjI3ODBiODdmMjhhZ
Tk1MjMxZmUzYmRkMWUxNmI5YmE2MDYzMDc3MjhmNTU=" |base64 -d
sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55
root@test:/var/lib/containers/storage/overlay-images/300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55# echo "c2lnbmF0dXJlLWRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmM
mEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=" |base64 -d
signature-dbbacecc49b088458781c16f3775f2a2ec7521079034a7ba499c8b0bb7f86875
podman如何使用Store
举几个实力命令来了解podman如何使用不同的文件:
当运行podman images时,podman会从overlay-images目录中的images.json文件中提取信息,然后打印镜像列表。
root@test:/var/lib/containers/storage/overlay-images# inotifywait -m -e access /var/lib/containers/storage/overlay-images/images.json
Setting up watches.
Watches established.
/var/lib/containers/storage/overlay-images/images.json ACCESS
/var/lib/containers/storage/overlay-images/images.json ACCESS
/var/lib/containers/storage/overlay-images/images.json ACCESS
^C
当运行 podman run ubuntu:latest 命令时,它会在 images.json 文件中搜索 Ubuntu 镜像。如果镜像不存在,它会从镜像仓库中拉取该镜像。一旦镜像存在于本地系统中,它会从 images.json 和 layers.json 文件中获取相关的元数据。然后,它会挂载所有的镜像层,并在顶部添加一个读写层。这些镜像层包括只读的基础镜像层和读写的顶层。对于挂载镜像层,它会查找镜像的最顶层layer(存储在 images.json 中)。它从 overlay//link 文件中获取该层的随机 ID,从 overlay//lower 文件中获取所有父层的 ID。然后,它会从 overlay/l/ 目录中挂载这些镜像层。
最顶层layer如下:
{
"id": "bacbb4f6531eea67a52fc10fa4af0657ff3dbee71c5ef31cf10b32bf4d615562",
"digest": "sha256:c0114756bc381135afefddc026ba5106029fbc9f1fec0063ba7fc5cdee25356c",
"names-history": [
"docker.io/library/dc21594874aaed65754273a27a984e89556b8888a03811f9e1c58983c1c85519-tmp:latest"
],
"layer": "3ee8154a3ab87f4d350b88ac62b9f4c95f32a7916d80837d8a0af84f5d092fef",
......
}
# 对应的layer层父目录软连接
root@test:/var/lib/containers/storage/overlay/3ee8154a3ab87f4d350b88ac62b9f4c95f32a7916d80837d8a0af84f5d092fef# cat lower
l/SZQBY23EDAFYWBJ263MFX7DNU4:l/Q2GL55ZACOG6LMSLTQPS7QDFLL:l/SAJEHSHXNE2NKYN4M33V73HQWG:l/QKYDR52UCEDAFJQCVN5ESUWI4P:l/ICMMCAN2VCVS4URLUS5WEMXJA5:l/WEKUEFPNUDLA7UOXVWUJZLCJGO
# 对应本层数据的软连接
root@test:/var/lib/containers/storage/overlay/3ee8154a3ab87f4d350b88ac62b9f4c95f32a7916d80837d8a0af84f5d092fef# cat link
WJP7FVIYD7FHVYA54NKZY52RBR
# 启动容器
root@test:/var/lib/containers/storage/overlay/3ee8154a3ab87f4d350b88ac62b9f4c95f32a7916d80837d8a0af84f5d092fef# podman run -it bacbb4f6531e /bin/sh
# 查看挂载,注意短链接名称
mount |grep overlay |grep SZQBY23EDAFYWBJ263MFX7DNU4
overlay on /var/lib/containers/storage/overlay/2df09bddc01b6bb1ae65eae790af5a4575df1f975fc486978cd8ac97d79568a5/merged type overlay (rw,nodev,relatime,lowerdir=/var/lib/container
s/storage/overlay/l/WJP7FVIYD7FHVYA54NKZY52RBR:/var/lib/containers/storage/overlay/l/SZQBY23EDAFYWBJ263MFX7DNU4:/var/lib/containers/storage/overlay/l/Q2GL55ZACOG6LMSLTQPS7QDFLL:/
var/lib/containers/storage/overlay/l/SAJEHSHXNE2NKYN4M33V73HQWG:/var/lib/containers/storage/overlay/l/QKYDR52UCEDAFJQCVN5ESUWI4P:/var/lib/containers/storage/overlay/l/ICMMCAN2VCV
S4URLUS5WEMXJA5:/var/lib/containers/storage/overlay/l/WEKUEFPNUDLA7UOXVWUJZLCJGO,upperdir=/var/lib/containers/storage/overlay/2df09bddc01b6bb1ae65eae790af5a4575df1f975fc486978cd8
ac97d79568a5/diff,workdir=/var/lib/containers/storage/overlay/2df09bddc01b6bb1ae65eae790af5a4575df1f975fc486978cd8ac97d79568a5/work,xino=off,metacopy=on)
当运行podman ps命令的时候,会查找overlay-containers/containers.json文件,并列出对应的所有正在运行的容器。
运行容器以后的存储目录结构如下:
root@test:/var/lib/containers/storage# tree -L 4
.
├── libpod
│ ├── bolt_state.db
│ └── defaultCNINetExists
├── mounts
├── overlay
│ ├── 2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859
│ │ ├── diff
│ │ │ ├── bin -> usr/bin
│ │ │ ├── dev
│ │ │ ├── etc
│ │ │ ├── home
│ │ │ ├── lib -> usr/lib
│ │ │ ├── lib64 -> usr/lib64
│ │ │ ├── lost+found
│ │ │ ├── media
│ │ │ ├── mnt
│ │ │ ├── opt
│ │ │ ├── proc
│ │ │ ├── root
│ │ │ ├── run
│ │ │ ├── sbin -> usr/sbin
│ │ │ ├── srv
│ │ │ ├── sys
│ │ │ ├── tmp
│ │ │ ├── usr
│ │ │ └── var
│ │ ├── empty
│ │ ├── link
│ │ ├── merged
│ │ └── work
│ ├── b8648c033100732de66cf583971c08cd862aff24086e92a8ae4ff7fdbebe7bbf(新增的容器层,也就是所谓的读写层)
│ │ ├── diff
│ │ │ └── run
│ │ ├── link
│ │ ├── lower
│ │ ├── merged
│ │ │ ├── bin -> usr/bin
│ │ │ ├── dev
│ │ │ ├── etc
│ │ │ ├── home
│ │ │ ├── lib -> usr/lib
│ │ │ ├── lib64 -> usr/lib64
│ │ │ ├── lost+found
│ │ │ ├── media
│ │ │ ├── mnt
│ │ │ ├── opt
│ │ │ ├── proc
│ │ │ ├── root
│ │ │ ├── run
│ │ │ ├── sbin -> usr/sbin
│ │ │ ├── srv
│ │ │ ├── sys
│ │ │ ├── tmp
│ │ │ ├── usr
│ │ │ └── var
│ │ └── work
│ │ └── work
│ └── l
│ ├── 57MIHXCXZI4TMYKQRHKJAAIU2Q -> ../2653d992f4ef2bfd27f94db643815aa567240c37732cae1405ad1c1309ee9859/diff
│ └── TJT7UBJNLFG43TKK35H2CM4NJN -> ../b8648c033100732de66cf583971c08cd862aff24086e92a8ae4ff7fdbebe7bbf/diff
├── overlay-containers
│ ├── c7941dc49b388654c169e19621290e297a73e6077be67a89e4391497d29d36de
│ │ └── userdata
│ │ ├── artifacts
│ │ ├── attach
│ │ ├── config.json
│ │ ├── ctl
│ │ ├── secrets
│ │ ├── shm
│ │ └── winsz
│ ├── containers.json
│ └── containers.lock
├── overlay-images
│ ├── 300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55
│ │ ├── =bWFuaWZlc3Qtc2hhMjU2OjgzMDFkMTAwMDIwZmZhZWRjOTNmNTdkOGM4YmIwZThlODgwMDY4NjJiY2I0OGViYzVkZmJlMWQxY2I4MzA2MGM=
│ │ ├── =bWFuaWZlc3Qtc2hhMjU2OmRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=
│ │ ├── =c2hhMjU2OjMwMGUzMTVhZGIyZjk2YWZlNWYwYjI3ODBiODdmMjhhZTk1MjMxZmUzYmRkMWUxNmI5YmE2MDYzMDc3MjhmNTU=
│ │ ├── =c2lnbmF0dXJlLWRiYmFjZWNjNDliMDg4NDU4NzgxYzE2ZjM3NzVmMmEyZWM3NTIxMDc5MDM0YTdiYTQ5OWM4YjBiYjdmODY4NzU=
│ │ └── manifest
│ ├── images.json
│ └── images.lock
├── overlay-layers
│ ├── layers.json
│ └── layers.lock
├── storage.lock
├── tmp
└── userns.lock
root@test:/var/lib/containers/storage# mount |grep overlay
overlay on /var/lib/containers/storage/overlay/b8648c033100732de66cf583971c08cd862aff24086e92a8ae4ff7fdbebe7bbf/merged type overlay (rw,nodev,relatime,lowerdir=/var/lib/container
s/storage/overlay/l/57MIHXCXZI4TMYKQRHKJAAIU2Q,upperdir=/var/lib/containers/storage/overlay/b8648c033100732de66cf583971c08cd862aff24086e92a8ae4ff7fdbebe7bbf/diff,workdir=/var/lib
/containers/storage/overlay/b8648c033100732de66cf583971c08cd862aff24086e92a8ae4ff7fdbebe7bbf/work,xino=off,metacopy=on)
images.json 和 layers.json
mages.json 文件存储了每个镜像的元数据,它是一个 JSON 对象,包含以下信息:
1. Image id (镜像 ID:每个镜像都有一个唯一的 ID,用于标识该镜像)
2. Name (名称:镜像的名称,通常是镜像仓库中的标识符)
3. Top most Layer id (最顶层的层 ID:镜像的最顶层的 ID。)
4. Creation time(创建时间:记录镜像的创建时间。)
layers.json文件存储了存储库中每个层的元数据,它也是一个 JSON 对象,包含以下信息:
1. Layer id(每个层都有一个唯一的 ID,用于标识该层。)
2. Creation time(创建时间:记录层的创建时间。)
3. Compressed digest(压缩摘要:对层进行压缩后的摘要信息。)
4. Compressed size(压缩大小:压缩后的层的大小。)
5. Uncompressed digest(解压缩摘要:层解压缩后的摘要信息。)
6. Uncompressed size(解压缩大小:解压缩后的层的大小。)
7. Parent layer(父层:每个层都可以有一个或多个父层,存储了层之间的继承关系。)
附加存储(additional storage)
镜像被存储在 /var/lib/containers/storage 目录中,这是 podman 的唯一可读写的镜像存储区。但是,可以存在多个只读的镜像存储区,称为附加存储。要使用附加存储区,可以将其绝对路径添加到 podman 的配置文件中,该文件位于 /etc/containers/storage.conf。
包含 Ubuntu 镜像的附加存储区的结构如下所示:
/path/to/additional/store
├── overlay
│ ├── 2ce3c1
│ │ ├── diff
│ │ ├── link
│ ├── 4cc8a6
│ │ ├── diff
│ │ ├── link
│ │ ├── lower
│ ├── 720c33
│ │ ├── diff
│ │ ├── link
│ │ ├── lower
│ ├── da0180
│ │ ├── diff
│ │ ├── link
│ │ ├── lower
│ └── l
│ │ ├── DCA3F65L7EAFZTJ55MTGPQMDSM -> ../4cc8a6/diff
│ │ ├── JJG53NDRFWK4UKBVGWDMYNB3L3 -> ../da0180/diff
│ │ ├── NQZ7WILONDZ22PEBUGEZ3MOFID -> ../720c33/diff
│ │ └── VAEMYZJDRVJZDW6ZIJO76NEVCQ -> ../2ce3c1/diff
├── overlay-images
│ ├── 4e2eef
│ │ ├── =bWFY3YTU=
│ │ ├── =bWFZTU=
│ │ ├── =c2hM2M=
│ │ ├── =c2lZTU=
│ │ └── manifest
│ ├── images.json
│ └── images.lock
├── overlay-layers
│ ├── layers.json
│ └── layers.lock
与可读写存储区相比,上述结构有以下变化:
在 overlay// 目录中,我们不再有 upper、merge 和 work 目录。这是因为它是一个只读存储区,因此无法将它们与叠加层(overlay)一起使用。相反,podman 将这些层挂载到可读写存储区的 overlay 目录中。
附加存储区是 podman 的一个强大功能。想象一下,设置了一个只读的镜像存储区,并通过 NFS 使用它,在不将镜像拉取到本地系统的情况下运行容器。 2020 年度 Google Summer of Code(GSoC)项目中,在 CVMFS 存储库中创建了一个附加存储区,可以在其中运行容器而无需将其拉取到本地系统。(这个功能不太熟悉)