跳到主要内容

沙箱生命周期:Firecracker microVM 的编排

30 秒导读: 当 orchestrator 收到"给我一个沙箱"的 gRPC 请求,它要在这台节点上把一个 Firecracker microVM 真正拉起来——装配内存、磁盘、网络、cgroup,启动 FC 进程,等 VM 里的 envd 守护进程应答,然后才把它标成"可路由"。本章讲的就是这条装配线,以及它反过来怎么把 沙箱暂停成快照或销毁掉。快照内部、内存惰性加载、块存储、网络槽的实现分别交给 03/04/05。


1. 这章讲什么(先建立直觉)

上一章 01-architecture 讲了"一次创建沙箱"从 API 到 orchestrator 的旅程。 本章把镜头推到单台节点内部:一个沙箱在这里如何被拉起、暂停、销毁。

先给一个心智模型。一个"沙箱"在 orchestrator 里其实是一组东西被串在一起:

  • 一个真实的 Firecracker 进程(跑着 guest 内核和用户的代码);
  • 它的四样底层资源:内存后端、rootfs、网络槽、cgroup;
  • 一个 清理栈(Cleanup),记录"拆的时候要反着做哪些事";
  • 一条 就绪判定:VM 里的 envd 守护进程能应答了,沙箱才算"起来了"。

把这些装好、点火、确认心跳,再登记进"活沙箱表",就是一次启动;反过来对称拆掉,就是一次停止。

关键角色一览:

角色是什么在哪
Factory沙箱的工厂,持有网络池/设备池/cgroup 管理器等共享资源,CreateSandbox/ResumeSandbox/RebootSandbox 都是它的方法packages/orchestrator/pkg/sandbox/sandbox.go:331 Factory
Sandbox一个活沙箱的句柄,内嵌 Resources(槽/rootfs/内存)和 Metadatasandbox.go:254 Sandbox
fc.Process对一个 Firecracker 进程的封装packages/orchestrator/pkg/sandbox/fc/process.go:135 Process
Cleanup后进先出的清理栈,保证资源对称释放packages/orchestrator/pkg/sandbox/cleanup.go:20 Cleanup
Map节点上所有沙箱的注册表,管"可见性/可路由"的状态机packages/orchestrator/pkg/sandbox/map.go:172 MarkRunning

2. 三种启动类型:Create / Resume / Reboot

一切从一个区分开始。同一台节点上"拉起一个 VM"有三种语义完全不同的姿势,代码用三个常量把它们 钉死,并作为 start_type 打进启动指标:

// packages/orchestrator/pkg/sandbox/sandbox.go:64 StartType
const (
StartTypeCreate StartType = "create" // 冷启动(模板构建)
StartTypeResume StartType = "resume" // 从快照恢复(运行时最常见的路径)
StartTypeReboot StartType = "reboot" // 从快照 rootfs 冷启动(仅文件系统恢复)
)

三者的差别,一张表看清:

启动类型常量典型场景恢复了什么内存后端就绪判定
冷启动StartTypeCreate模板构建时从零引导无,全新引导NoopMemory(不惰性缺页)冷启动引导后等 envd
秒级恢复StartTypeResume运行时最常见:从内存快照恢复内存 + 进程 + socket + 文件系统Uffd(惰性缺页,见 03)WaitForEnvd
文件系统恢复StartTypeReboot从"仅文件系统"快照恢复只有文件系统;RAM/进程/socket 全丢NoopMemory(全新匿名 RAM)WaitForEnvd

它们各对应一个入口函数:

  • StartTypeResumeFactory.ResumeSandbox(sandbox.go:694)——这是用户沙箱日常"唤醒"走的路。
  • StartTypeRebootFactory.RebootSandbox(reboot.go:39)——它内部复用 CreateSandbox 做冷启动,只是把 start_type 记为 reboot
  • StartTypeCreateFactory.CreateSandbox(sandbox.go:390)——模板构建时的冷引导走这里。

一个重要观察:orchestrator 的 gRPC Create 处理器并不叫"新建",它其实是"恢复"。它读快照 元数据,按"是不是仅文件系统快照"在 reboot 与 resume 之间二选一:

