关于容器:Nydus-下一代容器镜像的探索实践

8次阅读

共计 10263 个字符,预计需要花费 26 分钟才能阅读完成。

文|严松(花名:井守 )

Nydus 镜像开源我的项目 Maintainer、蚂蚁团体技术专家

蚂蚁团体基础设施研发,专一云原生镜像与容器运行时生态

本文 7060 字 浏览 15 分钟

|前言|

容器镜像是云原生的基础设施之一,作为容器运行时文件系统视图根底,从它诞生到当初,衍生出了镜像构建、存储、散发到运行时的整个镜像生命周期的各种生态。

然而,尽管镜像生态泛滥,但自它诞生以来,镜像设计自身并没有多少改良。这篇文章要探讨的就是对容器镜像将来倒退的一些思考,以及 Nydus 容器镜像的摸索和实际。

读完这篇文章,你可能理解到:

容器镜像的基本原理,以及它的组成格局;

目前的镜像设计有哪些问题,应该如何改良;

Nydus 容器镜像做了哪些摸索,以及怎么实际。

PART. 1

容器镜像

OCI 容器镜像标准

容器提供给了利用一个疾速、轻量且有着根本隔离环境的运行时,而镜像提供给了容器 RootFS,也就是容器内能看到的整个 Filesystem 视图,其中至多包含了文件目录树结构、文件元数据以及数据局部。镜像的特点如下:

易于传输,例如通过网络以 HTTP 的形式从 Registry 上传或下载;

易于存储,例如能够打包成 Tar Gzip 格局,存储在 Registry 上;

具备不可变个性,整个镜像有一个惟一 Hash,只有镜像内容发生变化,镜像 Hash 也会被扭转。

晚期的镜像格局是由 Docker 设计的,经验了从 Image Manifest V1[1]、V2 Scheme 1[2]到 V2 Scheme 2[3]的演进。起初呈现了诸如 CoreOS 推出的其余容器运行时后,为了防止竞争和生态凌乱,OCI 标准化社区成立。它定义了容器在运行时、镜像以及散发相干的实现规范,咱们目前用的镜像格局根本都是 OCI 兼容的。

镜像次要是由镜像层和容器配置两大部分组成的。

什么是镜像层?

能够回忆下平时写的 Dockerfile 文件:每条 ADD、COPY、RUN 指令都可能会产生新的镜像层,新层蕴含的是在旧层的根底上,新减少或批改的文件 (蕴含元数据和数据),或被删除的文件 (用称之为  Whiteout *[4] 的非凡文件示意删除)*。

所以简略来说镜像的每一层存储的是 Lower 与 Upper 之间的 Diff,十分相似 Git Commit。这层 Diff 通常会被压缩成 Tar Gzip 格局后上传到 Registry。

在运行时,所有 Diff 重叠起来后,就组成了提供给容器的整个文件系统视图,也就是 RootFS。镜像的另外一部分是容器运行时配置,这部分蕴含了命令、环境变量、端口等信息。

镜像层和运行时配置各自有一个惟一 Hash (通常是 SHA256),这些 Hash 会被写进一个叫 Manifest[5]的 JSON 文件里,在 Pull 镜像时理论就是先拉取 Manifest 文件,而后再依据 Hash 去 Registry 拉取对应的镜像层 / 容器运行时配置。

目前的镜像设计问题

第一 ,咱们留神到镜像层须要全副重叠后,容器能力看到整个文件系统视图,所以容器须要等到镜像的每一层都下载并解压之后能力启动。有一篇 FAST 论文钻研剖析[6] 说镜像拉取占了大概容器 76% 的启动工夫,但却只有 6.4% 的数据是会被容器读取的。这个后果很乏味,它激发了咱们能够通过按需加载的形式来进步容器启动速度。另外,在层数较多的状况下,运行时也会有 Overlay 重叠的开销。

第二,每层镜像是由元数据和数据组成的,那么这就导致某层镜像中只有有一个文件元数据发生变化,例如批改了权限位,就会导致层的 Hash 发生变化,而后导致整个镜像层须要被从新存储,或从新下载。

