☰
Spin 应用通过 OCI Registry 分发:SIP 008 设计与源码实现深度解析
2026/10/8 1:45:17 网站建设 项目流程
  • 云原生
  • 微服务

【免费下载链接】spin

Spin is the open source developer tool for building and running serverless applications powered by WebAssembly.

项目地址:https://gitcode.com/gh_mirrors/spin1/spin
点击查看免费下载

导读

本文围绕 Spin 项目的改进提案 SIP 008 - Distributing Spin applications using OCI registries,完整梳理 Spin 应用从传统 Bindle 分发机制迁移到 OCI Registry 的动机、用户交互设计、OCI 镜像结构设计、认证方案与源码级实现细节。读完本文,你将掌握spin oci push、spin oci pull、spin oci login与spin up --oci背后的完整链路:如何把一个包含元数据、Wasm 模块和静态资源的 Spin 应用打包为多层 OCI artifact、如何复用 Docker 凭据、以及 Spin 如何用 locked application 作为配置对象从本地缓存中重建并运行应用。

背景:为什么 Spin 要引入 OCI Registry

自 Spin 首次发布以来,应用分发一直依赖 Bindle——一个最初为分发 WebAssembly 应用及配套文件而设计的实验性聚合对象存储项目。然而 Bindle 作为相对早期的项目,暴露了两个突出问题:

  • 没有托管版 Bindle 服务,用户分发 Spin 应用必须自行搭建并维护基础设施,这对最简单的应用来说也显著增加了复杂度;
  • 基础设施的横向扩容在 Bindle 项目中是尚未解决的难题。

与此同时,OCI(Open Container Initiative) 已成为打包与分发容器镜像的事实标准,且随着 OCI Artifacts 项目的推进,容器 Registry 的使用范围正扩展到越来越多的 artifact 类型。所有主流云厂商均提供托管 Registry 服务,其中不少已支持分发其他类型的 artifact。因此 SIP 008 提议:让 Spin 直接支持通过 OCI Registry 分发应用,一举解决上述两个问题:

  • 借助现成的托管服务(如 GitHub Container Registry、Docker Hub、AWS Elastic Container Registry、Azure Container Registry、Google Artifact Registry 等),用户无需自建分发基础设施;
  • 扩容问题交给托管服务解决;即便自建,容器 Registry 的横向扩容也有远比 Bindle 丰富的资源与方案。

提案核心:基于 OCI artifact 的 Spin 应用分发

SIP 008 的核心观点是:一个 Spin 应用由元数据 + 组件信息以及组成这些组件的Wasm 模块和静态资源构成。因此,一个 Spin 应用在概念上并不是“单一 artifact”,而是多个不同对象的集合。

基于此,提案设计:Spin 应用发布到 OCI Registry 后,将成为一个新的 OCIartifact,拥有多个层(layers)(注意 OCI 中image与artifact是两种不同实体)。具体约定如下:

实体说明
配置对象 media typeapplication/vnd.fermyon.spin.application.v1+config,承载 Spin 应用定义
Wasm 模块层每个组件的 Wasm 模块成为一个独立 layer(application/vnd.wasm.content.layer.v1+wasm)
静态资源层组件引用的每个静态资源也成为独立 layer(application/vnd.wasm.content.layer.v1+data)

正因为每个文件与每个 Wasm 模块都成为独立的 layer,Registry 可以高效地对应用进行去重(de-duplicate)与分发:多个应用共享同一 Wasm 模块或静态文件时,Registry 只需存储一份 blob。

应用定义用什么表示:locked application

剩下的关键问题是如何表示 Spin 应用定义。Spin 引入了locked application(锁定应用)这一内部表示——它是 Spin 应用的一种中间表示,能够以内容寻址(content-address)的方式引用 Wasm 模块与静态资源。SIP 008 的实现正是将 locked application 作为 OCI 配置对象(即 OCI manifest 中的config项)使用。

用户体验设计:push / pull / run 三命令