// packages/orchestrator/pkg/server/sandboxes.go:230
if meta.IsFilesystemOnly() {
sbx, err = s.sandboxFactory.RebootSandbox(ctx, template, config, runtime, ...)
} else {
sbx, err = s.sandboxFactory.ResumeSandbox(ctx, template, config, runtime, ...)
}

为什么必须由快照自己的元数据来裁决?因为内存快照的 rootfs 可能缺了只存在 guest page cache 里的写——那些写只有在内存恢复时才被带回来。对内存快照做冷启动,会挂载一块不一致的磁盘。 RebootSandbox 因此设了一道安全闸,拒绝对非"仅文件系统"的快照冷启动(reboot.go:63)。


3. 顶层全景:一次 Resume 的旅程

先看最常见的 ResumeSandbox。它的骨架是:并发装配四样资源 → 造 FC 进程 → 恢复 VM → 等 envd → 登记为可路由

怎么读这张图:从上往下是时间顺序;中间那段四条支线是并发装配(用 Promise 并行跑), 后面几步才是串行。

ResumeSandbox (sandbox.go:694)

│ ==== 并发装配资源(promise) ====
├─ 网络槽 getNetworkSlot ──────→ 细节见 05
├─ rootfs NBDProvider(overlay)──────→ 细节见 04
├─ 内存 serveMemory(uffd) ──────→ 细节见 03
└─ cgroup createCgroup

│ ==== 串行点火 ====

fc.NewProcess 装 FC 启动脚本 / unshare 挂载空间 / 备好 socket


fc.Resume loadSnapshot(挂 uffd)→ resumeVM → 写 MMDS 元数据


WaitForEnvd 反复 POST /init,直到 guest 里的 envd 回 204


MarkRunning 进 live 表 → 现在可被 Get / 路由 / 计数

一句话主线:资源先并行备齐,FC 才能被 loadSnapshot 拉起;VM 恢复运行后还不算数,必须 等 envd 应答;应答了才把沙箱"点亮"为可路由。 下面逐段拆开。


4. 装配资源:Create 与 Resume 的主流程

4.1 四样资源,装进 Resources

不管 Create 还是 Resume,一个沙箱最终都要凑齐这四样,打包进 Resources 结构:

// packages/orchestrator/pkg/sandbox/sandbox.go:212 Resources
type Resources struct {
Slot *network.Slot // 网络槽:tap/veth/netns(见 05)
rootfs rootfs.Provider // 写时复制的根文件系统(见 04)
memory uffd.MemoryBackend // 内存后端:Uffd 或 NoopMemory(见 03)
}

Create 与 Resume 在"内存后端"上分道扬镳,这是两条路径最本质的区别:

  • Create(冷启动)用 uffd.NewNoopMemory(...)(sandbox.go:528)——VM 的 RAM 是 FC 自己 新分配的匿名内存,没有"从快照惰性缺页"这回事。
  • Resume(秒级恢复)用真正的 uffd.New(...)(sandbox.go:744),并在 serveMemory 里把它 启动起来监听 uffd socket。VM 访问某页内存时才按需从快照拉进来——这就是"秒级恢复"的核心, 机制留给 03-fast-resume-uffd

4.2 Resume 用 Promise 并行装配

Resume 对启动延迟很敏感,所以它把慢活儿都包成 utils.Promise 并行跑:内存 uffd、网络槽、 rootfs overlay、内存服务各起一个 promise,最后统一 Wait(sandbox.go:847-875)。

网络槽的获取就是个典型 promise——从池里借一个槽,并当场把归还登记进清理栈:

// packages/orchestrator/pkg/sandbox/sandbox.go:1657 getNetworkSlot
return utils.NewPromise(func() (*network.Slot, error) {
slot, err := networkPool.Get(ctx, networkConfig) // 从池借一个槽
if err != nil { return nil, err }
cleanup.Add(ctx, func(ctx context.Context) error { // 反向登记:拆时归还
return networkPool.ReturnAsync(ctx, slot, networkReleased, network.ReturnDelay)
})
return slot, nil
})

要记住的模式:每借到一样资源,立刻把"怎么还它"压进 cleanup 这样无论后面哪一步失败, defer cleanup.Run 都能把已经装好的东西对称拆干净——这是整条装配线的安全网(§7 详述)。

