BuildKit 构建 Nydus 按需拉取加速镜像:从 buildkitd 编译到镜像导出全指南
2026/9/15 12:21:03 网站建设 项目流程

BuildKit 构建 Nydus 按需拉取加速镜像:从 buildkitd 编译到镜像导出全指南

【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit

Nydus 是一种由 Dragonfly 社区 image-service 项目提出的、兼容 OCI/Docker 规范的容器镜像加速格式,其核心价值是让容器运行时按需拉取镜像数据,不必等整个镜像下载完成即可启动容器。本文以 BuildKit 当前仓库为蓝本,完整讲解如何编译带 Nydus 支持的buildkitd、通过buildctl导出 Nydus 镜像,并从源码层面剖析导出链路中"额外 metadata 层"的产生机制与各项已知限制,最终交付一套可直接落地的 Nydus 镜像构建实战方案。

一、Nydus 镜像格式:为什么需要"按需拉取"

1.1 传统镜像拉取方式的痛点

在使用普通 OCI 镜像时,容器运行时的启动流程通常是:先拉取全部镜像层并解压到本地,然后才挂载文件系统、启动容器。当镜像体积大、网络带宽有限时,镜像拉取和解压会显著拉长容器冷启动时间,同时占用大量磁盘 IO 与临时磁盘空间。

1.2 Nydus 的解决思路:blob + bootstrap 分层

Nydus 将镜像内容拆分为两类数据(参见 cache/compression_nydus.go 中MergeNydus的注释与实现):

  • 数据层(nydus blob):镜像的真实文件内容按 chunk 切分后存放,拉取时可做到"用到哪块拉哪块",即按需拉取(on-demand pull)。
  • 元数据层(nydus bootstrap):描述整个文件系统视图的元数据(目录结构、文件属性、chunk 索引等),体积很小。

容器启动时只需要先拿到体积小巧的 bootstrap 元数据即可建立文件系统视图,实际文件数据在访问时才按需从远端拉取,因此无需等待完整镜像下载完成就能启动容器。按 docs/nydus.md 的描述,Nydus 已投入生产使用,并在拉取镜像、启动容器的时间、网络与磁盘 IO 开销上带来了显著改善。

1.3 运行时形态:FUSE 用户态文件系统与内核 EROFS

Nydus 镜像可以灵活地以两种形态被消费:

  • FUSE 用户态文件系统:通过用户态的 nydus daemon 提供文件系统能力,部署灵活、不依赖内核版本。
  • 内核 EROFS(Enhanced Read-Only File System):从 Linux 内核 v5.16 起,Nydus 可以直接以内核 EROFS 文件系统形态挂载,用户态只需运行 nydus daemon 配合。由于 EROFS 是只读压缩文件系统且具备内核对文件系统的直接支持,性能开销更低。

由于 nydus daemon 运行在用户态,与 KataContainers 这类基于 VM 的容器运行时集成也变得更加容易:可以把 nydus daemon 一同放进 guest 虚拟机中,实现在 VM 场景下的按需拉取。

二、环境准备:编译带 Nydus 支持的 buildkitd

2.1 build tag 机制:Nydus 是编译期特性

BuildKit 通过 Go 的构建标签(build tag)将 Nydus 支持作为编译期可选特性注入。这一点在源码中非常清晰:

  • util/compression/nydus.go 以//go:build nydus开头,定义了完整的nydusType压缩类型实现(CompressDecompressNeedsConversionOnlySupportOCITypesMediaType等)。
  • util/compression/parse.go 则以//go:build !nydus开头,是未开启该特性时的"空实现"。

也就是说,不携带nydustag 编译出的buildkitd根本不包含 Nydus 压缩类型,导出时会直接走默认压缩逻辑。

2.2 编译命令

在 BuildKit 仓库根目录执行(参考 docs/nydus.md):

go build -tags=nydus -o ./bin/buildkitd ./cmd/buildkitd

编译完成后,./bin/buildkitd即为带 Nydus 导出能力的守护进程。除buildkitd外,其它导出路径(如 exporter/containerimage/patch_nydus.go、cache/compression_nydus.go、util/winlayers/apply_nydus.go)也均由nydustag 控制,与默认的非 Nydus 版本(如 exporter/containerimage/patch.go)形成一一对应。

2.3 准备 nydus-image 工具

导出 Nydus 镜像时,buildkitd 需要调用外部二进制nydus-image来完成数据打包,因此必须预先准备:

  1. 从 Nydus 官方 release 页面下载nydus-image二进制,要求版本 v2.1.6 及以上
  2. nydus-image放入$PATH,或通过NYDUS_BUILDER环境变量显式指定其路径:
env NYDUS_BUILDER=/path/to/nydus-image buildkitd ...

在 vendor 依赖 nydus-snapshotter 的 converter 实现中可以看到对应环境变量常量envNydusBuilderenvNydusWorkDir(位于 vendor 下 nydus-snapshotter 的pkg/converter/convert_unix.go),BuildKit 直接复用该套环境变量约定。另外还存在NYDUS_DISABLE_TAR2RAFS环境变量,可用于控制是否禁用 tar 到 rafs 的转换路径。

2.4 临时工作目录:NYDUS_WORKDIR

构建过程中,Nydus 转换会产生一些中间文件。默认情况下这些中间文件创建在当前工作目录,构建结束后会自动清理。如需改变中间文件存放位置,通过NYDUS_WORKDIR环境变量指定即可:

env NYDUS_WORKDIR=/var/tmp/nydus-build buildkitd ...

在 CI 或容器化部署 buildkitd 时,将NYDUS_WORKDIR指向有足够磁盘空间、且可写的独立目录是更稳妥的做法。

三、使用 buildctl 导出 Nydus 镜像

3.1 完整导出命令

在 buildctl 一侧,Nydus 被作为镜像导出时的一种压缩类型(compression type)来指定。将compression=nydusforce-compression=true组合,即可把构建产物导出为 Nydus 镜像:

buildctl build ... \ --output type=image,name=docker.io/username/image,push=true,compression=nydus,force-compression=true,oci-mediatypes=true

3.2 关键导出参数逐个拆解

参数取值作用
type=image固定使用镜像导出器(image exporter),产物为符合 OCI 规范的镜像并推送到 registry
namedocker.io/username/image目标镜像名(含 tag),将推送到该地址
push=true布尔导出完成后直接推送到 registry
compression=nydus压缩类型指定导出层的压缩/格式为 Nydus,BuildKit 将调用nydus-image生成 nydus blob 层并追加 bootstrap 层
force-compression=true布尔强制所有层统一使用指定压缩类型,禁止混合其它压缩类型(详见下文限制章节)
oci-mediatypes=true布尔使用 OCI media types(而非 Docker media types),是 Nydus 这种非 Docker 原生格式正常工作的前提

3.3 参数在源码中的落地

compression=nydus的解析入口在 util/compression/nydus.go 的Parse/FromMediaType函数:当内置的常规压缩类型解析失败、而目标字符串恰好是"nydus"(或 media type 是MediaTypeNydusBlob)时,返回compression.Nydus类型。该类型的关键行为包括:

  • Compress:调用nydusify.Pack将层内容打包为 nydus blob,并为层写入containerd.io/uncompressed标签与LayerAnnotationNydusBlob注解,用于标识该层是 nydus blob 层(util/compression/nydus.go);
  • Decompress:调用nydusify.Unpack将 nydus blob 还原为普通层内容;
  • OnlySupportOCITypes返回true,说明 Nydus 层只支持 OCI media types,这正对应导出时要求开启oci-mediatypes=true
  • NeedsComputeDiffBySelf返回true,表示该压缩类型需要由自身完成 diff 计算。

四、Nydus 导出流程的源码级原理

4.1 导出主流程:patchImageLayers

镜像导出器在组装 manifest 时会调用patchImageLayers(调用点在 exporter/containerimage/writer.go)。该函数存在两个版本:

  • 默认版本 exporter/containerimage/patch.go:仅做层与 history 的规范化(normalizeLayersAndHistory),不额外追加任何层;
  • Nydus 版本 exporter/containerimage/patch_nydus.go:当压缩类型是compression.Nydus时,先调用cache.MergeNydus生成最终的 bootstrap 描述符,并将其追加到 manifest 的层描述符末尾,然后再做层与 history 的规范化。

4.2 多一层的来源:MergeNydus 的两步合并

为什么 Nydus 镜像总是比其它压缩类型的镜像多一层?答案就在 cache/compression_nydus.go 的MergeNydus中:

  1. 提取 bootstrap:对每一层,从其 nydus 格式内容(nydus blob + nydus bootstrap)中取出对应的 bootstrap 元数据;
  2. 合并 bootstrap:将各层 bootstrap 合并成一个最终的 bootstrap,代表整个镜像文件系统视图的完整元数据,并以 tar.gz 形式写回 content store,作为镜像的额外一层追加到 manifest。