提案给出的目标用户体验非常直接:把应用推送到兼容 Registry、拉取到本地、然后运行:

$ spin oci push ghcr.io/<username>/my-spin-application:v1 INFO spin_publish::oci::client: Pushed "https://ghcr.io/v2/<username>/my-spin-application/manifests/sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b" $ spin oci pull ghcr.io/<username>/my-spin-application:v1 INFO spin_publish::oci::client: Pulled ghcr.io/<username>/my-spin-application:v1@sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b $ spin up --oci ghcr.io/<username>/my-spin-application:v1 INFO spin_publish::oci::client: Pulled ghcr.io/<username>/my-spin-application:v1@sha256:9f4e7eebb27c0174fe6654ef5e0f908f1edc8f625324a9f49967ccde44a6516b Serving http://127.0.0.1:3000

其中spin up --oci <reference>会在启动前自动完成拉取,然后基于本地缓存直接运行应用。

认证:复用 Docker 凭据 + 内置spin oci login

历史上dockerCLI 是与容器镜像、Registry 交互的主流工具链,因此提案要求spin oci能够复用已登录用户的 Docker 凭据,同时大多数容器 Registry 服务也都有基于docker login的登录说明。

但本地装有 Docker 不应成为使用 Spin 的前提。为此提案设计了spin oci login命令,让 Spin CLI 直接向目标 Registry 完成认证:

$ spin oci login --username <username> --password <password> # OR 通过 stdin 读取密码,避免密码出现在命令行历史中: $ echo $CONTAINER_REGISTRY_PASSWORD | spin oci login --username <username> --password-stdin

这一交互方式对标docker login命令。

源码印证:凭据的获取与存储优先级

从仓库源码看,认证逻辑已经落地在 crates/oci/src/client.rs 的Client::auth中,其凭据解析优先级为:

  1. 优先读取 Spin 自身的认证配置文件($XDG_CONFIG_HOME/fermyon/registry-auth.json,由 crates/oci/src/auth.rs 维护,auths字段以 registry server 为 key、以 base64 编码的username:password为 value);
  2. 读取失败则回退到 Docker 凭据(docker_credentialcrate),支持UsernamePassword形式;若 Docker 凭据是IdentityToken形式则退化为匿名认证;
  3. 兜底为匿名认证(RegistryAuth::Anonymous)。

此外Client::login(crates/oci/src/client.rs)在保存凭据前会先调用validate_credentials向 Registry 发送一次认证请求,提前校验凭据有效性,避免用户在第一次 push/pull 时才遇到认证错误。

从 Bindle 迁移到 OCI Registry

将分发机制从 Bindle 切换到 OCI 对项目而言是破坏性变更。为降低迁移成本,提案建议提供从 Bindle 迁移到 OCI Registry 的功能。考虑到该工具的“临时性”——其价值会随时间迅速衰减,且未来移除它会再次造成破坏性变更——该功能最适合做成一个 Spin 插件,按需安装、独立分发:

spin bindle2oci \ --bindle-server <server> \ --bindle-username <username> \ --bindle-password <password> \ --bindle <name> \ --oci <new-reference>

注意:该迁移工具在 SIP 008 成文时尚未开始实现(见下文“实现状态”)。

一个完整的实例:manifest、locked app 与本地缓存结构

提案用github-stars-webhook这个应用完整演示了从spin.toml到 OCI manifest、locked application,再到本地拉取缓存目录的完整形态。

应用定义:spin.toml

spin_version = "1" authors = ["Radu Matei <radu.matei@fermyon.com>"] description = "" name = "github-stars-webhook" trigger = { type = "http", base = "/" } version = "0.1.0" [[component]] id = "github-star-webhook" source = "target/spin-http-js.wasm" files = ["my-file.json"] allowed_http_hosts = ["https://hooks.slack.com"] [component.trigger] route = "/..."

该应用包含一个组件(github-star-webhook,Wasm 源为target/spin-http-js.wasm)与一个静态资源(my-file.json)。将其发布到 GitHub Container Registry:

