Dragonfly 社区技术分享|Nydus 远端转换缓存实现

9a2a5e6de94083669a5d52826545493b.gif

在 2023 年的开源活动中,于强同学参加并负责了 Nydus 开源存储构建与分发支持课题的相关工作。

大家好!我是来自西北工业大学的于强,在开源之夏 2023 中报名参与了 Nydus 项目,并在导师的帮助下顺利完成了题目。通过此次活动,不仅对容器和镜像的底层技术有了更进一步的了解,也体验了如何开始参与一个开源项目。

Nydus GitHub:

https://github.com/dragonflyoss/nydus

1

关于 Nydus & Acceleration Service

1.1 Nydus

Nydus 镜像加速框架[1] 项目是 CNCF[2] 开源项目 Dragonfly[3] 的子项目,它是对 OCI 镜像格式的探索改进, Nydus 提供了容器镜像与多种数据的按需加载的能力,它已在生产环境支撑了每日百万级别的容器创建,将容器或代码包的端到端冷启动时间从分钟级降低到了秒级。Nydus 目前由蚂蚁集团、阿里云、字节跳动联合研发,也是 Kata Containers 与 Linux 内核态原生支持的镜像加速方案。

1.2 Acceleration Service  

加速镜像格式和普通镜像格式不同,构建或转换步骤是必须的,可以使用 Buildkit[4] 从 Dockerfile 直接构建加速镜像,也可以使用 Nerdctl[5] 或 Nydusify[6] 转换工具。为了让 Harbor[7] 进一步支持用户透明地使用加速镜像, Harbor 的子项目 Acceleration Service[8] 诞生了,Acceleration Service 为 Harbor 提供了自动转换加速镜像的能力。

Acceleration Service 作为通用的加速镜像转换框架,提供了两种转换服务:

  • Acceld 是一个通过 Harbor Webhook[9] 扩展的服务,它是一个通用的加速镜像转换框架。当用户推送镜像时,Harbor 向该服务发送 Webhook 请求,通过其集成的 Nydus、eStargz[10] 等转换驱动完成镜像的转换。 

  • AccelCtl 是一个 CLI 服务,指定源镜像后同样通过集成的转换驱动完成一次加速镜像转换。 

Nydusify 是 Nydus 生态提供的工具,可用于转换、挂载、校验 Nydus 镜像,其中 Convert 子命令使用了  Acceleration Service 项目提供的 Go Package ,以实现从 Docker / OCI 镜像到 Nydus 镜像的转换。

2

Issue 描述

容器镜像是由 manifest 、config 和若干 layer 组成,容器镜像示意:

c69e83663d8fc6a2b1f16649dc290876.png

  • layer 文件一般是 tar 包或者压缩后的 tar 包,包含镜像数据文件,多个 layer 文件共同组成一个完整的根文件系统(也就是从该镜像启动容器后,进入容器中看到的文件系统)。 

  • config 文件是一个 JSON 文件,包含镜像的一些配置信息,比如镜像时间、修改记录、环境变量、镜像的启动命令等等。

  • manifest 文件也是一个 JSON 文件,可以看作是镜像文件的清单,标识了镜像包含了哪些 layer 文件和哪个 config 文件。

  • index 文件也是一个可选的 JSON 文件,可以被认为是 manifest 的 manifest ,用来索引多个不同架构平台的 image 镜像。

而不同的容器镜像之间可能有相同的镜像层,下图示意了通过 dockerfile 创建的过程,假如存在另一个镜像在构建的时候,以相同的 FROM golang:1.20-alpine 命令作为 base layer ,或者是使用已有的 base image ,那么这两个镜像便包含相同的 image layer 。

632f7e7cd27cab5274eeb1818a40ebda.png

当 Acceld 进行加速镜像转换时,事实上是需要通过 containerd 的 Converter[11] ,通过指定的转换驱动,对源镜像的每一层 layer、镜像的 manifest 和 config 进行转换,从而完成整个容器镜像的转换。

当前的问题是某些镜像可能存在相同的镜像层,对于已经转换过的镜像层,仍然会进行重复且不必要的镜像层转换,我需要为 Acceleration Service 提供转换缓存的功能并将缓存功能集成到 Nydusify 的 Convert 子命令中,使得对于某些已经转换后的镜像层不必再度转换。

