hcsshim实时迁移原理详解:跨主机无缝移动容器的实现机制
【免费下载链接】hcsshimWindows - Host Compute Service Shim项目地址: https://gitcode.com/gh_mirrors/hc/hcsshim
hcsshim(Host Compute Service Shim)是微软开源的 Windows 容器运行时核心组件,负责把 containerd 与宿主机上的 HCS(Host Compute Service)对接起来。而 hcsshim实时迁移(Live Migration)则是它最具"黑科技"色彩的能力:在不停止服务的前提下,把一个正在运行的 LCOW 沙箱——包括整台工具虚拟机(Utility VM)和其中的所有容器——从一台宿主机完整地"搬运"到另一台宿主机。本文将从源码层面拆解这套跨主机无缝移动容器的实现机制。
什么是 hcsshim 实时迁移?
传统方案要迁移容器,只能先停掉、打包、拷走、再启动,服务必然中断。而实时迁移的目标是让应用"感觉不到"自己被搬了家:迁移期间虚拟机内进程继续运行,只有最后一小段"切机"窗口内会短暂冻结,随后在目标主机上无缝恢复。
这套机制的核心代码集中在两个位置:
internal/controller/migration/:迁移会话控制器,负责编排整个迁移生命周期pkg/migration/migration.proto:对外暴露的 ttrpc 迁移服务接口定义
实时迁移的完整流程:源端与目标端如何协作
一次 hcsshim实时迁移由**源端(SOURCE)和目标端(DESTINATION)**两套 shim 协同完成,调用顺序如下:
| 步骤 | 源端 shim | 目标端 shim |
|---|---|---|
| 1 | PrepareAndExportSandbox(初始化迁移、导出快照) | 等待 |
| 2 | 等待 | ImportSandbox(导入快照,重建 shim 状态) |
| 3 | 等待 | NewTask(逐容器修补资源路径) |
| 4 | 等待 | PrepareSandbox(创建 HCS 计算系统,但不启动) |
| 5 | CreateDuplicateSocket | CreateDuplicateSocket |
| 6 | TransferSandbox(内存传输) | TransferSandbox(内存传输) |
| 7 | FinalizeSandbox | FinalizeSandbox |
| 8 | Cleanup | Cleanup |
可以看到,两端先各自"备料",再在传输阶段汇合,最后按各自角色收尾。整个调用契约定义在pkg/migration/migration.proto的Migration服务中。
关键机制一:不透明快照与资源路径修补
迁移的第一步是导出状态。源端调用PrepareSource让 HCS 把正在运行的虚拟机置为"migrating"状态(此后不再接受普通修改),再由ExportState收集完整的内存态快照。
有意思的是,这个快照对调用方是不透明的:ExportState把 VM 状态和每个 Pod 状态各自打包成google.protobuf.Any包络,再统一塞进一个带schema_version的版本化外壳(见internal/controller/migration/save/payload.proto),由调用方原样转交给目标端。好处是各控制器可以独立演进版本,互不干扰。
目标端拿到快照后调用ImportState重新"水合"(rehydrate)shim 状态。但此时快照里的资源路径还指向源主机的磁盘路径,必须通过PatchResourcePaths逐容器修补——每个容器执行一次NewTask,把源容器 ID 重绑为目标 ID,并修正 VHD 等资源的路径。只有全部容器修补完成,PrepareDestination才被允许创建目标端的 HCS 计算系统(注意:创建但不启动,启动要留到传输阶段)。相关实现见internal/controller/migration/controller_destination.go。
关键机制二:套接字复制与内存传输
两端"备料"完成后,就进入最核心的内存传输阶段。这里有一个非常巧妙的设计:套接字复制(Socket Duplication)。
调用方先在每台宿主机上创建迁移传输套接字,然后把序列化的WSAProtocolInfo描述符交给 shim。shim 调用WSASocket在自己的进程内重建出一个重复的套接字句柄(见internal/controller/migration/socket.go),并用SO_CONNECT_TIME验证它确实已连接。
为什么非要"复制"而不是直接用原套接字?注释给出了答案:把传输通道与 containerd 进程的生命周期解耦。一旦重复套接字就绪,即使 containerd 中途崩溃,shim 也能独立把迁移跑完,这大大提升了容错性。
随后TransferSandbox在两端同时发起,基于这个套接字流式搬运虚拟机内存,直到目标端内存镜像追平源端。会话 ID 还会通过 SHA-256 映射成一个稳定的 32 位整数,确保两端对同一次会话的识别完全一致。
迁移状态机:一次会话如何精确推进
如此复杂的多阶段流程,如果没有严谨的状态管理极易出错。hcsshim 为此实现了一个显式的迁移状态机,定义在internal/controller/migration/state.go,源端与目标端走两条路径、最终汇合:
源端: Idle → SourcePrepared → SourceExported ─┐ ├→ SocketReady → Transferring → TransferCompleted → Finalized → Idle 目标端: Idle → DestinationImported → DestinationPrepared ─┘- 如果
Transfer在套接字注册前就调用,会进入SocketWaiting状态后台等待(默认超时 10 分钟) - 传输由后台 goroutine 驱动,调用方立即返回,进度与结果通过事件订阅通知,而不是阻塞在 RPC 上
状态机的核心价值在于每个状态只允许特定调用:比如只有DestinationImported状态才能做资源修补,只有所有容器修补完才能PrepareDestination,非法调用会直接返回FailedPrecondition错误,把错误尽早暴露。
失败与取消:迁移的安全网
实时迁移的价值一半在成功路径,另一半在失败处理。hcsshim 的收尾语义非常讲究——同一个Finalize动作在两端含义截然不同:
| 动作 | 源端 | 目标端 |
|---|---|---|
| STOP | 迁移成功,拆除源端计算系统、释放资源 | 迁移失败/取消,丢弃目标端计算系统 |
| RESUME | 迁移失败/取消,回滚并在源端继续运行 | 迁移成功,在目标端恢复运行 |
也就是说,"停止"和"恢复"由两端按自己角色反向理解,配合Cancel(中止进行中的迁移)与Cleanup(无论成败都执行的最终清理),任何异常路径都能收敛回Idle状态。此外,迁移过程中的进度、错误、超时、完成等事件会通过流式Notifications推送给订阅者(见internal/controller/migration/notifications.go),让上层容器编排系统可以实时感知迁移状态。
总结
hcsshim实时迁移的精彩之处,在于它把"正在运行的整台虚拟机连容器一起搬走"这件复杂的事,拆解成了源端导出、目标端重建、套接字复制、内存传输、双端收尾这样一套职责清晰、状态严谨的流程。不透明快照让组件独立演进,套接字复制带来抗崩溃能力,而精确定义的 STOP/RESUME 语义则让成功与失败路径都能优雅收场。理解了这些机制,你就掌握了 Windows 容器在跨主机无缝移动场景下的底层运作逻辑。
【免费下载链接】hcsshimWindows - Host Compute Service Shim项目地址: https://gitcode.com/gh_mirrors/hc/hcsshim
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考