jimyag's Blog

从 OCI 镜像到毫秒级 Sandbox:CubeSandbox 与 E2B 模板实现对比

容器镜像描述文件层和运行配置,MicroVM Sandbox 还需要内核、内存和设备状态。CubeSandbox 和 E2B 都会在构建阶段把 OCI 镜像转成 ext4 rootfs,预启动 MicroVM 并保存快照,但它们把成本放在了不同位置:

  • CubeSandbox 先把完整 ext4 分发到目标 Cubelet,再在每个节点本地制作快照。
  • E2B 把 rootfs、内存和 Firecracker 状态拆成可寻址对象,运行节点通过多级缓存按需取块。

分析范围

本文基于 CubeSandbox bebdf020 和 E2B infra 110fa5be。只分析开源代码可验证的行为,不把 E2B 托管服务的运营参数当成源码事实。共享 Volume 是另一条数据链,见《CubeSandbox 与 E2B 共享存储实现原理》。

从镜像到 Sandbox

1
2
3
4
5
6
7
OCI image
  ↓ 还原 layer,制作 ext4
rootfs
  ↓ 注入 agent/配置,启动 MicroVM
template snapshot = rootfs + memory + VM state + metadata
  ↓ 恢复并叠加独立 CoW
sandbox instance

OCI image 不包含 Firecracker 内存与设备状态;template 是共享的不变基线,实例写入进入自己的 CoW 层。

CubeSandbox:完整分发,逐节点预启动

create-from-image 是一条异步作业:PULLINGUNPACKINGDISTRIBUTING → 创建 template replica。主链路见 runTemplateImageJob

Loading GitHub file…
  flowchart LR
  I[OCI registry] --> A[CubeMaster ext4 artifact]
  A --> N1[Cubelet A]
  A --> N2[Cubelet B]
  N1 --> R1[local READY replica]
  N2 --> R2[local READY replica]
  R1 --> C1[CubeCoW instance]
  R2 --> C2[CubeCoW instance]

rootfs exporter

CubeMaster 默认走 native exporter;显式关闭后,PATH 中同时有 skopeo 和 umoci 才走 dockerless 路径,否则回退到 Docker-compatible Engine/CLI。选择顺序见 PrepareSource,默认值见 nativeRootfsExportEnabled。三条路径的差异只在 rootfs 获取阶段,后续注入、ext4、artifact 和分发流程相同。

Loading GitHub file…
Loading GitHub file…

路径还原方式依赖与边界
nativego-containerregistry 直连 registry,默认 6 并发预取 layer,再用 containerd archive.Apply 顺序应用不依赖 Docker/skopeo/umoci,也不借用 daemon layer cache;平台固定为 linux/GOARCH
skopeo + umociskopeo copy 生成 OCI layout,umoci unpack 还原 bundle无 daemon,但需要两个 CLI 和更多临时磁盘;非 root 的 --rootless 会压平 UID/GID 并跳过 device node
Docker-compatible Engine/CLIEngine API inspect/pull,失败后用 docker CLI;通过 docker createdocker export 还原需要 daemon/socket、名为 docker 的 CLI 和 tar;源码不检测 podman,按调用方式推断,Podman 必须同时提供 Docker-compatible socket 和 docker 命令兼容层
Loading GitHub file…

native 和 Docker 可直接写入 loop-mounted ext4,skopeo/umoci 只走先还原目录再制作 ext4 的路径,见 BuildExt4。native 不依赖外部镜像工具,但 archive.Apply 仍可因 root/CAP_MKNOD、xattr、file capabilities 或 device node 恢复失败,见 StreamRegistryToDir。dockerless 和 Docker 的具体导出分别见 dockerlessExportImageRootfsdockerExportImageRootfs

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

artifact 和 template replica

layer 按 OCI 顺序应用后,CubeMaster 注入 envd 和 CubeEgress CA,再制作 ext4,见 buildRootfsArtifact。artifact 的 fingerprint 包含 image digest、CA 指纹、envd SHA 和模板规格;只有 fingerprint 一致且 ext4 校验通过才复用,见 ensureRootfsArtifact