在用户 Push 镜像后,Acceleration Service 创建加速镜像转换任务,它需要先拉取原始镜像数据,转换完成后将加速镜像 Push 回 Harbor ,Acceleration Service 需要支持本地与远端两种缓存机制:

  • 本地缓存:

    用于缓存源镜像层,避免相同镜像层的重复拉取;

  • 远端缓存:

    用于缓存转换后的镜像层,避免相同镜像层的重复转换。

3

我的工作

对于当前的镜像转换框架,我们将框架内的转换工作增添了拉取缓存、更新缓存、推送缓存的逻辑,并对原有的拉取源镜像、转换源镜像进行修改,使其可以跳过不必要的重复的镜像层,转换工作示意图如下:

2e9b3c6b898658c2e9f9475a711191d9.png

3.1  远端缓存存储实现

首先我们使用了 cache manifest 来存储在某个 platform 下的已转换过的镜像 layer 数据,随后使用 cache index 来存储多个 platform 下的 cache manifest ,缓存会伴随相同镜像,并以指定的 tag 后缀存入 Harbor 同一 repository 下(若不指定 tag ,则表示禁用缓存),下图为 nginx 镜像以及 nginx 缓存在 Harbor 中的存储形式。

7c41e20fb81c232beb5cfefe7333317f.png

cache image index,存储了不同 platform 的 cache manifest。cache image index 示例如下:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:4778928307d551db03197c8458c2d25f5c1fe4925e8a3da3f0516400cbf2234f",
      "size": 693,
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:e2924242732b3cf0fffa7c5af813f135d72fd2e3b05ab02b4e3f0190fbd6738e",
      "size": 693,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

cache manifest 中的 layer 层记录了某个 repository 在某个 platform 下的缓存层,其中 layer 的 Digest 记录了转换后的 layer 的 sha256 摘要值,这也是一个镜像层的唯一标识, layer 的 Annotation 中记录了 Layer 的一些附属信息,其中 containerd.io/snapshot/nydus-source-digest 字段记录了该层未被转换前的源镜像层, cache manifest 示例如下:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
    "size": 2
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.nydus.blob.v1",
      "digest": "sha256:a4003a6f4a4dccb6e2d535d6d047b12fbb1e5a447fb7ac8994e2a8e027a5608d",
      "size": 4299951,
      "annotations": {
        "containerd.io/snapshot/nydus-blob": "true",
        "containerd.io/snapshot/nydus-source-digest": "sha256:af09961d4a43b504efc76e38b50918977c28be73eeb8b926247783a00e8b9f2f"
      }
    }
  ]
}

3.2  Fetch,Update,Push cache

Remote Cache 作为单独的 module ,Converter 模块在转换过程前后可以使用以下接口来操作缓存:

  • Get 根据源镜像层 Digest 获取转换后镜像层 Digest

  • Set 向 cache 中增加某一条缓存记录信息

  • Update 更新 cache 中的缓存记录信息

  • Fetch 从远端拉取 cache manifest 并写入 cache 中

  • Push 将本地的缓存记录推送到 Harbor 远端

结合上面转换流程图,对于缓存的操作可以概括为:Fetch -> Convert -> Fetch -> Update -> Push。

  • Fetch (第一次) 拉取缓存,用于源镜像拉取与镜像转换服务;

  • Convert 通过 wrap 后的 Info 、 Update 等方法,在转换过程中使用缓存,避免无用的镜像层转换;

  • Fetch (第二次) 重新拉取缓存,避免本地转换时间久,远端缓存已被其他转换服务更新;

  • Update 根据重新拉取的缓存和在转换过程中新增加的缓存记录,通过 LRU 的淘汰策略,更新缓存记录,示意如下:

cde1ceb334954996139527ed7f293f3f.png

  • Push 将更新后的缓存推送到远端。

3.3  Fetch Source Image and Convert with Cache

3.3.1 避免相同镜像层拉取

在拉取源镜像之前,Converter 会先尝试拉取同一 repository 下的缓存,若存在则从远端 fetch config 中指定的 platform 的 cache manifest ,并通过 Set 接口,将缓存记录存入本地,随后通过 wrap 后的 InfoUpdate 方法,得到源镜像层以及转换后 Nydus 的镜像层在本地已经存在的“假象”。

对源镜像层的拉取是分层进行,在拉取源镜像之前, containerd 会先检查本地是否已经存在需要拉取的镜像层,若存在则不拉取,借由缓存提供源镜像层已经存在的“假象”,拉取缓存可以避免重复的拉取。

3.3.2 避免相同镜像层重复转换

