如何打包 OCI 到 WebAssembly?

解读

在国内云原生面试场景里,面试官问“如何把 OCI 镜像打成 WebAssembly”并不是想听你罗列 wasm 指令,而是考察三条线:

  1. 是否理解 OCI 镜像规范WASM 模块 在格式、运行时、安全模型上的本质差异;
  2. 是否掌握 Rust 工具链(cargo、wasm32-wasi target、wasm-opt)与 容器生态(buildah、docker、crictl)的衔接点;
  3. 是否能把“编译-优化-打包-推送-运行”做成一条 可落地 CI/CD 流水线,并解释每一步的国产化合规细节(如使用国内镜像源、SBOM 生成、签名验签)。
    回答时务必先给出“最小可验证路径”,再补充“生产级加固方案”,让面试官听到“能跑”也听到“敢跑”。

知识点

  • OCI Image Spec:layers、config.json、manifest.json 的语义;
  • WASM 核心规范:二进制格式、导入段、导出段、start function;
  • WASI Preview1:fd_write、environ_get 等系统接口与宿主机能力映射;
  • wasm32-wasi target:Rust 编译开关、panic=abort、strip 符号、opt-level=z;
  • wasm-opt / wasm-strip:二进制体积压缩与危险段裁剪;
  • crun+wasmtime 插件:国内阿里云 ACK-Terway、华为云 CCE 已内置,符合 GB/T 38674-2020 容器安全要求
  • oci-artifact 类型:mediaType 为 application/vnd.wasm.content.layer.v1+wasm,避免被 docker hub 误判为 linux/amd64;
  • cosign + 国密 SM2:镜像签名合规方案;
  • SBOM 生成工具:syft + 国内源替换,满足信创审计。

答案

“我习惯把流程拆成五步,全部用 Rust 官方工具链和国内源完成,CI 跑在 GitLab 自建实例上。”

  1. 编译
    在 Cargo.toml 里把 crate-type 设为 ["cdylib"],release profile 打开

    panic = "abort"
    opt-level = "z"
    lto = true
    strip = true
    

    执行

    cargo build --target wasm32-wasi --release
    

    得到初版 app.wasm。

  2. 优化
    用国内镜像拉 wasm-opt 容器:

    reg.cn-beijing.aliyuncs.com/open-source/wasm-opt:latest \
    -Os --strip-debug --strip-producers -o app-opt.wasm app.wasm
    

    体积通常再降 30 %,去除 producers section 可防止供应链溯源泄露 CI 路径

  3. 构造 OCI 兼容层
    新建空目录 blobs/sha256,把 app-opt.wasm 重命名为其 sha256 值,再写 config.json:

    {
      "architecture": "wasm32",
      "os": "wasi",
      "rootfs": { "type": "layers", "diff_ids": ["sha256:xxx"] }
    }
    

    然后写 manifest.json,mediaType 固定为

    application/vnd.oci.image.manifest.v1+json
    

    并把 layer 的 mediaType 写成

    application/vnd.wasm.content.layer.v1+wasm
    

    这样 crun 识别到非 linux 架构会直接走 wasmtime,不会误用 runc

  4. 打包并推送
    用 buildah 免 Docker Daemon:

    buildah manifest create myapp:wasm
    buildah add myapp:wasm app-opt.wasm
    buildah commit myapp:wasm
    buildah push myapp:wasm \
      docker://registry.internal.company.com/team/myapp:wasm
    

    国内网络加 --tls-verify=false 时,务必在内网 Harbor 打开“镜像签名强制检查”,否则等保 2.0 测评会被扣分。

  5. 运行
    在 Kubernetes 1.28+ 的节点上,containerd 配置

    [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.wasm]
      runtime_type = "io.containerd.wasmtime.v1"
    

    Pod 描述里把 runtimeClassName 设为 wasm,镜像拉取策略 IfNotPresent。
    拉起后 kubectl logs 可直接看到 println! 输出,因为 wasmtime 把 fd1 重定向到 containerd 的 log pipe。

整个流水线我写成 GitLab CI 模板,平均 25 秒出包,编译通过即正确,未出现一次运行时 panic;同时用 syft 生成 SPDX 格式的 SBOM,cosign 做 SM2 签名,满足公司信创验收要求。

拓展思考

  1. 多语言混合:如果业务里出现 Go 编译的 wasm 与 Rust 的 wasm 需要同进程互调,可用 wasmedge 的 go-sdk,但注意 cgo 会引入 libc,体积膨胀 2 MB 以上,需要再跑一遍 wasm-opt 的 --dce pass。
  2. sidecar 模式:在 Service Mesh 场景,把 wasm 当 filter 打进 istio-proxy,国内金融客户要求 国密 TLS 算法在 wasm 内完成,此时得把 rust-crypto 的 sm2 分支静态编进模块,并打开 -C target-feature=+bulk-memory 提升大报文性能。
  3. 冷启动 vs 安全:wasmtime 的 ahead-of-time 编译可把模块预编译成 cranelift 本地码,启动时间从 80 ms 降到 8 ms,但 AOT 缓存文件需做完整性度量,可用 dm-verity 或国内可信计算芯片做哈希锚定,否则等保三级测评会被判为“不可信缓存”。