4.3 装配顺序与"惰性缺页前的隔离"

Resume 有一处顺序上的讲究:如果这是个网络隔离的沙箱(如快照预取用的一次性沙箱), 必须在 fcHandle.Resume 之前DenyEgress:

// packages/orchestrator/pkg/sandbox/sandbox.go:859
if ropts.denyEgress {
if err := ips.DenyEgress(ctx); err != nil { ... }
}

原因写在源码注释里:ResumeSandbox阻塞到 envd init 完成,等拿到句柄再断网就晚了—— 那时 envd 初始化甚至短暂解冻的负载都可能已经出网了。这是"生命周期顺序影响安全边界"的一个缩影。

4.4 造 FC 进程 → 恢复 → 等 envd → 点亮

资源备齐后,Resume 的收尾是四步串行(对应第 3 节图的下半段):

  1. fc.NewProcess(sandbox.go:896)——组装但还没启动 Firecracker(见第 5 节)。
  2. fcHandle.Resume(sandbox.go:1024)——真正 loadSnapshot + resumeVM
  3. sbx.WaitForEnvd(ctx, StartTypeResume, ...)(sandbox.go:1061)——等 guest 就绪(见第 6 节)。
  4. f.Sandboxes.MarkRunning(ctx, sbx)(sandbox.go:1076)——登记为可路由。

注意 MarkRunning 排在 WaitForEnvd 之后:沙箱在 envd 应答前不可被路由,避免流量打到 一个还没准备好的 VM。Create 冷启动默认在函数末尾就 MarkRunning(sandbox.go:635),但 reboot 路径会用 WithDeferredMarkRunning() 把它推迟到 envd 就绪之后(见第 8 节),对齐 Resume 的路由保证。


5. Firecracker 集成:fc/ 包怎么点火

fc/ 包把"和 Firecracker 打交道"这件事收拢成几个文件,各司其职:

文件职责关键符号
process.goFC 进程的启动/配置/暂停/停止/快照NewProcess,configure,Create,Resume,Stop,Pause,CreateSnapshot
config.go版本号 → 内核/FC 二进制路径,rootfs 路径格式Config,RootfsPaths,ConstantRootfsPaths
script_builder.go生成 FC 启动 shell 脚本StartScriptBuilder,startScriptV2
kernel_args.go把 map 拼成内核命令行字符串KernelArgs.String
mmds.go定义写给 guest 的 MMDS 元数据结构MmdsMetadata
memory.go暂停时从 FC 进程导出脏内存页(供快照,见 03)ExportMemory,MemoryInfo

5.1 NewProcess:组装启动脚本,但先不点火

NewProcess(process.go:159)不启动 FC,只是把命令备好。它先让 StartScriptBuilder 生成 一段 bash 启动脚本,再把它包进 unshare -m(新挂载命名空间)里:

// packages/orchestrator/pkg/sandbox/fc/process.go:195
cmd := exec.CommandContext(execCtx, "unshare", "-m", "--", "bash", "-c", startScript.Value)

那段启动脚本(模板 startScriptV2,script_builder.go:55)做三件事:把挂载空间设为私有、用 tmpfs + 软链把 rootfs 和内核摆到 FC 期望的路径、最后 进网络命名空间拉起 firecracker 二进制:

mount --make-rprivate /
mount -t tmpfs tmpfs {SandboxDir} # 一块干净的临时挂载点
ln -s {HostRootfsPath} .../rootfs.ext4 # 软链真实 rootfs
ln -s {HostKernelPath} .../vmlinux.bin # 软链内核
ip netns exec {NamespaceID} {firecracker} --api-sock {socket}

进程真正被 Start() 是在 configure(process.go:227)里,它同时:接管 stdout/stderr、通过 CLONE_INTO_CGROUP 把进程原子地放进沙箱 cgroup(process.go:254)、建好 metrics FIFO、然后 socket.Wait 等 FC 的 API socket 出现(process.go:307)——socket 一出现就能用 FC HTTP API 了。

5.2 Create:一条条 FC API 调用把机器配起来