在 nydus snapshotter[12] 转换过程中,我们可以通过 wrap 后的 Info 方法来检查某个 layer 是否已经被转换过了,具体方法则是通过 Info 方法获取某个源镜像层的信息,随后检查镜像层的 label,如果存在 containerd.io/snapshot/nydus-source-digest 这样的 label ,表示该镜像层存在转换后的 Nydus 镜像层缓存,不必重新进行转换,随后直接通过 Info 方法得到的镜像层信息,返回转换后的 descriptor ,达到镜像已经完成转换的"假象",避免重复转换。

3.3.3 转换性能数据

下表是 wordpress 镜像前后两次转换的性能数据,第二次是基于 wordpress 镜像添加增量层(几 MB 数据)构建的新镜像,没有本地缓存,只命中远端缓存的情况:

c1729a192a3dacb63689279aec52142d.png

3.4 检查 filesystem 相同

Nydus 提供了两种不同的文件系统格式(rafs v5 & v6 ),若是在转换过程中检查到了不同的 fs version,则会触发 merge boostrap 失败,此时我们需要以不使用缓存进行 Nydus 加速图像转换,对此我们在设定了错误类型与返回码,并在 check_compatibility 函数检查是否 fs version 是否相同, Acceld 服务检查返回码来确定是否需要再次进行不使用缓存的转换。

4

总结

开源之夏截止至今, 我对 Nydus 社区已经有了一定的了解, 在这段时间中, 我能够清晰的感觉到自身在以下方面有了新的认知。

  • 代码规范的重要性

以前我对于代码规范性虽然有所了解,但是并没有太多深刻理解,觉得自己只要不随便命名,写上注释便足够,但是这段时间的学习与开发,特别是 mentor 对于我 PR 的 review 意见,让我深深意识到了我在这方面的缺陷,导师在 review 时会考虑代码规范性的方方面面,甚至包括 comment 是否足够清晰明了,我觉得这是我需要完善的第一步。 

  • 学会沟通

沟通在这段时间内显得非常重要,同时也感谢导师在这段时间内对我的悉心指导,因为我并不是非常熟悉 Go/Rust ,有些时候问题甚至会显得有些过于低级,但是导师仍然一一解答,并尽量抽时间为我 review 代码,同时这段时间的开发,让我更加熟悉了容器镜像格式, OCI 标准以及 containerd 等知识,让我以前非常松散的容器知识结构更加丰富饱满。

  • 如何与其他人协同工作

之前我在协同工作的时候,对于项目代码的维护并不规范。这种无序的协同会阻碍程序的开发,有的时候我们自己甚至需要回滚整个项目。这段时间的学习与经历,让我清晰地了解到,规范的项目是如何维护的,如何使用 git 更加高效的管理项目与功能的开发、修复,以及出现了问题如何与他人沟通,这对于我来说弥足珍贵。 

参考链接

[1].Nydus 镜像加速框架

https://www.cncf.io/

[2].CNCF

https://nydus.dev/

[3].Dragonfly

https://d7y.io/

[4].Buildkit

https://github.com/moby/buildkit/blob/master/docs/nydus.md

[5].Nerdctl 

https://github.com/containerd/nerdctl

[6]Nydusify

https://github.com/dragonflyoss/nydus/blob/master/docs/nydusify.md

[7].Harbor

https://goharbor.io/

[8].Acceleration Service

https://github.com/goharbor/acceleration-service

[9].WebHook 

https://goharbor.io/docs/1.10/working-with-projects/project-configuration/configure-webhooks/

[10].eStargz

https://github.com/containerd/stargz-snapshotter/blob/main/docs/estargz.md

[11].Converter

https://github.com/containerd/containerd/blob/main/images/converter/default.go

[12].Nydus-Snapshotter 

https://github.com/containerd/nydus-snapshotter

   推荐阅读   

d9ce49dd7d7b0ebcd482a678fecc2480.jpeg

Dragonfly 社区技术分享|Nydus Registry 鉴权与 Mount 改进

380a942648779e7ab6717c1a27fae9ff.jpeg

火山引擎基于 Dragonfly 加速实践

71a2d867c88520794eafa71ff2c13af0.png

Dragonfly 中 P2P 传输协议优化

ed6be16052e093caefb89c60c94bb0b5.png

Dragonfly 基于 P2P 的文件和镜像分发系统

587ff343997f0f70ab52d1ab1ce2d474.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值