跳转到主要内容

PID 不是虚拟机身份

为什么 Barn 在发送信号或删除任何东西前,必须让 QMP 身份、进程证据、类型化启动参数与操作日志相互吻合。

本文阐述 Barn 1.1.0 的 Linux 运行时进程身份与崩溃恢复机制。原生 Mac 生命周期独立使用逐实例操作锁与 runner 协议 2。

pidfile 只能记录写入瞬间的进程号(PID)。几分钟甚至几天之后——尤其在经历过进程崩溃或宿主重启后——单凭这一个整数,既无法确认原先的 QEMU 进程是否还活着,也无法排除操作系统内核已经把同一个 PID 分配给了另一个毫不相干的进程,更无法证明它归属哪一个虚拟机节点。

如果仅凭 pidfile 就直接发送 SIGKILL 或删除磁盘,一旦遇到 PID 复用就会误杀宿主进程或毁掉正在运行的节点根盘。因此,Barn 绝不单凭 PID 授权发送信号或删除文件;所有破坏性操作都必须在多项独立身份证据完全吻合后才会执行。

QMP 是首要运行时身份

每台 Barn Linux 虚拟机都会分配一个唯一 UUID 与预期的 QEMU 实例名称(barn-<node>)。拉起 QEMU 进程后,Barn 会立即连接该节点的 QEMU 机器协议(QMP)Unix 套接字并查询这两个字段。只有当 QMP 返回的名称与 UUID 与预期完全一致时,Barn 才会判定虚拟机进程启动成功。

在每次关机或状态变更前,Barn 都会重复这一 QMP 身份核验:

  1. 通过 QMP 查询当前实例的名称与 UUID;若返回身份不匹配,立即中止操作,绝不向该套接字发送任何控制指令。
  2. 对已验证身份的虚拟机,先通过 QMP 发送客机 ACPI 正常关机指令(system_powerdown);等待超时后再通过 QMP 发送 quit;仅当 QMP 自身已无法通信时,才转入进程信号备用路径。
说明

遇到操作中断或身份校验报错时,请先按故障排查指南处理,不要手工删除 ~/.barn 下的文件。

用进程四元组封死信号备用路径的竞态缺口

如果 QEMU 异常崩溃、临时套接字目录被清理或关机过程在半途中断,QMP 套接字可能无法响应。为了在没有 QMP 时依然能安全使用进程信号,Barn 在启动虚拟机时会将完整的类型化 QEMU 启动参数与一组 进程身份四元组 一同持久化:

  • 进程 ID(PID)
  • 解析后的可执行文件绝对路径
  • 操作系统记录的进程启动时间戳
  • 实际命令行参数(argv)的 SHA-256 摘要

发送 SIGTERM 前,Barn 会读取宿主当前进程表,要求目标进程的上述四项属性与记录完全一致。如果进程在 SIGTERM 等待窗口内仍未退出,Barn 在升级发送 SIGKILL 前会 第二次 重新采集并核对这组四元组——从而彻底堵上“等待期间原进程退出、PID 恰好被新进程复用”的竞态窗口。

当 QMP 无法连接、且宿主进程表证实原 PID 已不存在或已属于其他无关进程时,Barn 会安全地将该节点标记为 stopped,绝不触碰那个无关进程。反之,如果 QMP 仍有响应,或者进程四元组因权限/系统状态无法明确读取,Barn 会直接报错停止,绝不盲目发送信号。

用预备操作日志追踪状态提交前的文件

启动一台新虚拟机之前,Barn 需要先在磁盘上创建写时复制根盘、数据盘与 cloud-init 初始化介质;而只有在 QEMU 进程启动并通过 QMP 核验后,节点的正式状态记录(state.json)才会提交。如果进程在这两步之间被强制中断(SIGINT/SIGKILL),磁盘上就会留下一批尚未写入正式状态的半成品文件。

为了在不靠盲目扫描目录的前提下精准回收这些文件,Barn 在准备节点文件前会先写入一份权限为 0600、带严格版本校验的 JSON 预备操作日志(Prepare Journal):

  • 本次操作 UUID 与目标虚拟机 UUID
  • 节点名称与解析后的规格哈希
  • 按创建顺序记录的已完成产物列表(严格受限于固定的 (kind, path) 白名单)
  • 磁盘准备完成后的类型化 QEMU 启动参数
  • 状态提交成功后的正式节点状态文件路径

在恢复阶段,Barn 会严格校验日志格式、UUID、文件权限、无符号链接及目录边界,仅按日志记录精准清理未提交操作产生的文件。

收窄的离线回滚与保守销毁

针对未提交节点的离线回滚,必须同时满足以下三项前置条件才会执行:

  1. 该节点尚不存在已提交的正式状态文件。
  2. 操作日志所记录的 QMP 套接字与 pidfile 均不存在。
  3. 节点目录中除操作日志及其白名单记录的已完成产物外,没有任何其他未知文件。

三项条件全部满足时,Barn 按创建逆序依次删除未提交产物、操作日志与空节点目录。这一设计让你在首次 barn up 失败后可以直接修改 barn.yml 并安全重试,同时从机制上保证陈旧的操作日志绝不可能回滚已提交的节点,也不会误删原有的持久数据盘。

常规 barn destroy 同样遵循这套保守原则:先核验路径包含关系、文件属主、进程已终止及白名单产物集合,确认所有已知文件删除完毕后才移除空的节点目录;只要目录中存在任何未知文件,就会立即停下报错,将现场留给操作者检查。

原子状态落盘

所有状态更新均先写入同目录下的临时文件,依次执行文件 fsync、原子 rename 与父目录 fsync,并由部署锁和节点锁保证串行互斥。无论在任何时刻发生崩溃,磁盘上的正式状态要么是旧记录、要么是新记录,而两者之间的短暂过渡窗口则由预备操作日志完整覆盖。同样,在线 barn resize 通过 QMP 热插拔 CPU 或内存之前,会先在节点状态中持久化 resize_incomplete: true,并在 QMP 变更与新规格状态落盘后清除该标记,使 barn status(stage: "resize"、reason: "resize_incomplete")及后续 up、start 或 resize 能准确识别并恢复半途中断的在线扩容。

下一篇:repo.yaml 是意图,catalog.json 是证据。