Loading GitHub file…
Loading GitHub file…

CubeSandbox 从 OCI Config 只保留 EntrypointCmdEnvWorkingDirUser,不继承 ExposedPortsVolumesLabelsStopSignalHealthcheck,见 convertV1Config。端口和 HTTP probe 需要显式配置;最多 3 个自定义端口,49983 保留,见 normalizeTemplateExposedPorts

Loading GitHub file…
Loading GitHub file…

artifact 会并发分发到目标 Cubelet,每个节点单独记录结果,见 distributeRootfsArtifact。Cubelet 拿到完整 ext4 后启动临时 Sandbox,制作内存和 rootfs 快照;AppSnapshot 的前置检查强制要求 CubeCoW,快照过程见 AppSnapshot。内存与 metadata 留在本地 catalog,当前 S3 只导出 template rootfs,见 UploadTemplateRootfs。因此运行就绪的单位是「某个节点上的 READY replica」。

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

E2B:分层快照,按需恢复

E2B 在 base、user、用户 steps 和 finalize 阶段反复启动或恢复 Firecracker VM,见 Builder.Build

Loading GitHub file…
  flowchart LR
  I[OCI image] --> B[ext4 plus E2B layers]
  B --> P[provision]
  P --> U[user steps]
  U --> F[finalize]
  P -. recipe hash .-> C[(layer cache)]
  U -. recipe hash .-> C
  F --> S[(object storage)]
  S -->|UFFD memory and NBD rootfs| X[Sandbox]

构建和缓存

控制面先用 RegisterBuild 在一个事务中写入 waiting build、tag assignment 和 active build,见 RegisterBuild。build start 只接受 pending build,会选择 builder、记录其 CPU 信息,然后调用 CreateTemplate,见 template_start_build_v2CreateTemplate 先发出 gRPC TemplateCreate,再标记 in_progress,后台用 BuildStatusSync 追踪结果,见构建启动与同步;启动链失败会标记 failed,见失败处理。

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

builder 按 TARGET_ARCH 选择并校验 OCI manifest,见 DefaultPlatformverifyImagePlatform。它追加 envd、BusyBox、provision script 和 init service 等 layer,然后制作 ext4,见 Rootfs.Build。发行版 provisioning 完成后,模板由显式的 start/ready command 驱动,不直接继承 Docker HEALTHCHECKEXPOSEVOLUME

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

每个构建阶段计算 recipe hash,并受 CacheScope 隔离。未命中时,LayerExecutor 恢复上一层、执行 action,再 PauseAndUpload,见 BuildLayer。快照先进入本地 cache,但只有全部 artifact 上传完成后,recipe hash 到 build ID 的映射才对其他 builder 可见,见 UploadSnapshot

Loading GitHub file…
Loading GitHub file…

存储和恢复

E2B 按 build ID 保存 memfilerootfs.ext4snapfilemetadata.json 及内存/rootfs header,见 storage.Paths。header 描述块所属和差分祖先链。对象存储前可叠加本地/NFS cache,也可通过 Redis 定位持有某个 build ID 的 peer,见 Cache.GetTemplate

Loading GitHub file…
Loading GitHub file…

运行节点的 Server.Create 先用 build ID 取 template,再按 metadata 选择 RebootSandboxResumeSandbox,见 Server.Create。常规恢复通过 userfaultfd 按页加载内存,rootfs 由 NBD 提供差分块设备;快照对象未上传或已不存在时返回 FailedPrecondition,不会带着缺失状态启动,见 runtime 架构 和错误分支。finalize snapshot 还会试运行一次以收集 prefetch mapping;优化失败不会让构建失败,见 OptimizeBuilder.Build

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

核心差异