$ spin oci push ghcr.io/radu-matei/spin-example:v1 INFO spin_publish::oci::client: Pushed "https://ghcr.io/v2/radu-matei/spin-example/manifests/sha256:8f86a27fbc457416701c4d18680083f598076d0a52dca2a5936e92754a845ed1" $ spin oci pull ghcr.io/radu-matei/spin-example:v1 INFO spin_publish::oci::client: Pulled ghcr.io/radu-matei/spin-example:v1@sha256:8f86a27fbc457416701c4d18680083f598076d0a52dca2a5936e92754a845ed1

拉取后的本地缓存目录结构

$ tree /Users/radu/Library/Application\ Support/fermyon/registry └── oci ├── data │ └── sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02 ├── manifests │ └── ghcr.io │ └── radu-matei │ └── spin-example │ └── v1 │ ├── config.json │ └── manifest.json └── wasm └── sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb

缓存目录按「Registry 名 / 仓库路径 / tag」组织 manifest,blob 则按 sha256 digest 存放在wasm(Wasm 模块)与data(静态资源)两个目录中。这与仓库中的实现完全对应:Cache::ensure_dirs(crates/loader/src/cache.rs)会创建<root>/registry/{manifests,wasm,data}三个子目录;Client::pull(crates/oci/src/client.rs)将 manifest 写入<cache_root>/registry/oci/manifests/<registry>/<repository>/<tag>/manifest.json,把 Wasm 层写入 wasm 目录、其他层(含归档层解包后)写入 data 目录。

OCI manifest:应用成为多层 artifact

查看manifest.json,可以看到 artifact 配置的顶层 media type 正是application/vnd.fermyon.spin.application.v1+config:

{ "schemaVersion": 2, "config": { "mediaType": "application/vnd.fermyon.spin.application.v1+config", "digest": "sha256:b36160facea3076ad136c09bd4975a429805945ad313b4674363841d5a7f66a0", "size": 643 }, "layers": [ { "mediaType": "application/vnd.wasm.content.layer.v1+wasm", "digest": "sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb", "size": 2147122 }, { "mediaType": "application/vnd.wasm.content.layer.v1+data", "digest": "sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02", "size": 178 } ] }

manifest 中出现了两个 layer:组件的 Wasm 模块层与组件引用的静态资源层。每新增一个组件,其 Wasm 模块与静态资源都会成为 manifest 中各自的独立 layer。层与配置对象之间的引用通过sha256digest 建立,这与仓库中assemble_layers系列函数的行为一致:push时先为每个组件的 source、每个依赖、每个 trigger 依赖以及每个静态资源文件生成独立ImageLayer,再以 locked app 序列化结果作为配置层(crates/oci/src/client.rs)。

OCI 配置对象:即 locked application

config.json正是 Spin 能够直接据以运行应用的 locked application manifest:

{ "spin_lock_version": 0, "metadata": { "description": "", "name": "github-stars-webhook", "trigger": { "base": "/", "type": "http" }, "version": "0.1.0" }, "triggers": [ { "id": "trigger--github-star-webhook", "trigger_type": "http", "trigger_config": { "component": "github-star-webhook", "executor": null, "route": "/..." } } ], "components": [ { "id": "github-star-webhook", "metadata": { "allowed_http_hosts": ["https://hooks.slack.com"] }, "source": { "content_type": "application/wasm", "digest": "sha256:55c29ad4b0ad0c6bd8ec1ffc8f04e63342e5901280037ef706b1b114475d3cbb" }, "files": [ { "digest": "sha256:a4699e4f9ef3f4922f38f0d017aa26438908f38caf020a739e0ee27fe796eb02", "path": "my-file.json" } ] } ] }

观察组件source与files中的digest字段——它们与 OCI manifest 中各 layer 的 digest 一一对应,即组件内容以内容寻址方式指向 Registry 中的实际 blob。这正构成了 Spin push、pull、run 全流程所需的全部信息:push 时锁定应用的引用,pull 时按 digest 取回内容,run 时从本地缓存还原应用。