冷启动的 Process.Create(process.go:319)是"照着 FC REST API 把 VM 配置齐全再点火"的直白流水:

步骤FC API 调用干什么
引导源setBootSource内核路径 + 内核命令行参数
rootfssetRootfsDrive挂载根盘(带限速 rate limiter)
网络setNetworkInterfacetap 设备 + MAC,MMDS 传输也随之就绪
机器setMachineConfigvCPU 数、内存 MB、是否 hugepages
熵源setEntropyDevicevirtio-rng
气球installBalloon可选,free-page reporting/hinting
元数据setMmds可选,写 guest 元数据(见 5.4)
点火startVM真正开机

内核命令行由 KernelArgs(一个 map[string]string)拼成,Create 里定义了一串生产参数—— 关掉内核日志提速、配 IPv4/IPv6、panic=1rootflags=discard(让 ext4 对释放块发 TRIM,从而 被快照 diff 省掉)等(process.go:369)。KernelArgs.String()(kernel_args.go:13)把它排序拼成 key=value 串。

5.3 Resume:从快照拉回一整台机器

恢复路径的 Process.Resume(process.go:513)比 Create 少了"逐项配置",多了"整体加载":

  • errgroup 并发做三件事:configure 启动 FC 进程、等 uffd socket、软链 rootfs(process.go:534-585)。
  • 核心是 loadSnapshot(process.go:605)——把 snapfile 交给 FC,并把 uffd socket 接上做内存惰性加载。
  • 然后 resumeVM(process.go:634)解冻,setMmds 补上元数据。

也就是说:Create 是"从零把零件装成机器",Resume 是"把整机快照 mmap 回来再解冻"。后者为什么快、 uffd 怎么按页缺页,是 03 的主题。

5.4 MMDS:给冷启动的 envd 递一张"身份条"

MMDS(Firecracker 的实例元数据服务)是 host 写、guest 读的一小块 JSON。E2B 用它把沙箱身份和 访问令牌哈希递给 guest 里的 envd,好让 envd 能鉴权 /init:

// packages/orchestrator/pkg/sandbox/fc/mmds.go:6 MmdsMetadata
type MmdsMetadata struct {
SandboxID string `json:"instanceID"`
TemplateID string `json:"envID"`
LogsCollectorAddress string `json:"address"`
AccessTokenHash string `json:"accessTokenHash,omitempty"`
}

关键细节:内存恢复时,身份能从恢复的 RAM 里继承;但冷启动/reboot 的 envd 没有旧状态,所以 Create 会在开机前主动写 MMDS(process.go:485),空令牌也照写(它哈希成"无令牌"值,与 Resume 对齐)。


6. 等 envd 就绪:VM 开机 ≠ 沙箱可用

FC startVM/resumeVM 返回只代表 guest 内核在跑;沙箱要能干活,得等 VM 里的 envd 守护进程 起来并应答。这道判定就是 WaitForEnvd

6.1 WaitForEnvdinitEnvd:无限重试打 /init

// packages/orchestrator/pkg/sandbox/sandbox.go:1738 WaitForEnvd
func (s *Sandbox) WaitForEnvd(ctx context.Context, startType StartType, timeout time.Duration) (e error) {
// ... 起一个 goroutine:超时 / FC 进程提前退出 都会取消 ctx
if err := s.initEnvd(ctx, startType); err != nil { ... }
// 成功后 SetStartedAt(now),并记录一批启动 KPI 指标
}

initEnvd(envd.go:229)向 guest 的 http://<slot-ip>:49983/init 反复重试 POST,直到拿到 204 No Content(envd.go:243,envd.go:274)。整个等待被 timeout 兜底:超时或 FC 进程提前 死掉,ctx 就被取消,WaitForEnvd 失败,进而触发清理拆掉这个半死的沙箱。

WaitForEnvd 里那段指标代码有个巧思:用 startupStatsOnce 保证 uffd 启动页数只在第一次 WaitForEnvd 时采样——因为 ServeStats() 是恢复以来累计的,模板构建里 envd 二进制热替换会二次 调用 WaitForEnvd,不 gate 住就会把之后的缺页也算进"启动工作集"(sandbox.go:1771)。