维度CubeSandboxE2B
OCI 转换逐层 apply,注入 envd/CA,生成不变 ext4追加系统 layer,写入 ext4,再在 VM 中 provisioning
构建缓存按完整规格 fingerprint 复用 rootfs artifactCacheScope + recipe hash 复用阶段性 VM 快照
快照位置每个 Cubelet 的本地 catalog/CubeCoW对象存储持久化,前面叠加多级 cache
分发方式先下载完整 ext4,再预启动根据 header 和块范围懒加载
新节点代价分发 artifact 并生成 READY replica可直接恢复兼容快照,但有冷缓存读取
实例写入CubeCoW childNBD CoW diff

CubeSandbox 在模板就绪前完成分发和预启动;E2B 把部分加载延迟到恢复路径。

模板如何跨不同 CPU 机器

这个问题包含三层:ISA(amd64/arm64)、同 ISA 下的 vendor/model/features,以及 host kernel、KVM、Firecracker 和 guest kernel 组合。rootfs 主要受 ISA 限制,内存快照同时绑定三层状态。

CubeSandbox

常规 template replica 在每个目标节点单独启动和制作:同一 ext4 可以在同 ISA、不同 CPU model 节点各自生成本地快照,但新节点不能直接使用别的节点的内存快照。native exporter 只拉 linux/$GOARCH,artifact 分发链未再按 OCI architecture 过滤节点,因此 amd64 和 arm64 应分开构建与分发,见 defaultPlatform

Loading GitHub file…

运行中 Sandbox 暂停或 commit 后的跨节点恢复更严格。Cubelet 从 /proc/cpuinfo 生成 cpuid_hash,恢复策略要求目标节点的 cpuid_hashhost_kernel_release 严格相等,并拒绝 KVM module 带可疑 taint 的节点,见 EvaluateSnapshotRestoreCompat。还必须满足 S3 remote_status=ready 且没有 host mount;调度优先回原节点,再考虑兼容 peer,见 restoreplace.Decidecpuid_hash 只读第一个逻辑 CPU block,在 big.LITTLE、Intel P/E core 或混合 socket 机器上可能误判,见 parseCPUInfo

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

E2B

E2B 把 builder 的 CPU architecture、family、model 和 flags 写入 build,placement 用这些信息筛节点,见 template_start_build_v2isNodeCPUCompatible。兼容规则不会通用地判断 CPUID feature subset:architecture/family 必须一致,model 默认也要一致;当前唯一显式例外是 Intel Ice Lake model 106 → Emerald Rapids model 207,反向不允许,见 MachineInfo.IsCompatibleWith。CPU flags 虽被持久化,当前没有参与子集判定;老 build 缺 architecture 时为了兼容会 fail open。

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

template builder 选择要求 architecture/family/model 完全匹配,不使用运行时的跨代兼容表,见 GetAvailableTemplateBuilder。暂停快照继续绑定 source build 的 CPU 约束,不会因为 Sandbox 在新 CPU 上暂停就改变兼容集,见 buildUpsertSnapshotParams

Loading GitHub file…
Loading GitHub file…

场景CubeSandboxE2B
amd64 ↔ arm64需要独立 artifact/template,目标节点池应按 ISA 隔离OCI 拉取和 placement 都按 architecture 隔离
同 ISA、同 modeltemplate 在每个节点本地生成;暂停快照还要同 cpuid_hash 和 host kernel允许调度
同 ISA、不同 model可分别制作 template replica;已暂停快照通常不能跨机默认禁止;当前只有 Ice Lake → Emerald Rapids
Intel ↔ AMD x86_64template 可分别本地制作,同一暂停快照不通用family/model 不同,禁止
CPU flags 不同进入 cpuid_hash,阻止跨节点恢复已记录,但当前不直接参与兼容计算
新节点下载 ext4 并重做 replica兼容即可调度,但会遇到冷 cache

内存快照还绑定 guest kernel、Firecracker、vCPU 和 RAM。E2B 把版本写入 layer metadata,CubeSandbox 把 CPU/内存写入快照规格,见 BuildLayerAppSnapshot。修改这些参数需要新模板,不能把旧内存快照当成普通磁盘镜像搬迁。