这一结构在 crates/oci/src/loader.rs 的OciLoader::load_from_cache中得到印证:读取配置对象后,若包含spin_lock_version字段则将其解析为LockedApp,并把origin元数据写为vnd.fermyon.origin-oci:<reference>(由 crates/oci/src/lib.rs 定义的ORIGIN_URL_SCHEME);随后调用resolve_component_content_refs(crates/oci/src/loader.rs),将每个组件 source、依赖、trigger 依赖的 Wasm 内容替换为缓存中的 wasm 文件路径,把静态资源复制/落盘到临时挂载目录并更新files引用。

关于 Wasm 层 media type 的社区现状

提案明确提示:上述 media type 并非最终定稿,可能随社区对 OCI Registry 中 Wasm 模块的标准化而调整。文档列举了当时的几种既有方案:

  • wasm-to-oci与oci-distribution使用application/vnd.wasm.content.layer.v1+wasm;
  • solo-io/wasm/spec使用application/vnd.module.wasm.content.layer.v1+wasm;
  • Docker 的预览实现似乎以application/vnd.docker.container.image.v1+json分发。

简言之,当时尚无统一标准。倾向于为模块采用application/vnd.module.wasm.content.layer.v1+wasm的理由是未来可能引入components概念。仓库实现中同样保留了这一演进空间:Wasm 层 media type 被定义为常量WASM_LAYER_MEDIA_TYPE,并注释“一旦上游定义规范值将更新”(crates/oci/src/client.rs);拉取时 Wasm 层与数据层分别写入缓存的不同目录,且Cache::wasm_file在 wasm 目录缺失时会回退查找 data 目录(crates/loader/src/cache.rs),以兼容未来 media type 的变化。

spin oci push的设计:宽松的 tag 与版本策略

spin oci push的目标是让用户借助广泛可用的容器 Registry 服务分发应用,并尽可能灵活地与用户现有工作流集成,因此在版本号与 tag 可变性上刻意保持无意见(unopinionated)。

方式一:命令行显式指定引用

$ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:v1 # OR $ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:latest # OR $ spin oci push --file <path to spin.toml> myregistry.com/myusername/myapp:v0.1.0+r2d2

方式二:从spin.toml推导引用与 tag

当spin.toml中的应用名与版本构成完整引用时,无需在命令行重复指定:

name = "myregistry.com/myusername/myapp" version = "1.2.3"
$ spin oci push --file <path to spin.toml> ... Pushed the application to myregistry.com/myusername/myapp:v1.2.3 # OR 附加构建信息: $ spin oci push --file <path to spin.toml> --buildinfo ... Pushed the application to myregistry.com/myusername/myapp:v1.2.3+r2d2

注意:当未在命令行显式传值、由spin.toml推导引用与 tag 时,应用名必须包含完整限定的引用(fully qualified reference),且 tag 只能是语义化版本。

源码佐证:push 的内部流程

从源码看,Client::push(crates/oci/src/client.rs)的流程为:解析 reference → 获取认证 → 用spin_loader::from_file依据spin.toml构建 locked application → 对每个组件调用validate::ensure_wasms校验 source/dependency/trigger dependency 都是合法 Wasm → 组装层并推送。组装层时支持两种模式:

  • ComposeMode::All:推送前先用 crates/compose 对组件进行预组合(composition),依赖与 trigger 依赖合并进组件 Wasm,最终每组件一个层;
  • ComposeMode::Skip:跳过组合,依赖与 trigger 依赖(如 HTTP 中间件)各自成为独立层,避免发布结果仍指向推送机器的本地文件系统。