6.2 就绪之后:周期性 /health 心跳

沙箱一旦 MarkRunning,Checks 会起一个后台健康循环(checks.go:58 Startchecks.go:84 logHealth),每 20 秒打一次 guest 的 /health,期望 204:

// packages/orchestrator/pkg/sandbox/health.go:16 getHealth
address := fmt.Sprintf("http://%s:%d/health", c.sandbox.Slot.HostIPString(), consts.DefaultEnvdServerPort)
// ... 期望 http.StatusNoContent(204),否则视为不健康

健康状态用一个 atomic.Bool + CompareAndSwap边沿触发上报:只有 健康↔不健康 翻转时才 写日志(checks.go:110-121),避免每 20 秒刷屏。这套心跳的间隔/超时是常量:20s / 100ms(checks.go:22)。


7. 资源记账与清理:cgroup 与后进先出的清理栈

7.1 Cleanup:对称拆解的安全网

前面反复出现 cleanup.Add(...)Cleanup(cleanup.go:20)就是一个后进先出的函数栈:装配时 正序压栈,拆解时逆序执行,天然保证"先装的后拆"。

// 示意,非源码:cleanup 的核心思想
stack := []func(){}
stack = append(stack, releaseNetworkSlot) // 先借网络
stack = append(stack, removeCgroup) // 再建 cgroup
stack = append(stack, stopFcProcess) // 最后起 FC
// 出错或收尾时,从后往前拆:
for i := len(stack) - 1; i >= 0; i-- { stack[i]() }

真实实现里 run(cleanup.go:78)先跑优先清理 priorityCleanup、再跑普通 cleanup,都逆序。 沙箱的 Stop 就是用 AddPriority 压进去的(sandbox.go:619),确保"先把 FC 停掉"排在最前。 Runsync.Once 包住,幂等——多次调用只真正拆一次(cleanup.go:70)。

最底层的文件清理由 cleanupFiles(cleanup.go:103)负责:删掉 FC socket、uffd socket、rootfs 软链 这些落盘的临时物。

7.2 cgroup:原子放置 + 一键杀干净

每个沙箱有独立 cgroup,用于资源记账和可靠地杀掉整棵进程树。它在装配时由 createCgroup (sandbox.go:1637)建好,拿到一个 FD——这个 FD 通过 CLONE_INTO_CGROUP 让 FC 进程一 fork 就落在正确的 cgroup 里(§5.1),避免"先 fork 再搬"的竞态。

停止时,CgroupHandle.Kill(cgroup/manager.go:113)往 cgroup.kill 写个 1,内核就把整个 cgroup 里的进程一次性杀光,再 poll cgroup.events 等它清空。FC 的子孙进程不靠 numeric PID group 去信号, 而是靠 cgroup 兜底(process.go:690 注释),这样不会漏杀也不会误杀被复用的 PID。

7.3 Stop / Close / Shutdown:三个收尾动词

方法干什么幂等机制
Stop停健康检查 → 停 FC 进程 → 杀 cgroup → 停内存后端utils.Lazy,只有第一次真正执行(sandbox.go:1139)
Closecleanup.Run(逆序拆全部资源)+ 从 Map 摘除Cleanup.once(sandbox.go:1124)
Shutdown暂停 VM → 造一次性快照(仅为触发磁盘 flush)→ Close—(sandbox.go:1183)

doStop(sandbox.go:1146)的顺序值得记:Checks.Stop() 停心跳(否则会把一个正在关的沙箱 误报成不健康),再停 FC、杀 cgroup,最后停 uffd 内存后端。

装配阶段还挂了一个后台 goroutine 守着 FC 退出:FC 进程一 Exit,就自动调 sbx.Stop (sandbox.go:628,Resume 版在 sandbox.go:1085)——VM 自己崩了也能触发对称拆解


8. Reboot:仅文件系统快照的冷启动

RebootSandbox(reboot.go:39)是"文件系统恢复"的入口,专门用于恢复仅文件系统快照: guest RAM、进程、socket 全部丢失,只有磁盘幸存。