第三,如果某个文件在 Upper 层里被删除或者被批改,旧版本文件仍然留存在 Lower 层里不会被删除。在拉取新镜像时,旧版本还是会被下载和解压,但实际上这些文件是容器不再须要的了。当然咱们能够认为这是因为镜像优化做的不够好,但在简单场景下却很难避免出现这样的问题。

第四,镜像 Hash 可能保障镜像在上传和下载时候的不可变,但在镜像被解压落盘后,很难保障运行时数据不被篡改,这也就意味着运行时的数据是不可信的。

第五,镜像是以层为根本存储单位,数据去重是通过层的 Hash,这也导致了数据去重的粒度较粗。从整个 Registry 存储上看,镜像中的层与层之间,镜像与镜像之间存在大量反复数据,占用了存储和传输老本。

镜像设计应该如何改良

咱们看到了 OCI 镜像设计的诸多问题,在大规模集群场景下,存储与网络负载压力会被放大,这些问题的影响尤为显著,因而镜像设计急需从格局、构建、散发、运行、平安等各方面做优化。

首先,咱们须要实现按需加载。 在容器启动时,容器内业务 IO 申请了哪些文件的数据,咱们再从远端 Registry 拉取这些数据,通过这种形式,能够防止镜像大量数据拉取阻塞容器的启动。

其次,咱们须要用一个索引文件记录某个文件的数据块在层的 Offset 偏移地位。 因为当初的问题是,Tar 格局是不可寻址的,也就是说须要某个文件时,只能从头程序读取整个 Tar 流能力找到这部分数据,那么咱们天然就想到了能够用这种形式来实现。

接着,咱们革新层的格局以反对更简略的寻址。 因为 Tar 是会被 Gzip 压缩的,这导致了就算晓得 Offset 也比拟难 Unzip。

咱们让原来的镜像层只存储文件的数据局部 (也就是图中的 Blob 层)。Blob 层存储的是文件数据的切块 (Chunk),例如将一个 10MB 的文件,切割成 10 个 1MB 的块。这样的益处是咱们能够将 Chunk 的 Offset 记录在一个索引中,容器在申请文件的局部数据时,咱们能够只从远端 Registry 拉取须要的一部分 Chunks,如此一来节俭不必要的网络开销。

另外,按 Chunk 切割的另外一个劣势是细化了去重粒度,Chunk 级别的去重让层与层之间,镜像与镜像之间共享数据更容易。

最初,咱们将元数据和数据拆散 这样能够避免出现因元数据更新导致的数据层更新的状况,通过这种形式来节俭存储和传输老本。

元数据和 Chunk 的索引加在一起,就组成了上图中的 Meta 层,它是所有镜像层重叠后容器能看到的整个 Filesystem 构造,蕴含目录树结构,文件元数据,Chunk 信息等。

另外,Meta 层蕴含了 Hash 树以及 Chunk 数据块的 Hash,以此来保障咱们能够在运行时对整颗文件树校验,以及针对某个 Chunk 数据块做校验,并且能够对整个 Meta 层签名,以保障运行时数据被篡改后仍然可能被查看进去。

如上所述,咱们在 Nydus 镜像格局中引入了这些个性,总结下来如下:

镜像元数据和数据拆散,用户态按需加载与解压;

更细粒度的块级别数据切割与去重;

扁平化元数据层 (移除中间层),间接出现 Filesystem 视图;

端到端的文件系统元数据树与数据校验。

PART. 2

Nydus 解决方案

镜像减速框架

Nydus 镜像减速框架是 Dragonfly[7] (CNCF 孵化中我的项目) 的子项目。它兼容了目前的 OCI 镜像构建、散发、运行时生态。Nydus 运行时由 Rust 编写,它在语言级别的安全性以及在性能、内存和 CPU 的开销方面十分有劣势,同时也兼具了平安和高可扩展性。

