共计 12293 个字符,预计需要花费 31 分钟才能阅读完成。
文|唐斌
字节跳动基础架构研发工程师
Nydus 与 Nydus snapshotter 社区贡献者,专一存储,云原生技术。
本文 6964 字 浏览 15 分钟
1 Nydus
1.1 存在的问题
对于容器镜像使用者
问题一: 启动容器慢:容器启动慢的状况广泛产生在当用户启动一个很大的容器镜像时,因为在容器筹备阶段须要三步(以 overlayfs 为例):
– 下载镜像;
– 解压镜像;
– 应用 overlayfs 将容器可写层和镜像中的只读层聚合起来提供容器运行环境。
其中,下载镜像阶段须要下载整个镜像文件,不能实现文件数据按需加载。再加上下载镜像自身受限于网络带宽,当容器镜像达到 GB 级别时,下载工夫会较长,毁坏了容器本来优良的用户体验。
问题二: 较高的本地存储老本:不同镜像之间能够共享的最小单位是镜像中的层,毛病之一是反复数据的解决效率较低。
起因如下:
– 首先,层外部存在反复的数据;
– 其次,层与层之间可能存在大量反复的数据,即便有渺小的差异,也会被作为不同的层;
– 再次,依据 OCI imagespec 对删除文件和 hardlink 的设计,镜像外部曾经被下层删除的文件可能依然存在于上层,并蕴含在镜像中。
对于镜像提供者
这里的提供者次要指容器服务的镜像核心。
问题一: 微小的存储资源节约。
– 存在大量类似镜像,造成这种状况有两个起因:
- 首先,下面提到的层的毛病,导致在容器镜像核心存在许多类似镜像;
- 其次,OCI image 应用了 tar+gzip 格局来示意镜像中的层,而 tar 格局并不辨别 tar archive entries ordering,这带来一个问题,如果用户在不同机器上 build 同一个镜像,最终可能会因为应用了不同的文件系统而失去不同的镜像,用户上传之后,镜像核心中会存在若干不同镜像的本质内容是完全相同的状况。
– 镜像去重效率低
尽管镜像核心有垃圾回收机制来实现去重性能,但其依然以层为单位,所以只能在有完全相同 hash value 的层之间去重。
问题二: 云原生软件供应链带来的新需要。
随着时间推移,和软件供应链一起倒退的还有对软件供应链环节的多样性攻打伎俩。平安防护是软件供应链中十分重要的组成,不光体现在对软件自身的平安加强,也体现在对供应链的平安加强。因为利用运行环境被前置到了容器镜像中,所以对容器镜像的平安,包含对镜像的破绽扫描和签名成为了容器服务提供者的必要能力。
OCI 镜像标准的缺点
次要的缺点有两点:
– tar 格局规范
- tar 格局并不辨别 tar archive entries ordering,这带来一个问题,即如果用户在不同机器上;build 同一个镜像,最终可能会因为应用了不同的文件系统而失去不同的镜像,比方在文件系统 A 上的 order 是 foo 在 bar 之前进入 tar,在文件系统 B 上的 order 是 bar 在 foo 之前进入 tar,那么这两个镜像是不同的;
- 当 tar 被 gzip 压缩过之后不反对 seek,导致运行之前必须先下载并解压 targz 的 image layers,而不能实现文件数据按需加载。
– 以层为镜像的根本单位
- 内容冗余:不同层之间雷同信息在传输和存储时都是冗余内容,在不读取内容的时候无奈判断到这些冗余的存在;
- 无奈并行:每一层是一个整体,对同一个层既无奈并行传输,也不能并行提取;
- 无奈进行小块数据的校验,只有残缺的层下载实现之后,能力对整个层的数据做完整性校验;
- 其余一些问题:比方,跨层数据删除难以完满解决。
1.2 Nydus 根底
在容器的生产实践中,偏小的容器镜像可能很快部署启动。当利用的镜像达到 GB 级以上的时候,在节点上下载镜像通常会耗费大量的工夫。Dragonfly 通过引入 P2P 网络无效地晋升了容器镜像大规模散发的效率。然而,用户必须期待镜像数据残缺下载到本地,而后能力创立本人的容器。
Nydus 是在最新的 OCI Image-Spec 根底之上设计的容器镜像减速服务,从新设计了镜像格局和底层文件系统,从而减速容器启动速度,进步大规模集群中的容器启动成功率。Nydus 由阿里云和蚂蚁团体的工程师合作开发,并大规模部署在外部的 生产环境中。
Nydus 优化了现有的 OCI 镜像规范格局,并以此设计了一个用户态的文件系统。通过这些优化,Nydus 可能提供这些个性:
- 容器镜像按需下载,用户不再须要下载残缺镜像就能启动容器
- 块级别的镜像数据去重,最大限度为用户节俭存储资源
- 镜像只有最终可用的数据,不须要保留和下载过期数据
- 端到端的数据一致性校验,为用户提供更好的数据保护
- 兼容 OCI 散发规范和 artifacts 规范,开箱即可用
反对不同的镜像存储后端,镜像数据不只能够寄存在镜像仓库,还能够放到 NAS 或者相似 S3 的对象存储上
- 与 Dragonfly 的良好集成
1.3 Nydus 架构
Nydus 的架构次要蕴含两局部内容:
– 新的镜像格局(Rafs)
– 负责解析容器镜像的 FUSE 用户态文件系统过程
Nydus 兼容多种文件系统,可能解析 FUSE 和 virtiofs 协定来反对传统的 runc 容器、Kata 容器。对于存储后端,反对应用容器仓库(Registery)、OSS 对象存储、NAS、Dragonfly 的超级节点和 Peer 节点作为 Nydus 的镜像数据存储后端。此外,为了减速启动速度,Nydus 还能够配置一个本地缓存,防止每次启动容器时都从远端数据源拉取数据。
1.4 Nydus 个性
容器启动速度变快
用户部署了 Nydus 镜像服务后,因为应用了按需加载镜像数据的个性,容器的启动工夫显著缩短。在官网的测试数据中,Nydus 可能把常见镜像的启动工夫,从数分钟缩短到数秒钟。实践上来说,容器镜像越大,Nydus 体现进去的成果越显著。
提供运行时数据统一校验
在传统的镜像中,镜像数据会先被解压到本地文件系统,再由容器利用去拜访应用。解压前,镜像数据是残缺校验的。然而解压之后,镜像数据不再可能被校验。这带来的一个问题就是,如果解压后的镜像数据被无心或者歹意地批改,用户是无奈感知的。而 Nydus 镜像不会被解压到本地,同时能够对每一次数据拜访进行校验,如果数据被篡改,则能够从远端数据源从新拉取。
从图中能够看出,对容器镜像数据进行运行时一致性校验是通过对每个数据块计算 SHA 256 实现的,这得益于 Nydus 采纳分块的形式治理镜像数据。如果在镜像文件十分大的时候,对整个镜像文件计算哈希值十分不事实。
1.5 Nydus 镜像格局:RAFS
RAFS 是对 EROFS 文件系统的加强,拓展在云原生场景下的能力,使其适应容器镜像存储场景。RAFS v6 是内核态的容器镜像格局,除了将镜像格局下沉到内核态,还在镜像格局上进行了一系列优化,例如块对齐、更加精简的元数据等。
RAFS v6 镜像格局
1.6 Nydus -snapshotter
Nydus snapshotter 是 containerd 的一个内部插件,使得 containerd 可能应用 Nydus 镜像减速服务。在 containerd 中,snapshot 的工作是给容器提供 rootfs,Nydus snapshotter 实现了 containerd 的 snapshot 的接口,使得 containerd 能够通过 Nydus 筹备 rootfs 以启动容器。因为 nydus-snapshotter 实现了按需加载的个性,在 containerd 启动容器时,只须要依据容器镜像的元数据信息筹备 rootfs,局部目录对应的数据并未存储在本地,当在容器中拜访到(本地拜访未命中)这部分数据时,通过 Nydusd 从镜像 registry 拉取对应数据内容。
02 FUSE
用户态文件系统(filesystem in userspace,简称 FUSE)使得用户无需批改内核代码就能创立自定义的文件系统。FUSRE 催生了驰名的 fuse-overlayfs,其在 rootless 容器化中表演重要的角色。
用户态文件系统并不齐全在用户态实现,由两局部组成:内核模块和用户态过程。
- 内核模块:文件系统数据流程的性能实现,负责截获文件拜访申请和返回用户态过程解决申请的后果
- 用户态过程:负责解决具体的数据申请,对应处理函数由内核模块触发
FUSE 的工作流程如下图:
其中,fuse_user 是运行在用户态的文件系统过程,该程序会在启动时注册实现的数据申请解决接口,如 ls、cd、mkdir 等,同时,程序在某个门路挂载 fuse 文件系统 /tmp/fuse_fs,当对 /tmp/fuse_fs 执行相干操作时:
- 申请会通过 VFS(虚构文件系统)达到 fuse 的内核模块
- 内核模块依据申请类型,调用用户态过程注册的函数
- 当程序实现对申请的解决后,将后果通过 VFS 返回给零碎调用
03 Containerd
Containerd 最开始是 Docker Engine 中的一部分,起初,containerd 被分离出来作为独立的开源我的项目,指标是提供更凋谢、稳固的容器运行基础设施。分离出来的 containerd 将具备更多的性能,涵盖整个容器运行时治理的所有需要。
Containerd 是一个行业标准的容器运行时,强调简略性、健壮性和可移植性,能够作为守护过程运行在零碎中。
Containerd 的性能次要包含以下内容:
- 治理容器的生命周期(从创立容器到销毁容器)
- 拉取 / 推送容器镜像
- 存储管理(治理镜像及容器数据的存储)
- 调用 runc 运行容器(与 runc 等容器运行时交互)
- 治理容器网络接口及网络
Containerd 采纳 C/S 架构,服务端通过 unix domain socket 裸露低层 gRPC 接口,客户端通过这些 gRPC 接口治理节点上的容器,containerd 负责管理容器的镜像、生命周期、网络和存储,理论运行容器是由容器运行时(runc 是其中一种)实现。
Containerd 将零碎划分成不同的组件,每个组件由一个或多个模块合作实现(Core 局部),不同模块都以插件的模式集成到 containerd 中,插件之间相互依赖。
Containerd 的组件能够分成三类:Storage、Metadata 和 Runtimes,snapshot 属于 Storage 组件中的一个插件,用来治理容器镜像的文件系统快照,镜像中的每一层都会被解压成文件系统快照。在应用 Nydus 的 containerd 环境中,Nydus-snapshotter 负责实现这部分工作。
04 Erofs + fsache
Erofs over fscache 基本概念
Erofs over fscache 是 Linux 内核原生的镜像按需加载个性,于 5.19 版本合入 Linux 内核主线。
已有的用户态计划会波及频繁的内核态 / 用户态上下文切换,以及内核态 / 用户态之间的内存拷贝,从而造成性能瓶颈。这一问题在容器镜像曾经全副下载到本地的时候尤其突出,容器运行过程中波及的文件拜访,都会陷出到用户态的服务过程。
事实上咱们能够将按需加载的
(1)缓存治理和
(2)缓存未命中的时候通过各种路径 (例如网络) 获取数据,这两个操作解耦开。缓存治理能够下沉到内核态执行,这样当镜像在本地 ready 的时候,就能够防止内核态 / 用户态上下文的切换。而这也正是 erofs over fscache 技术的价值所在。
fscache/cachefiles (以下统称 fscache) 是 Linux 零碎中绝对成熟的文件缓存计划,广泛应用于网络文件系统,erofs over fscache 技术使得 fsache 可能反对 erofs 的按需加载个性。
容器在拜访容器镜像的时候,fscache 会查看以后申请的数据是否曾经缓存,如果缓存命中 (cache hit),那么间接从缓存文件读取数据。这一过程全程处于内核态之中,并不会陷出到用户态。
缓存未命中 (cache miss)时 须要告诉用户态的 Nydusd 过程以解决这一拜访申请,此时容器过程会陷入睡眠期待状态;Nydusd 通过网络从远端获取数据,通过 fscache 将这些数据写入对应的缓存文件,之后告诉之前陷入睡眠期待状态的过程该申请曾经解决实现;之后容器过程即可从缓存文件读取到数据。
Erofs over fscache 劣势
– 异步预取
容器创立之后,当容器过程尚未触发按需加载 (cache miss) 的时候,用户态的 Nydusd 就能够开始从网络下载数据并写入缓存文件,之后当容器拜访的文件地位恰好处于预取范畴内的时候,就会触发 cache hit 间接从缓存文件读取数据,而不会再陷出到用户态。用户态计划则无奈实现该优化。
– 网络 IO 优化
当触发按需加载 (cache miss) 时,Nydusd 能够一次性从网络下载比以后理论申请的数据量更多的数据,并将下载的数据写入缓存文件。例如容器拜访 4K 数据触发的 cache miss,而 Nydusd 理论一次性下载 1MB 数据,以减小单位文件大小的网络传输延时。之后容器拜访接下来的这 1MB 数据的时候,就 不用再陷出到用户态。
用户态计划则无奈实现该优化,因为即便触发 cache miss 的时候,用户态的服务过程同样实现了该优化,下一次容器拜访位于读放大范畴内的文件数据的时候,同样会陷出到用户态。
– 更佳的性能体现
当镜像数据曾经全副下载到本地的时候 (即不思考按需加载的影响),erofs over fscache 的性能体现显著优于用户态计划,同时与原生文件系统的性能相近,从而实现与原生容器镜像计划 (未实现按需加载) 相近的性能体现。
05 环境装置
nerdctl 装置
# git clone https://github.com/rootless-containers/rootlesskit.git
# cd rootlesskit
# make && sudo make install
wget https://github.com/containerd/nerdctl/releases/download/v0.22.2/nerdctl-full-0.22.2-linux-amd64.tar.gz
sudo tar -zxvf nerdctl-full-0.22.2-linux-amd64.tar.gz -C /usr/local
sudo systemctl enable --now containerd
sudo systemctl enable --now buildkit
# sudo apt-get install uidmap -y
# containerd-rootless-setuptool.sh install
sudo nerdctl version # 须要应用 sudo,不然会提醒装置 rootless
Nydus 装置
装次要的 3 个工具(也能够间接下载所有工具的二进制文件,编译装置时默认没有没有 Nydusify):
– nydusify
将 OCI 格局的容器镜像转换为 Nydus 格局(RAFS)容器镜像的工具。
– nydus-image
将解压后的容器镜像转换为 Nydus 格局镜像的工具。
– nydusd
解析 Nydus 格局镜像并提供 FUSE 挂载点以供容器拜访的守护程序。nydusd
也能够用作 virtiofs 后端,使得 Guest 能够拜访 Host 的文件。
git clone https://github.com/dragonflyoss/image-service.git
cd image-service
make && make install
# 默认没有装置 nydusify
wget https://github.com/dragonflyoss/image-service/releases/download/v2.1.0-rc.1/nydus-static-v2.1.0-rc.1-linux-amd64.tgz
mkdir nydus-static
tar -zxvf nydus-static-v2.1.0-rc.1-linux-amd64.tgz -C nydus-static
sudo cp nydus-static/nydusify /usr/local/bin
sudo cp nydus-static/nydus-overlayfs /usr/local/bin
nydus-image --version
nydusify --version
nydusd --version
装置 Nydus-snapshotter
git clone github.com/containerd/nydus-snapshotter.git
cd nydus-snapshotter
make && make install
sudo systemctl enable nydus-snapshotter
sudo systemctl start nydus-snapshotter
systemctl status nydus-snapshotter
Nydus-snapshotter 以 service 的模式运行 /usr/local/bin/containerd-nydus-grpc 可执行文件,配置信息位于 /etc/nydus/config.json 文件。
默认 address 地位:
/run/containerd-nydus/containerd-nydus-grpc.sock
默认工作目录:
/var/lib/containerd-nydus-grpc
默认缓存目录:
/var/lib/containerd-nydus-grpc/cache
部署本地镜像仓库(测试用)
# sudo docker run -d -p 5000:5000 \
# --restart=always \
# --name registry \
# -v /var/lib/registry:/var/lib/registry \
# -d registry
sudo docker run -d --name=registry --restart=always -p 5000:5000 registry
sudo docker logs registry -f
将 OCI 格局的镜像转换为 RAFS 格局镜像
sudo nydusify convert \
--nydus-image $(which nydus-image) \
--source ubuntu:16.04 \
--target localhost:5000/ubuntu:16.04-nydus
Nydusify 根本命令:
转换后的镜像层文件位于当前目录下的 tmp 文件夹:
sudo tree tmp -L 4
将 OCI 规范的镜像格局转换为 Nydus 应用的 RAFS 镜像格局后,能够应用 Nydusd 解析并提供 fuse 挂载点供容器应用。编写配置文件 registry.json,使得 Nydus 应用 容器镜像 registry(曾经搭建本地容器镜像 register 用于测试)作为存储后端。
{
"device": {
"backend": {
"type": "registry",
"config": {
"scheme": "http",
"host": "localhost:5000",
"repo": "ubuntu"
}
},
"digest_validate": false
},
"mode": "direct"
}
挂载 RAFS 镜像 为 fuse 挂载点,–bootstrap 参数传递位于 tmp/bootstraps 下的文件门路:
sudo nydusd \
--config ./registry.json \
--mountpoint /mnt \
--bootstrap ./tmp/bootstraps/4-sha256:fb15d46c38dcd1ea0b1990006c3366ecd10c79d374f341687eb2cb23a2c8672e \
--log-level info
查看挂载状况:
输入日志信息:
除了应用 Nydusify 间接将 OCI 规范格局镜像转换为 Nydus 格局镜像,Nydus-image 工具也反对间接对曾经解压的 OCI 容器镜像文件层转换为 Nydus 格局镜像。
(1)获取 OCI 镜像元数据信息:
docker pull ubuntu:16.04
sudo docker inspect -f "{{json .GraphDriver}}" ubuntu:16.04 | jq .
Docker 应用 overlay2 存储驱动,通过所需的 lowerdir、upperdir、merged 和 workdir 构造主动创立 overlay 挂载点。
对于 Nydus 来说,目录树(通常是一个镜像层)由两局部组成:
- bootstrap:存储目录的文件系统元数据信息
- blob:存储目录中的所有文件数据
(2)建设生成 Nydus 镜像的目录:
mkdir -p nydus-image/{blobs,layer1,layer2,layer3,layer4}
(3)转换最底层的镜像层:
sudo nydus-image create \
--bootstrap ./nydus-image/layer1/bootstrap \
--blob-dir ./nydus-image/blobs \
--compressor none /var/lib/docker/overlay2/78f2b3506072c95ca3929a0a797c1819e8966b8bbf5ce8427b671296ca1ad35a/diff
tree -L 2 ./nydus-image
(4)转换第 2 底层镜像层,–parent-bootstrap 指父层,即方才转换好的镜像层:
sudo nydus-image create \
--parent-bootstrap ./nydus-image/layer1/bootstrap \
--bootstrap ./nydus-image/layer2/bootstrap \
--blob-dir ./nydus-image/blobs \
--compressor none /var/lib/docker/overlay2/373ea430abb0edd549583f949ec8259806d9eb7d0a0416ec1494d2fc7efeeedc/diff
(5)转换第 3 层和第 4 层,每次都须要指定 –parent-bootstrap 为上一次生成的镜像层:
sudo nydus-image create \
--parent-bootstrap ./nydus-image/layer2/bootstrap \
--bootstrap ./nydus-image/layer3/bootstrap \
--blob-dir ./nydus-image/blobs \
--compressor none /var/lib/docker/overlay2/05424b8c067c59368c11ad5674d68d95365e87487bdf10e3d9842b1016583369/diff
sudo nydus-image create \
--parent-bootstrap ./nydus-image/layer3/bootstrap \
--bootstrap ./nydus-image/layer4/bootstrap \
--blob-dir ./nydus-image/blobs \
--compressor none /var/lib/docker/overlay2/942c712e7276be5bde4fb7b30f72583c4a9cf0b2aaa14215cd690daf893a630e/diff
将 Nydus 镜像挂载到目录:
sudo nydusd \
--config ./localfs.json \
--mountpoint /mnt \
--bootstrap ./nydus-image/layer4/bootstrap \
--log-level info
其中,localfs.json 文件的内容为:
{
"device": {
"backend": {
"type": "localfs",
"config": {"dir": "/<YOUR-WORK-PATH>/nydus-image/blobs"}
}
},
"mode": "direct"
}
Dir 为生成的 Nydus 镜像文件中 blobs 目录的绝对路径。
06 通过 Nydus+snapshotter 启动容器
增加配置文件
Nydus 提供了 containerd 近程快照管理工具 containerd-nydus-grpc 用于筹备 Nydus 镜像格局的容器 rootfs,首先将 Nydusd 配置保留到 /etc/nydus/config.json 文件。
sudo tee /etc/nydus/config.json > /dev/null << EOF
{
"device": {
"backend": {
"type": "registry",
"config": {
"scheme": "http",
"skip_verify": false,
"timeout": 5,
"connect_timeout": 5,
"retry_limit": 2,
"auth": ""
}
},
"cache": {
"type": "blobcache",
"config": {"work_dir": "cache"}
}
},
"mode": "direct",
"digest_validate": false,
"iostats_files": false,
"enable_xattr": true,
"fs_prefetch": {
"enable": true,
"threads_count": 4
}
}
EOF
containerd-nydus-grpc 会主动从 $HOME/.docker/config.json 中读取 docker login auth,如果不想应用这个值,须要间接替换配置文件中的内容。
间接从终端启动 containerd-nydus-grpc,如果曾经通过 containerd-nydus-grpc service 启动,则能够跳过此步骤:
sudo /usr/local/bin/containerd-nydus-grpc \
--config-path /etc/nydus/config.json \
--shared-daemon \
--log-level info \
--root /var/lib/containerd/io.containerd.snapshotter.v1.nydus \
--cache-dir /var/lib/nydus/cache \
--address /run/containerd-nydus/containerd-nydus-grpc.sock \
--nydusd-path /usr/local/bin/nydusd \
--nydusimg-path /usr/local/bin/nydus-image \
--log-to-stdout
批改 containerd 配置文件
proxy_plugins.nydus 的 address 和 containerd-nydus-grpc 的对应。
sudo tee -a /etc/containerd/config.toml << EOF
[proxy_plugins]
[proxy_plugins.nydus]
type = "snapshot"
address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
[plugins.cri]
[plugins.cri.containerd]
snapshotter = "nydus"
disable_snapshot_annotations = false
EOF
sudo systemctl restart containerd
sudo ctr -a /run/containerd/containerd.sock plugin ls | grep nydus
通过 Nydus 启动容器
# 转换镜像并上传到本地 registry
sudo nydusify convert --nydus-image /usr/local/bin/nydus-image --source ubuntu --target localhost:5000/ubuntu-nydus
sudo nerdctl --snapshotter nydus pull localhost:5000/ubuntu-nydus:latest
sudo nerdctl --snapshotter nydus run --rm -it localhost:5000/ubuntu-nydus:latest bash
重启主机之后启动环境
sudo systemctl restart nydus-snapshotter
sudo systemctl restart containerd
sudo docker rm -f registry
sudo docker run -d --name=registry --restart=always -p 5000:5000 registry && sudo docker logs registry -f
07 参考资料
[1]OCI 镜像规范格局:
https://github.com/opencontainers/image-spec
[2]自校验的哈希树:
https://en.wikipedia.org/wiki/Merkle_tree
[3]FUSE:
https://www.kernel.org/doc/html/latest/filesystems/fuse.html
[4]virtiofs:
https://virtio-fs.gitlab.io/
[5]runc 容器:
https://github.com/opencontainers/runc
[6]Kata 容器:
https://katacontainers.io/
[7]OSS 对象存储:
https://www.alibabacloud.com/product/oss
[8]Nydus-snapshotter:
https://github.com/containerd/nydus-snapshotter
[9]fuse-overlayfs:
https://github.com/containers/fuse-overlayfs
[10]5.19 版本:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=65965d9530b0c320759cd18a9a5975fb2e098462
[11]对于容器镜像使用者:
https://mp.weixin.qq.com/s/yC-UmMSDja959K9i_jSucQ
本周举荐浏览
Nydus —— 下一代容器镜像的摸索实际
Nydus 镜像减速插件迁入 Containerd 旗下
cgo 机制 – 从 c 调用 go
从规模化平台工程实际,咱们学到了什么?