由于 bootstrap 体积非常小,合并操作很快(源码注释明确指出 "The nydus bootstrap size is very small, so the merge operation is fast")。合并产物层的描述符带LabelUncompressedLayerAnnotationNydusBootstrap注解,用于标识它是 nydus bootstrap 层。这正是 docs/nydus.md 中"导出的 Nydus 镜像总是比其它压缩类型多一个 metadata 层"这一说法的实现来源。

4.3 集成测试验证:三层 manifest 结构

仓库内置的 Nydus 集成测试 client/client_nydus_test.go(//go:build nydus)以 alpine 为基础镜像执行touch操作后导出,断言 manifest 恰好包含 3 层:

  • 第 1、2 层:携带LayerAnnotationNydusBlob注解的 nydus blob 层;
  • 第 3 层:携带LayerAnnotationNydusBootstrap注解的 nydus bootstrap 层。

测试还以compression.Gzipcompression.Zstd分别导出同样内容做对照,验证普通镜像为 2 层,且同一镜像内不会出现 Nydus 层与 gzip/zstd 层混合的情况。这条测试同时覆盖了"强制统一压缩类型"的约束,与文档中的限制说明一一对应。

五、已知限制与注意事项

根据 docs/nydus.md 的 "Known limitations" 章节,使用 BuildKit 导出 Nydus 镜像时需注意以下四点:

  1. 仅支持 Linux 平台:Nydus 镜像的导出以及运行时(如 docker-nydus-graphdriver、containerd nydus-snapshotter 等)目前只在 Linux 上受支持。Windows 平台虽有针对 nydus blob 的应用逻辑(util/winlayers/apply_nydus.go),但其定位是处理已有 nydus 层,而非导出。
  2. 不得混用压缩类型:同一镜像内的层不能被混合为不同压缩类型,因此同时导出 Nydus 与其它压缩类型时必须开启force-compression=true,强制全镜像统一压缩格式。
  3. 基础镜像支持但不支持懒加载:Dockerfile 中可以将 Nydus 镜像作为基础镜像(base image)使用,但目前不支持对基础镜像的懒拉取(lazy pulling)。
  4. 不能作为缓存导出/导入:由于导出的 Nydus 镜像总是比其它类型镜像多一个 metadata(bootstrap)层,与缓存(cache)的导出/导入结构不兼容,因此 Nydus 镜像不能作为 BuildKit 缓存导出或导入。

六、其它创建 Nydus 镜像的方式

除 BuildKit 直接导出外,还有两类常用途径(参见 docs/nydus.md):

  • 预转换镜像:Dragonfly 社区在ghcr.io/dragonflyoss/image-service仓库提供了一批预先转换好的 Nydus 镜像,主要用于测试验证 Nydus 运行时与按需拉取能力。
  • Nydusify CLI:Nydus 官方提供的命令行工具,负责拉取一个 OCIv1 镜像、转换为 Nydus 镜像并推送回 registry,适合对存量镜像做离线批量转换。
  • Harbor Acceld:Harbor 提供的通用加速服务,能够把 OCIv1 镜像统一转换为 Nydus、eStargz 等多种加速镜像格式,适合在 Harbor 镜像仓库侧做集中式转换。

这些方式与 BuildKit 直接导出形成互补:需要从 Dockerfile 构建新镜像时用 BuildKit;需要转换已有镜像时用 Nydusify 或 Harbor Acceld。

七、总结与最佳实践

围绕 BuildKit 使用 Nydus 加速镜像的完整链路可以归纳为:

  1. 编译go build -tags=nydus -o ./bin/buildkitd ./cmd/buildkitd,确保nydus构建标签生效;
  2. 准备工具:下载 v2.1.6 及以上版本的nydus-image,放入$PATH或通过NYDUS_BUILDER指定,必要时用NYDUS_WORKDIR隔离中间文件;
  3. 导出:buildctl 输出参数固定搭配compression=nydus,force-compression=true,oci-mediatypes=true,容器运行时侧配合 nydus daemon(FUSE 或内核 EROFS,Linux v5.16+)即可享受按需拉取带来的启动提速与网络/磁盘 IO 节省。

实践中最容易踩坑的三点:忘记携带nydus编译标签导致导出类型不识别、force-compression未开启导致层类型混用、以及在非 Linux 平台尝试导出。理解了 util/compression/nydus.go、cache/compression_nydus.go 背后的 blob/bootststrap 拆分与合并机制后,这些问题都能一眼定位。

【免费下载链接】buildkitconcurrent, cache-efficient, and Dockerfile-agnostic builder toolkit项目地址: https://gitcode.com/GitHub_Trending/bu/buildkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询