Nydus 默认应用用户态文件系统实现 FUSE[8]来做按需加载,用户态的 Nydus Daemon 过程将 Nydus 镜像挂载点作为容器 RootFS 目录。当容器产生 read (fd, count) 之类的文件系统 IO 时,内核态 FUSE 驱动将该申请退出解决队列,用户态 Nydus Daemon 通过 FUSE Device 读取并解决该申请,从远端 Registry 拉取 Count 对应数量的 Chunk 数据块后,最终通过内核态 FUSE 回复给容器。

Nydus 减速框架反对了三种运行模式,以反对不同场景下的镜像按需加载:

1. 通过 FUSE 提供给 RunC 这类容器运行时的按需加载能力,也是 Nydus 目前最罕用的模式;

2. 通过 VirtioFS[9]承载 FUSE 协定,让基于 VM 的容器运行时,例如 Kata 等,为 VM Guest 里的容器提供 RootFS 按需加载能力;

3. 通过内核态的 EROFS[10]只读文件系统提供 RootFS,目前 Nydus 的 EROFS 格局反对曾经进入了 Linux 5.16 主线,其内核态缓存计划 erofs over fscache 也曾经合入 Linux 5.19-rc1 主线,内核态计划能够缩小上下文切换及内存拷贝开销,在性能有极致要求的状况下能够用这种模式。

在存储后端侧,Nydus 能够接各种 OCI Distribution 兼容的 Registry,以及间接对接对象存储服务例如 OSS,网络文件系统例如 NAS 等。它也蕴含了本地 Cache 能力,在数据块从远端拉取下来后,它会被解压并存储到本地缓存中,以便在下一次热启动时提供更好的性能。

另外除了近端本地 Cache,它也能够接 P2P 文件散发零碎 (例如 Dragonfly) 以减速块数据的传输。同时,它也可能最大水平升高大规模集群下的网络负载以及 Registry 的单点压力,理论场景测试在有 P2P 缓存的状况下,网络提早可能升高 80%  以上。

从这张图的基准测试能够看到,OCI 镜像容器的端到端冷启动工夫 (从 Pod 创立到 Ready) 随着镜像尺寸增大,耗时越来越越长,但 Nydus 镜像容器始终保持安稳,耗时在 2s 左右。

镜像场景性能优化

目前仅在蚂蚁的落地场景下,都有每日百万级别的 Nydus 减速镜像容器创立,它在生产级的稳定性和性能方面失去了保障。在如此大规模的场景考验下,Nydus 在性能,资源耗费等方面做了诸多优化。

镜像数据性能方面,Rust 实现的运行时 (nydusd) 自身曾经在内存及 CPU 方面做到了低开销。影响 Nydus 镜像容器启动性能的次要负载是来自网络,因而除了借助 P2P 散发从就近节点拉取 Chunk 块数据外,Nydus 还实现了一层本地 Cache,曾经从远端拉取的 Chunk 会解压缩后缓存在本地,Cache 能够做到以层为单位在镜像之间共享,也能够做到 Chunk 级别的共享。

尽管 Nydus 能够配置集群内 P2P 减速,但按需加载在拉取每个 Chunk 时都可能会发动一次网络 IO。因而咱们实现了 IO 读放大,将小块申请合并在一起发动一次申请,升高连接数。同时 Dragonfly 也实现了针对 Nydus 的 Chunk 块级别的 P2P 缓存和减速。

另外,咱们通过观察容器启动时读取镜像文件的程序,可能剖析出拜访模式,从而在容器 IO 读数据前事后加载这部分数据 (预取),可能进步冷启动性能。与此同时,咱们通过在镜像构建阶段从新排布 Chunk 程序,可能进一步升高启动提早。

镜像元数据性能方面,例如对于一个几十 GB 大小且小文件较多的 Nydus 镜像,它的元数据层可能会达到 10MB 以上,如果一次性加载到内存中会十分不合算。因而咱们革新了元数据结构,让它也实现了按需加载 (ondisk mmap),对函数计算这种内存敏感场景十分有用。