Loading GitHub file…
Loading GitHub file…

其他实现限制

类别限制与后果
容器语义两者都使用自己的 guest kernel、init、agent 和网络。依赖 Docker socket、host namespace、宿主机 module/设备、特定 LSM/SELinux 的应用需重新设计
发行版CubeSandbox 没有 E2B 式的 profile 白名单,但镜像仍要适配 guest kernel 和启动方式。E2B 显式支持若干 Debian/Fedora/Arch/Alpine/NixOS 系 profile,ID_LIKE 只是 best effort,并拒绝 rhelolamzn,见 distro.ProfilesRejectedIDs
builder 权限还原 OCI 文件需保留 UID/GID、whiteout、xattr、capabilities 和 device node。Cube 需 ext4 工具,可选 loop mount;E2B 还要 root、mount/overlayfs 和 Firecracker builder VM,当前 overlayfs fsconfig 路径要求 Linux 6.8+,见 MountOverlay
注入二进制Cube 可选 envd 必须是非空 ELF、不超过 16 MiB,并匹配 rootfs OS/ISA,见模板教程。E2B 从 builder 读取 host envd,BusyBox 路径则包含 runtime.GOARCH,见 additionalOCILayers
容量Cube 的 writable layer size 进入 artifact fingerprint。E2B base rootfs 默认上限为 25,000 MB,但可由 feature flag 调整,见 BuildBaseRootfsSizeLimitMB。大镜像会增加构建磁盘、分发/上传和冷 cache 成本
版本漂移快照不保证跨 guest kernel/Firecracker 版本兼容。Cube 会把 guest-image/agent/kernel 差异标为 STALE,但 STALE replica 仍可调度,这是运维信号而非强隔离,见 ScanNodeCompat
存储与地域性Cube AppSnapshot 必须使用 CubeCoW,内存和 metadata 默认在节点本地;E2B 把对象存储作为持久层,NFS/P2P 只是 cache。Cube 的 READY 要看 replica,E2B 的 READY 还要结合 CPU placement
部分失败Cube 有成功也有失败 replica 时返回 PARTIALLY_READY,见 summarizeStatus;E2B 只在层 artifact 全部上传后发布 hash 索引。全局 template ID 不能代替对节点/层完整性的检查
跨节点恢复Cube 的暂停快照要求 S3 ready、无 host mount 且通过 CPU/kernel 校验;E2B 的快照同样是 diff chain,placement 优先原节点再选兼容节点。「已上传」不等于「任意节点可恢复」
内存不可用Cube 常规 template 需在目标节点重做 replica。E2B 暂停 Sandbox 可显式请求 filesystem-only cold boot,但受 feature flag 控制,auto-resume 不会自动降级,见 Architecture。丢弃内存是语义变化,必须由调用方选择
快照链与冷 cacheE2B 的子快照依赖 .header 中的祖先块;缺失祖先会使恢复不完整。本地 template cache 默认 25 小时过期,过期影响下次冷恢复延迟,不改变对象存储的正确性,见 templateExpiration
数据持久性实例 rootfs 写入默认不回写模板。持久成新基线需显式 snapshot/commit;共享业务数据仍应使用 Volume。共享模板块不等于多 Sandbox 共享文件语义
网络可达性CubeMaster/E2B builder 必须能访问 registry,Cubelet 要能下载 artifact,E2B Orchestrator 要能访问 storage/cache。私有仓库 auth 不解决 DNS、代理、限流和大文件链路问题

Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…
Loading GitHub file…

动态 feature flag、私有化配置和托管服务 quota 还可以在这些源码边界上继续收紧。

小结

CubeSandbox 把模板实现为分发到节点的预启动副本:就绪前成本更高,恢复时数据已在本地。E2B 把构建中间步骤和最终模板都保存为分层 VM 快照:新节点不需要预先复制全量数据,但恢复路径必须处理 CPU 兼容、快照链和冷 cache。

#Sandbox #MicroVM #OCI #Firecracker #CubeSandbox #E2B