它的实现很能说明"三种启动类型如何复用"——它并不自己造 VM,而是转身调用 CreateSandbox (reboot.go:117),只是补了几样冷启动特有的处理:

  • 安全闸:非"仅文件系统"快照直接拒绝(reboot.go:63),理由见第 2 节。
  • 补默认用户/工作目录:冷启动的 envd 没有旧 RAM 可继承,必须通过 /init 重新下发,否则回落到 root//root(reboot.go:72)。
  • 造一块空 memfile 只为给 NoopMemory 定尺寸(reboot.go:89);真正的 RAM 是 FC 新的匿名内存。
  • WithDeferredMarkRunning():因为是冷启动,必须等 envd 应答后才可路由,于是 MarkRunning 推迟到 WaitForEnvd(..., StartTypeReboot, ...) 之后(reboot.go:145,reboot.go:151)。

一句话:reboot 是"披着 Create 外衣的 Resume 语义"——用冷启动的机制,兑现 Resume 的路由保证。


9. 生命周期收尾与事件

单个 Sandbox 会拆解自己,但"这个沙箱在节点上活着"这件事还要向上层(代理、API、事件总线)交代。 这层收尾在 server/sandboxes.go

9.1 Map 状态机:一个沙箱的可见性

Map(map.go)用几个状态位管理"这个沙箱能不能被查到、能不能被路由":

怎么读:从上到下是一次正常的生老病死;每一步只翻转一小块可见性。

AssignNetwork ──→ 按 IP 可达(GetByHostPort 能找到) map.go:147
│ (装配早期就登记,resume 期间也能按源地址找到)

MarkRunning ──→ 进 live 表:可 Get / 可路由 / 计入配额 map.go:172
│ (envd 就绪后才点亮)

MarkStopping ──→ 退出 live 表,但仍可按 IP 找到 map.go:197
│ (Delete / Pause 一进来就打上,阻止再被同步给 API)

Stop → Close


MarkStopped ──→ 从 lifecycle 追踪里彻底摘除 map.go:223

MarkStopping 这个中间态是优雅关停的关键:沙箱已经不该出现在"活沙箱列表"里(不再被同步给 API、不接新流量),但 FC 进程收尾期间仍要按 IP 可达,好把最后的杀请求/健康检查送进去。

9.2 三个收尾辅助函数

server/sandboxes.go 把收尾动作收成三个小工具:

函数干什么位置
setupSandboxLifecycle起后台 goroutine:sbx.Wait 等它自然退出 → Close 清理 → 从代理连接池摘除server/sandboxes.go:1039
stopSandboxAsync后台调 sbx.Stop,不阻塞请求server/sandboxes.go:1064
publishSandboxEvent后台向事件总线发一条沙箱事件(created/resumed/paused/killed…)server/sandboxes.go:1077

setupSandboxLifecycle 在每次成功 resume/reboot 后被挂上(server/sandboxes.go:272),它是那条 "沙箱一旦退出就自动清场并从连接池摘除"的兜底守护。请求处理器则用 stopSandboxAsync 把重活儿 甩到后台,让 Delete/Pause 快速返回(server/sandboxes.go:539:641)。


10. Pause / Checkpoint:触发快照的入口

暂停是生命周期的另一半:把一个活沙箱冻成快照,以便日后秒级恢复。本章只讲"入口怎么被生命周期 串起来",快照内部机制(内存 diff、COW 导出)交给 0304

gRPC Pause 处理器(server/sandboxes.go:596)的编排是:

  1. MarkStopping——先让它退出 live 表(server/sandboxes.go:631)。
  2. defer stopSandboxAsync——收尾时在后台停掉这个旧沙箱(server/sandboxes.go:641)。
  3. snapshotAndCacheSandbox——真正造快照并落本地缓存(server/sandboxes.go:644),这一步内部调用 sbx.Pause
  4. 之后异步上传快照、(可选)预取热身。

Sandbox.Pause(sandbox.go:1251)才是快照的核心编排,它的步骤在源码里有完整注释:停健康检查 → (尽力而为)guest 内回收 fstrim/sync → 暂停 FC(process.go:749 pauseVM)→ CreateSnapshot (process.go:823,顺带 drain+flush 磁盘)→ 后处理内存 diff 与 rootfs diff → 写元数据,返回一个 Snapshot