除了在运行时优化性能以外,Nydus 在构建时还做了一些优化工作。在少数场景下相比 Tar Gzip 格局的 OCI 镜像,Nydus 镜像层导出工夫优化到比其快 30%,将来指标是优化到 50% 以上

不止于镜像减速

这些优化伎俩足以应答镜像减速场景,但 Nydus 不止能利用在镜像的减速上,它也正在演进为一个能够在其余畛域同样实用的通用散发减速框架。总体出现如下:

1. Nydus 除了原生集成 Kata 平安容器外,在函数计算场景,例如阿里云的代码包减速以及 Serverless 场景,Runtime 镜像筹备的冷启动耗时通过 Nydus 从 20s 升高到了 800ms

2. 软件依赖包治理场景,例如前端 NPM 包,在装置阶段有大量的小文件须要解压落盘。但小文件 IO 十分影响性能,通过 Nydus 能够实现免解压,蚂蚁的 TNPM 我的项目[11] 为 Nydus 减少了 macOS 平台反对,将原生 NPM 的装置速度从 25s 升高到了 6s

3. 在镜像数据化场景,咱们通过算法剖析业务镜像之间的 Chunk 类似度,通过结构 Nydus Chunk 字典镜像,升高了业务疾速迭代导致的 50% 以上的存储耗费,将来还会通过机器学习,帮忙业务进一步优化镜像尺寸。

文件系统可扩展性

业界也有基于用户态块设施的镜像减速方案设计 (自定义块格局 > 用户态块设施 > 文件系统)。通过下面的介绍能够发现,Nydus 无论是 FUSE 用户态模式还是内核态 EROFS 模式,都是基于文件系统而非块设施,这样的设计使得 Nydus 无论是在构建还是运行时,都能够很容易地拜访到文件级别的数据信息。这种人造能力为许多其余场景提供了可能,例如:

1. 在平安扫描场景,无需把整个镜像下载解压,就能事后通过剖析元数据,发现其中的高危软件版本,再通过按需读取文件内容,扫描发现敏感与不合规数据,极大进步镜像扫描速度;

2. 镜像文件系统优化,通过 trace 运行时文件拜访申请,告知用户拜访过哪些文件,执行过哪些程序,这些记录能够提供给用户帮忙优化镜像大小,提供给平安团队帮忙审计可疑操作,提供给镜像构建阶段优化排布,以进步运行时预读性能等;

3. 运行时通过 hook 文件拜访申请,拦挡高危软件执行,阻断敏感数据读取,实现业务无感的破绽资源替换与热修复;

端到端的内核态计划

Nydus 在晚期齐全是一个用户态实现,但为了适应极致性能场景下的需要,例如函数计算与代码包场景,咱们又将按需加载能力下沉到了内核态。相比于 FUSE 用户态计划,内核态实现能够缩小随机小 I/O 拜访造成的大量零碎调用开销,缩小 FUSE 申请解决的用户态与内核态的上下文切换以及内存拷贝开销。

依靠于内核态 EROFS (始于 Linux 4.19) 文件系统,咱们对其进行了一系列的改良与加强,拓展其在镜像场景下的能力,最终出现为一个内核态的容器镜像格局——Nydus RAFS (Registry Acceleration File System) v6,相比于此前的格局,它具备块数据对齐,元数据更加精简,高可扩展性与高性能等劣势。

如上所述,在镜像数据全副下载到本地的状况下,FUSE 用户态计划会导致拜访文件的过程频繁陷出到用户态,并波及内核态 / 用户态之间的内存拷贝。因而咱们更进一步反对了 EROFS over fscache 计划  (Linux 5.19-rc1)

当用户态 nydusd 从远端下载 Chunk 后会间接写入 fscache 缓存,之后容器拜访时,可能间接通过内核态 fscache 读取数据,而无需陷出到用户态,在容器镜像的场景下实现简直无损的性能和稳定性。其体现优于 FUSE 用户态计划,同时与原生文件系统 (未应用按需加载) 的性能相近。