此外还有几项与推送强相关的实现细节:

  • 归档层:默认每文件一层(AssemblyMode::Simple);当层数超过MAX_LAYER_COUNT - 1(常量 500,对齐主流 Registry 的镜像层数上限,如 Elastic Container Registry 的 500 层限制)或设置环境变量SPIN_OCI_ARCHIVE_LAYERS时,改为AssemblyMode::Archive——组件的所有静态资源打包进单个application/vnd.wasm.content.bundle.v1.tar+gzip归档层(crates/oci/src/utils.rs 实现 tar.gz 打包/解包);
  • 小内容内联:小于DEFAULT_CONTENT_REF_INLINE_MAX_SIZE(128 字节)的内容直接内联进配置对象的ContentRef,不单独推送 blob,以规避部分 OCI 实现不支持极小 blob 的问题;
  • 层去重:assemble_layers对生成的层做unique()去重,多个组件共享同一 Wasm/文件时只推一份;
  • OCI 注解:除非显式关闭,推送时自动生成org.opencontainers.image.{authors,title,description,version,created}等预定义注解(显式注解优先),详见all_annotations(crates/oci/src/client.rs);
  • image config:构建 OCI image config 时写入architecture: wasm、os: wasip1,并通过 labelcom.fermyon.spin.lockedAppDigest引用 locked app 配置层的 digest,避免不同应用得到相同 image ID。

对spin deploy的影响

与spin oci push的灵活性不同,spin deploy属于有明确观点的意见化工作流,可以继续强制自己的 tag 可变性与版本策略。

提案采用渐进式迁移策略:

  1. 至少先发布一个包含spin oci的 minor 版本;
  2. 在 Fermyon Platform 与 Fermyon Cloud 支持接受 OCI 引用之后,先实现spin deploy --oci功能;
  3. 然后才把spin deploy的默认行为改为期望 OCI Registry,届时 Fermyon Platform 与 Fermyon Cloud 已同步支持该变更。

实现状态与 FAQ

SIP 008 成文时的实现状态如下:

  • spin oci push、spin oci pull、spin oci run已在原型 PR 中实现(可参考 crates/oci 模块,其中OciLoader同时支持加载应用与单个 Wasm 包两种 artifact);
  • 底层通过 Krustlet 的oci-distributioncrate 与容器 Registry 交互(当时使用一个待回馈上游的 fork);
  • loader 内部仍需进一步打磨方可合并;
  • Bindle → OCI 迁移工具尚未启动;
  • spin oci login当时尚未实现(其核心逻辑现已在 crates/oci/src/client.rs 与 crates/oci/src/auth.rs 中落地)。

FAQ 还回应了与 Bytecode Alliance Registry 项目(warg)的关系:Spin 维护者也是该 Registry 项目的创建者之一,该项目的目标之一是复用包括 OCI Registry 在内的既有存储作为后端;待其成熟后,Spin 计划将其作为分发机制之一。

关键源码索引

关注点位置
SIP 008 原始提案docs/content/sips/008-using-oci-registries.md
OCI 客户端(push/pull/login/认证)crates/oci/src/client.rs、crates/oci/src/auth.rs
OCI 加载器(pull 后重建可运行应用)crates/oci/src/loader.rs
归档/解包工具crates/oci/src/utils.rs
OCI 引用识别启发式(is_probably_oci_reference)crates/oci/src/lib.rs
本地缓存结构(manifests/wasm/data)crates/loader/src/cache.rs
组件预组合crates/compose/src/lib.rs

补充说明:SIP 008 属于早期设计提案,文中命令语法(如--file、--buildinfo)与最终 CLI 形态可能存在差异,使用时请以当前版本spin oci push --help的实际输出为准;提案中的参考链接(如原型 PR、oci-distribution)用于理解演进脉络,文中已改为仓库内源码证据。

  • 云原生
  • 微服务

【免费下载链接】spin

Spin is the open source developer tool for building and running serverless applications powered by WebAssembly.

项目地址:https://gitcode.com/gh_mirrors/spin1/spin
点击查看免费下载

相关推荐

上一篇:WarcraftHelper:让经典魔兽争霸3在现代电脑上焕发新生
下一篇:AMD Ryzen处理器终极调试指南:SMUDebugTool让你的硬件性能飞起来

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

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

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

立即咨询