两个入口选项决定快照形态:

  • 默认:全量内存快照——恢复走 resume,秒级带回整机状态。
  • WithFilesystemSnapshot()(sandbox.go:1231):仅文件系统快照——跳过内存导出,恢复走 reboot(第 8 节)。此路径必须先把 guest page cache 刷盘(guestPrepareFsForPause,reclaim.go:168), 否则冷启动会读到缺写的磁盘。

snapshotAndCacheSandbox 内部把 diff 导出、去重、上传的重活儿分派出去——这些"快照到底怎么算出来" 的机制,正是下一章的主题。


11. 明确边界:本章到哪为止

本章只讲"生命周期怎么把各子系统串起来",各子系统的内部机制分散在别处,别在这里找:

你想了解去哪
内存快照 + userfaultfd 惰性缺页(uffd 内部)03-fast-resume-uffd
写时复制块存储:rootfs 与内存 diff 的分层去重04-cow-block-storage
网络槽内部:tap/veth/netns、出站管控05-network-isolation
边缘路由与 envd 在 VM 内的角色06-edge-and-envd

本章对它们只关心两件事:装配时怎么把它们凑齐、拆解时怎么对称还回去。


12. 代码地图(导航索引)

主题文件路径符号名
三种启动类型常量packages/orchestrator/pkg/sandbox/sandbox.goStartTypeCreate / StartTypeResume / StartTypeReboot
冷启动主流程packages/orchestrator/pkg/sandbox/sandbox.goFactory.CreateSandbox
秒级恢复主流程packages/orchestrator/pkg/sandbox/sandbox.goFactory.ResumeSandbox
文件系统恢复packages/orchestrator/pkg/sandbox/reboot.goFactory.RebootSandbox
资源打包packages/orchestrator/pkg/sandbox/sandbox.goResources
网络槽装配packages/orchestrator/pkg/sandbox/sandbox.gogetNetworkSlot
内存后端启动packages/orchestrator/pkg/sandbox/sandbox.goserveMemory
cgroup 装配packages/orchestrator/pkg/sandbox/sandbox.gocreateCgroup
等 envd 就绪packages/orchestrator/pkg/sandbox/sandbox.goSandbox.WaitForEnvd
envd /init 重试packages/orchestrator/pkg/sandbox/envd.goSandbox.initEnvd
周期健康检查packages/orchestrator/pkg/sandbox/checks.go / health.goChecks.logHealth / Checks.getHealth
停止/关闭/关停packages/orchestrator/pkg/sandbox/sandbox.goSandbox.doStop / Sandbox.Close / Sandbox.Shutdown
清理栈packages/orchestrator/pkg/sandbox/cleanup.goCleanup / Cleanup.run / cleanupFiles
FC 进程组装packages/orchestrator/pkg/sandbox/fc/process.goNewProcess / configure
FC 冷启动配置packages/orchestrator/pkg/sandbox/fc/process.goProcess.Create
FC 快照恢复packages/orchestrator/pkg/sandbox/fc/process.goProcess.Resume
FC 暂停 / 造快照packages/orchestrator/pkg/sandbox/fc/process.goProcess.Pause / Process.CreateSnapshot
启动脚本模板packages/orchestrator/pkg/sandbox/fc/script_builder.goStartScriptBuilder / startScriptV2
内核命令行packages/orchestrator/pkg/sandbox/fc/kernel_args.goKernelArgs.String
guest 元数据packages/orchestrator/pkg/sandbox/fc/mmds.goMmdsMetadata
cgroup 一键杀packages/orchestrator/pkg/sandbox/cgroup/manager.goCgroupHandle.Kill
可见性状态机packages/orchestrator/pkg/sandbox/map.goMarkRunning / MarkStopping / MarkStopped
生命周期收尾packages/orchestrator/pkg/server/sandboxes.gosetupSandboxLifecycle / stopSandboxAsync / publishSandboxEvent
Pause 编排packages/orchestrator/pkg/sandbox/sandbox.goSandbox.Pause / WithFilesystemSnapshot
Pause gRPC 入口packages/orchestrator/pkg/server/sandboxes.goServer.Pause / snapshotAndCacheSandbox