目前 Nydus 在构建、运行、内核态  (Linux 5.19-rc1)  均已反对了该计划,具体用法能够参见 Nydus EROFS fscache user guide[12],另外想理解更多 Nydus 内核态实现细节,能够参见 Nydus 镜像减速之内核演进之路[13]。

PART. 3

Nydus 生态系统与将来

Nydus 兼容了目前的 OCI 镜像构建、散发、运行时生态,除了提供自有的工具链外,Nydus 与社区支流生态做了兼容与集成。

Nydus 工具链

Nydus Daemon (nydusd[14]):Nydus 用户态运行时,反对 FUSE,FUSE on VirtioFS 模式以及 EROFS 只读文件系统格局,目前也已反对 macOS 平台运行;

Nydus Builder (nydus-image[15]):Nydus 格局构建工具,反对从源目录 /eStargz TOC 等构建 Nydus 格局,可用于 OCI 镜像分层构建,以及代码包构建等场景,反对 Nydus 格局查看与校验;

Nydusify (nydusify[16]):Nydus 格局镜像转换工具,反对从源 Registry 拉取镜像并转换为 Nydus 镜像格局并 Push 到指标 Registry 或对象存储服务,反对 Nydus 镜像校验和远端缓存减速转换;

Nydus Ctl (nydusctl[17]):Nydus Daemon 管控 CLI,可用于查问 Daemon 状态,Metrics 指标以及运行时热更新配置;

Ctr Remote (ctr-remote[18]):增强版 Contianerd CLI (Ctr) 工具以反对间接拉取与运行 Nydus 镜像;

Nydus Backend Proxy (nydus-backend-proxy[19]):用于将本地目录映射为 Nydus Daemon 存储后端的 HTTP 服务,在没有 Registry 或对象存储服务的场景下可用;

Nydus Overlayfs (nydus-overlayfs[20]):Containerd Mount Helper 工具,它能够被用于基于 VM 的容器运行时,例如 Kata Containers 等。

Nydus 生态集成

Harbor (acceld[21]):由 Nydus 发动的镜像转换服务 Acceld,让 Harbor 原生反对 eStargz, Nydus 等减速镜像的转换;

Dragonfly (dragonfly):P2P 文件散发零碎,为 Nydus 实现了块级别的数据缓存与散发能力;

Nydus Snapshotter (nydus snapshotter[22]):Containerd 的子项目,以 Remote 插件机制为 Containerd 反对了 Nydus 容器镜像;

Docker (nydus graphdriver[23]):以 Graph Driver 插件机制为 Docker 反对了 Nydus 容器镜像;

Kata Containers (kata containers[24]):Nydus 为 Kata 平安容器提供原生的镜像减速计划;

EROFS (nydus with erofs[25]):Nydus 兼容 EROFS 只读文件系统格局,能够内核态形式间接运行 Nydus 镜像,晋升极限场景下的性能;

Buildkit (nydus compression type[26]):从 Dockerfile 间接导出 Nydus 格局镜像。

Nydus 将来方向

在逐步推进上游生态,扩大应用领域的同时,Nydus 也在进一步从性能,平安等如下几个方向上做了更多的摸索:

1. Nydus 目前曾经反对了内核态 EROFS 只读文件系统,咱们将进一步在性能、原生集成方面做更多工作;

2. 目前 Nydus 在大部分场景下导出速度比 OCIv1 Tar Gzip 更快,接下来咱们会让构建也实现按需加载,例如容许 Base 镜像指定为 Nydus 镜像,在做 Dockerfile 构建时就不须要先把整个 Base 镜像拉取下来,进一步提高构建速度;

3. 咱们在用机器学习办法剖析镜像与镜像间乃至整个镜像核心存储,利用运行时拜访模式分析等伎俩进一步优化镜像数据去重效率,升高存储,进步运行时性能;

4. 与各大镜像平安扫描框架单干,原生反对更快的镜像扫描,反对在运行时拦挡高危软件执行,阻断高危读写,业务无感的破绽热修复与资源替换;

5. 除了按需加载外,Nydus 还能够解决海量小文件 IO 性能问题,蚂蚁行将开源的前端 tnpm 我的项目曾经实际了计划,咱们在思考拓展到更多的场景。

