重学容器09: Containerd是如何存储容器镜像和数据的
yuyutoo 2024-12-19 17:34 3 浏览 0 评论
前面简单理解了Containerd的架构,本节来看一下Containerd是如何存储镜像和容器的,涉及到内容包括如何镜像存储和RootFS。
从pull镜像到启动容器
Containerd的配置文件中有如下两项配置:
root = "/var/lib/containerd"
state = "/run/containerd"
root配置的目录是用来保存持久化数据的目录,包括content, snapshot, metadata和runtime。 下面在一台测试的服务器上,删除所有的镜像和容器后,再执行下面的命令重新初始化一下这些目录,以准备后边的实验。
systemctl stop containerd
rm -rf /var/lib/containerd/*
rm -rf /var/lib/nerdctl/*
systemctl start containerd
完成测试环境初始化后,重新查看/var/lib/containerd目录:
tree /var/lib/containerd/ -L 2
/var/lib/containerd/
├── io.containerd.content.v1.content
│ └── ingest
├── io.containerd.metadata.v1.bolt
│ └── meta.db
├── io.containerd.runtime.v1.linux
├── io.containerd.runtime.v2.task
├── io.containerd.snapshotter.v1.btrfs
├── io.containerd.snapshotter.v1.native
│ └── snapshots
├── io.containerd.snapshotter.v1.overlayfs
│ └── snapshots
└── tmpmounts
/var/lib/containerd的各个子目录很清晰的对应到了content, snapshot, metadata和runtime,和containerd的架构示意图中的子系统和组件上:
/var/lib/containerd下各个子目录的名称也可以对应到使用ctr plugin ls查看打印的部分插件名称,实际上这些目录是Containerd的插件用于保存数据的目录,每个插件都可以有自己单独的数据目录,Containerd本身不存储数据,它的所有功能都是通过插件实现的。
下面按照下面containerd的数据流图,依次执行从pull镜像、启动容器、在容器中创建一个文件步骤,并观察containerd的数据目录的变化。
先pull一个镜像:
nerdctl pull redis:alpine3.13
docker.io/library/redis:alpine3.13: resolved |++++++++++++++++++++++++++++++++++++++|
index-sha256:eaaa58f8757d6f04b2e34ace57a71d79f8468053c198f5758fd2068ac235f303: done |++++++++++++++++++++++++++++++++++++++|
manifest-sha256:b7cb70118c9729f8dc019187a4411980418a87e6a837f4846e87130df379e2c8: done |++++++++++++++++++++++++++++++++++++++|
config-sha256:1690b63e207f6651429bebd716ace700be29d0110a0cfefff5038bb2a7fb6fc7: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:6ab1d05b49730290d3c287ccd34640610423d198e84552a4c2a4e98a46680cfd: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:8cc52074f78e0a2fd174bdd470029cf287b7366bf1b8d3c1f92e2aa8789b92ae: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:aa7854465cce07929842cb49fc92f659de8a559cf521fc7ea8e1b781606b85cd: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:8173c12df40f1578a7b2dfbbc0034a4fbc8ec7c870fd32b9236c2e5e1936616a: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:540db60ca9383eac9e418f78490994d0af424aab7bf6d0e47ac8ed4e2e9bcbba: done |++++++++++++++++++++++++++++++++++++++|
layer-sha256:29712d301e8c43bcd4a36da8a8297d5ff7f68c3d4c3f7113244ff03675fa5e9c: done |++++++++++++++++++++++++++++++++++++++|
elapsed: 16.4s total: 7.7 Mi (481.5 KiB/s)
从上面命令的执行过程来看总共pull了1个index, 1个manifest和6个layer。
io.containerd.metadata.v1.bolt/meta.db是boltdb文件,存储了对images和bundles的持久化引用。 boltdb是一个嵌入式的key/value数据库,containerd的源码文件https://github.com/containerd/containerd/blob/master/metadata/buckets.go头部的注释描述了db schema数据结构。
// keys.
// ├──version : <varint> - Latest version, see migrations
// └──v1 - Schema version bucket
// ╘══*namespace*
// ├──labels
// │ ╘══*key* : <string> - Label value
// ├──image
// │ ╘══*image name*
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──target
// │ │ ├──digest : <digest> - Descriptor digest
// │ │ ├──mediatype : <string> - Descriptor media type
// │ │ └──size : <varint> - Descriptor size
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──containers
// │ ╘══*container id*
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──spec : <binary> - Proto marshaled spec
// │ ├──image : <string> - Image name
// │ ├──snapshotter : <string> - Snapshotter name
// │ ├──snapshotKey : <string> - Snapshot key
// │ ├──runtime
// │ │ ├──name : <string> - Runtime name
// │ │ ├──extensions
// │ │ │ ╘══*name* : <binary> - Proto marshaled extension
// │ │ └──options : <binary> - Proto marshaled options
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──snapshots
// │ ╘══*snapshotter*
// │ ╘══*snapshot key*
// │ ├──name : <string> - Snapshot name in backend
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──parent : <string> - Parent snapshot name
// │ ├──children
// │ │ ╘══*snapshot key* : <nil> - Child snapshot reference
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──content
// │ ├──blob
// │ │ ╘══*blob digest*
// │ │ ├──createdat : <binary time> - Created at
// │ │ ├──updatedat : <binary time> - Updated at
// │ │ ├──size : <varint> - Blob size
// │ │ └──labels
// │ │ ╘══*key* : <string> - Label value
// │ └──ingests
// │ ╘══*ingest reference*
// │ ├──ref : <string> - Ingest reference in backend
// │ ├──expireat : <binary time> - Time to expire ingest
// │ └──expected : <digest> - Expected commit digest
// └──leases
// ╘══*lease id*
// ├──createdat : <binary time> - Created at
// ├──labels
// │ ╘══*key* : <string> - Label value
// ├──snapshots
// │ ╘══*snapshotter*
// │ ╘══*snapshot key* : <nil> - Snapshot reference
// ├──content
// │ ╘══*blob digest* : <nil> - Content blob reference
// └──ingests
// ╘══*ingest reference* : <nil> - Content ingest reference
可以看出主要记录了关于image, content, snapshots, containers的元数据。这里编写了一个简单的go程序读取打印一下当前boltdb中的内容,注意上面结构描述中的一些binary类型会打印成乱码。
package main
import (
"fmt"
"log"
bolt "go.etcd.io/bbolt"
)
func main() {
{
db, err := bolt.Open("/var/lib/continaerd/io.containerd.metadata.v1.bolt/meta.db", 0666, nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("v1")).Bucket([]byte("default"))
space := ""
travelBucket(b, space)
return nil
})
}
}
func travelBucket(b *bolt.Bucket, space string) {
space = space + "\t"
b.ForEach(func(k, v []byte) error {
if v == nil {
fmt.Printf("%sbucket=%s: \n", space, k)
travelBucket(b.Bucket([]byte(k)), space)
} else {
fmt.Printf("%skey=%s, value=%s\n", space, k, v)
}
return nil
})
}
到这里只需明白一点,meta.db中存储的是各个存储的元数据。那么实际pull的镜像被存储到哪了呢? 镜像内容被存储到了io.containerd.content.v1.content/blobs/sha256中:
ll io.containerd.content.v1.content/blobs/sha256
total 10668
1690b63e207f6651429bebd716ace700be29d0110a0cfefff5038bb2a7fb6fc7
29712d301e8c43bcd4a36da8a8297d5ff7f68c3d4c3f7113244ff03675fa5e9c
540db60ca9383eac9e418f78490994d0af424aab7bf6d0e47ac8ed4e2e9bcbba
6ab1d05b49730290d3c287ccd34640610423d198e84552a4c2a4e98a46680cfd
8173c12df40f1578a7b2dfbbc0034a4fbc8ec7c870fd32b9236c2e5e1936616a
8cc52074f78e0a2fd174bdd470029cf287b7366bf1b8d3c1f92e2aa8789b92ae
aa7854465cce07929842cb49fc92f659de8a559cf521fc7ea8e1b781606b85cd
b7cb70118c9729f8dc019187a4411980418a87e6a837f4846e87130df379e2c8
eaaa58f8757d6f04b2e34ace57a71d79f8468053c198f5758fd2068ac235f303
上面的9个文件,正好对应1个index文件, 1个config, 1个manifest文件和6个layer文件。index和manifest可以直接用cat命令查看,layer文件可以用tar解压缩。因此content中保存的是config, manifest, tar文件是OCI镜像标准的那套东西。
而实际上containerd也确实是将这些content中的tar解压缩到snapshot中。
查看/var/lib/containerd目录:
tree /var/lib/containerd/ -L 3
/var/lib/containerd/
├── io.containerd.content.v1.content
│ ├── blobs
│ │ └── sha256
│ └── ingest
├── io.containerd.metadata.v1.bolt
│ └── meta.db
├── io.containerd.runtime.v1.linux
├── io.containerd.runtime.v2.task
├── io.containerd.snapshotter.v1.btrfs
├── io.containerd.snapshotter.v1.native
│ └── snapshots
├── io.containerd.snapshotter.v1.overlayfs
│ ├── metadata.db
│ └── snapshots
│ ├── 1
│ ├── 2
│ ├── 3
│ ├── 4
│ ├── 5
│ └── 6
└── tmpmounts
可以看到io.containerd.snapshotter.v1.overlayfs/snapshots中多了名称为1~6的6个子目录,查看这6个目录:
tree /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ -L 3
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/
├── 1
│ ├── fs
│ │ ├── bin
│ │ ├── dev
│ │ ├── etc
│ │ ├── home
│ │ ├── lib
│ │ ├── media
│ │ ├── mnt
│ │ ├── opt
│ │ ├── proc
│ │ ├── root
│ │ ├── run
│ │ ├── sbin
│ │ ├── srv
│ │ ├── sys
│ │ ├── tmp
│ │ ├── usr
│ │ └── var
│ └── work
├── 2
│ ├── fs
│ │ ├── etc
│ │ └── home
│ └── work
├── 3
│ ├── fs
│ │ ├── etc
│ │ ├── lib
│ │ ├── sbin
│ │ ├── usr
│ │ └── var
│ └── work
├── 4
│ ├── fs
│ │ ├── bin
│ │ ├── etc
│ │ ├── lib
│ │ ├── tmp
│ │ ├── usr
│ │ └── var
│ └── work
├── 5
│ ├── fs
│ │ └── data
│ └── work
└── 6
├── fs
│ └── usr
└── work
containerd的snapshotter的主要作用就是通过mount各个层为容器准备rootfs。containerd默认配置的snapshotter是overlayfs,overlayfs是联合文件系统的一种实现。 overlayfs将只读的镜像层成为lowerdir,将读写的容器层成为upperdir,最后联合挂载呈现出mergedir。
下面启动一个redis容器:
nerdctl run -d --name redis redis:alpine3.13
可以看到io.containerd.snapshotter.v1.overlayfs/snapshots中多了名称为7的目录:
tree /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ -L 2
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/
├── 1
│ ├── fs
│ └── work
├── 2
│ ├── fs
│ └── work
├── 3
│ ├── fs
│ └── work
├── 4
│ ├── fs
│ └── work
├── 5
│ ├── fs
│ └── work
├── 6
│ ├── fs
│ └── work
└── 7
├── fs
└── work
可以使用mount命令查看容器挂载的overlayfs的RootFS:
mount | grep /var/lib/containerd
overlay on /run/containerd/io.containerd.runtime.v2.task/default/8102f7fbee26792830e54e80b3488714ac559e092c59beb2e311cf8e88f475d6/rootfs type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/5/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/3/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/work)
可以看出: snapshots/6/fs, snapshots/5/fs…snapshots/1/fs为lowerdir,snapshots/7/fs为upperdir。 最终联合挂载合并呈现的目录为/run/containerd/io.containerd.runtime.v2.task/default/8102f7fbee26792830e54e80b3488714ac559e092c59beb2e311cf8e88f475d6/rootfs即为容器的rootfs,ls查看这个目录可以看到一个典型的linux系统目录结构:
ls /run/containerd/io.containerd.runtime.v2.task/default/8102f7fbee26792830e54e80b3488714ac559e092c59beb2e311cf8e88f475d6/rootfs
bin data dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
我们exec到容器中去,并在/root目录中创建一个hello文件:
nerdctl exec -it redis sh
echo hello > /root/hello
在宿主机上的upperdir中可以找到这个文件。在容器中对文件系统做的改动都会体现在upperdir中:
ls /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/fs/root/
hello
containerd的Snapshotter
最后来看一下containerd的Snapshot组件。Snapshot为containerd实现了Snapshotter用于管理文件系统上容器镜像的快照和容器的rootfs挂载和卸载等操作功能。 snapshotter对标Docker中的graphdriver存储驱动的设计。contaienrd在设计上使用snapshotter新模式取代了docker中的graphdriver,其核心开发人员也在其博客中《 Where are containerd’s graph drivers?》中介绍了为什么要这么做。
参考
- https://github.com/containerd/containerd
- https://github.com/etcd-io/bbolt
- Where are containerd’s graph drivers?
- Contaienrd Snapshots
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)