Nydus 相较于社区其余按需加载计划,它在镜像场景为性能优化与低资源开销做了诸多工作,并且拓宽了按需加载技术在镜像扫描与审计,以及在非镜像场景着落地的可能性。

如题目所言,尽管它不肯定代表了容器镜像的将来,但想必它也能为将来容器镜像在格局设计,优化方向,实际思路等方面提供具备外围竞争力的参考。Nydus 秉承了开源也凋谢的理念,期待着有更多的社区一起参加,为容器技术的将来奉献本人的力量。

Nydus 网站:https://nydus.dev/

深刻 Nydus,与咱们一起摸索~

理解更多 …

Nydus Star 一下✨:
https://github.com/dragonflyoss/image-service

【参考链接】

[1]Image Manifest V1:https://github.com/moby/moby/tree/master/image/spec

[2]V2 Scheme 1:https://docs.docker.com/registry/spec/manifest-v2-1/

[3]V2 Scheme 2:https://docs.docker.com/registry/spec/manifest-v2-2/

[4]Whiteout:https://github.com/opencontainers/image-spec/blob/main/layer.md#representing-changes

[5]Manifest:https://github.com/opencontainers/image-spec/blob/main/manifest.md

[6]《Slacker Fast Distribution with Lazy Docker Containers》:https://www.usenix.org/conference/fast16/technical-sessions/presentation/harter

[7]Drafonfly:https://d7y.io/

[8]FUSE:https://www.kernel.org/doc/html/latest/filesystems/fuse.html

[9]VirtioFS:https://virtio-fs.gitlab.io/

[10]EROFS:https://www.kernel.org/doc/html/latest/filesystems/erofs.html

[11]TNPM:https://dev.to/atian25/in-depth-of-tnpm-rapid-mode-how-could-we-fast-10s-than-pnpm-3bpp

[12]《Nydus EROFS fscache user guide》:https://github.com/dragonflyoss/image-service/blob/fscache/docs/nydus-fscache.md

[13]《Nydus 镜像减速之内核演进之路》:https://mp.weixin.qq.com/s/w7lIZxT9Wk6-zJr23oBDzA

[14]Nydusd:https://github.com/dragonflyoss/image-service/blob/master/docs/nydusd.md

[15]Nydus Image:https://github.com/dragonflyoss/image-service/blob/master/docs/nydus-image.md

[16]Nydusify:https://github.com/dragonflyoss/image-service/blob/master/docs/nydusify.md

[17]Nydus Ctl:https://github.com/dragonflyoss/image-service/blob/master/docs/nydus-image.md

[18]Ctr Remote:https://github.com/dragonflyoss/image-service/tree/master/contrib/ctr-remote

[19]Nydus Backend Proxy:https://github.com/dragonflyoss/image-service/blob/master/contrib/nydus-backend-proxy/README.md

[20]Nydus Overlayfs:https://github.com/dragonflyoss/image-service/tree/master/contrib/nydus-overlayfs

[21]Acceld:https://github.com/goharbor/acceleration-service

[22]Nydus Snapshotter:https://github.com/containerd/nydus-snapshotter

[23]Nydus Graphdriver:https://github.com/dragonflyoss/image-service/tree/master/contrib/docker-nydus-graphdriver

[24]Kata Containers:https://github.com/kata-containers/kata-containers/blob/main/docs/design/kata-nydus-design.md

[25]Nydus with EROFS:https://static.sched.com/hosted_files/kccncosschn21/fd/EROFS_What_Are_We_Doing_Now_For_Containers.pdf

[26]Nydus Compression Type:https://github.com/imeoer/buildkit/tree/nydus-compression-type

本周举荐浏览

GLCC 首届编程夏令营 高校学生报名正式开始!

Nydus 镜像减速插件迁入 Containerd 旗下

技术人聊开源|这并不只是用爱发电

蚂蚁团体 Service Mesh 停顿回顾与瞻望

正文完
 0