node 与 VMM 进程
本页解释 node、VMM、数据面进程与出口进程在 systemd 中的进程模型,以及替换 node 程序前的两个检查命令。 升级与卸载的完整步骤见重装与升级。
概述
- 宿主机需要 systemd、cgroup v2 和 KVM。node 以 root 运行。
- 每个 Instance 的 VMM 由 node 启动为一个独立的 systemd transient service。Firecracker 与 KiboVM 两个后端都这样运行。
- exec、terminal、文件传输和端口转发的连接由数据面进程持有,它也是 node 启动的独立服务。停止、重启或升级 node 时,VMM 与数据面进程都继续运行。
进程模型
node 服务
- node 的服务单元使用
Restart=on-failure、RestartSec=5s、KillMode=control-group和SendSIGKILL=yes。systemd 停止旧 node 的 cgroup、清理它的辅助进程之后,新 node 才能启动。 - node 的服务单元设置
OOMScoreAdjust=-900:宿主内存耗尽时,内核先杀 VMM,不杀 node。node 自己启动的短期工具(mkfs、guestfish等)继承这个值。不使用打包的服务单元时,自己写的单元文件也要加上它。 - node 启动时先检查自己的 MainPID、cgroup 和有没有残留进程,然后获取状态目录的独占锁。
- 文件系统工具
guestfish与virt-ls固定使用LIBGUESTFS_BACKEND=direct,它们的辅助 VM 留在 node 服务的 cgroup 内,不经外部 libvirt 启动。
VMM 服务
- VMM 的服务单元使用
Type=exec、Restart=no、KillMode=control-group、OOMPolicy=kill,位于kibotos-vmm.slice(父 slice 为kibotos.slice)。OOMPolicy=kill让内核在杀掉单元里任一进程时一并杀掉整个单元,与 VMM 共享 guest 内存的辅助进程不会拖住内存。升级前已经在运行的 VMM 保持原设置,下次启动时生效。 - VMM 服务与 node 服务之间没有停止依赖:停止或重启 node 不影响 VMM。宿主机关机时 VMM 由 systemd 的默认关机顺序终止。
- Firecracker 的服务以 root 启动 jailer,jailer 再切换到 VMM 账户。KiboVM 的服务直接以非 root 的 VMM 账户运行,并带
kvm附加组。 - KiboVM 的服务设置
LimitMEMLOCK=infinity。较新的内核(如 7.0)把 io_uring 的 ring 内存计入所属用户的锁定内存,而所有 VMM 都以同一个 VMM 账户运行;沿用 systemd 默认的 8 MiB 时,整台宿主只能启动二十几台 VM,之后的 VM 启动失败,kibovm.log报Out of memory (os error 12)。这项限制由 node 在服务单元上设置,宿主的limits.conf与 systemd 的DefaultLimitMEMLOCK不需要修改。Firecracker 不使用 io_uring,保持默认值。 - 服务使用 node 预先创建的网络命名空间,并设置
PrivateMounts=no。文件与设备隔离由各后端自己的沙箱完成;jailer 不再创建或迁入其他 cgroup。
数据面进程
数据面进程(data-plane process)监听 node 配置的数据面地址,持有全部数据面连接。它运行同一个 kibotos-node 可执行文件,由 node 启动,每次启动得到一个递增的代号。
-
服务单元名是
kibotos-dataplane-<作用域>-<代号>.service,位于kibotos-dataplane.slice,使用Restart=no、KillMode=control-group与OOMScoreAdjust=-900。它与 node 服务之间没有停止依赖。作用域由 node 的 runtime 目录决定,同一台宿主上的多个 node 互不影响。 -
列出全部数据面进程:
systemctl list-units 'kibotos-dataplane-*' -
数据面进程以 root 运行:两个后端的 guest socket 只对 root 与 VMM 账户开放。
-
它的本地 socket 在 socket 目录(默认
/run/kibotos/node)的dataplane/下,不读写 node 的状态目录。target、验证公钥与 TLS 证书都由 node 下发。 -
node 先把新的 target 与验证公钥交给数据面进程,再回复 ctrl,所以 ctrl 签发的会话凭证一拿到就能用。数据面进程 5 秒内没有确认时,node 照常回复,并记一条警告
data-plane generation did not take the node's state before the reply。在 node 下一次重发之前(至多 15 秒),用到新 target 或新公钥的请求会被拒绝。 -
node 的
/metrics合计各个数据面进程的指标。一个数据面进程退出后,它的计数仍计入 counter,counter 不会变小。
node 启动时按正在运行的数据面进程决定怎么做:
| 正在运行的数据面进程 | node 的做法 | 已有连接 |
|---|---|---|
| 与 node 是同一构建,监听地址与 TLS 设置相同 | 直接接管 | 不受影响 |
| 同一发布线上的其他构建,或 TLS 设置变了 | 启动新的数据面进程,新进程接过监听 socket;旧进程不再接受新连接,等已有连接结束后退出 | 不受影响。交接期间新连接也不被拒绝 |
| 同一发布线上,但监听地址变了 | 新进程绑定新地址;旧进程在原地址上不再接受新连接,等已有连接结束后退出 | 不受影响 |
不在同一发布线上(跨 MINOR,或涉及 -alpha.N、-beta.N 与未发布构建) | 结束旧进程,再启动新进程,由新进程绑定监听地址 | 断开,可以重新建立 |
| 没有 | 启动新进程 | — |
注意:
- 等待已有连接结束的旧进程最多保留一个。再换一次时,node 结束更早的那一个,它上面剩下的连接断开。
- node 停着时,数据面进程按 node 最后下发的 target 继续服务,也接受新连接。这期间 ctrl 的停止、删除与撤销送不到 node,已签发的会话凭证在有效期内仍然可用,node 恢复后这些操作才生效。要立刻切断访问,停止全部数据面进程。
- 数据面 TLS 证书由 node 续期。node 停机期间证书过期时,新连接握手失败,已建立的连接不受影响。
- 数据面进程意外退出时,它持有的连接断开,node 在几秒内启动新的数据面进程。node 停着时没有人启动新的,等 node 恢复。
- 停止数据面进程时,它先给已有连接 10 秒自行结束,再关闭剩下的连接,并在日志里记下关闭了多少条。
出口进程
出口进程(egress process)转发 guest 经调用方 SOCKS5 出口发出的 TCP 连接,并替引用了调用方 Resolver 的 Workspace 把 DNS 查询转给那个上游。它与数据面进程一样运行同一个 kibotos-node 可执行文件,由 node 启动,按代号换代。
-
服务单元名是
kibotos-egress-<作用域>-<代号>.service,位于kibotos-egress.slice,使用Restart=no与KillMode=control-group,与 node 服务之间没有停止依赖。 -
只在这台 node 上有 Workspace 的网络策略引用 Egress 或 Resolver 时运行。不引用 Resolver 的 Workspace 的 DNS 查询由内核转给部署 resolver,不经过它。策略不再引用 Egress 后,它继续持有仍在排空的连接,最后一条结束或到期后 node 结束它。
-
它以系统用户
kibotos-egress运行,不带任何 capability,也不能提权:文件系统只读,只能写自己的 socket 目录,看不到 node 的状态目录。需要特权的 socket 由 node 创建后交给它,出口凭证也由 node 经本地 socket 下发,只留在内存里。 -
socket 目录是 socket 目录旁边加
-egress后缀的目录,默认/run/kibotos/node-egress,属主kibotos-egress、模式0700。代号计数在<storage.root>/state/egress-generation。 -
出口凭证由 node 用自己的身份向 ctrl 按 Egress revision 取回,每个 revision 一个文件,放在
<storage.root>/state/egress-credentials/,目录0700、文件0600,只有 root 能读。Workspace 切到新 revision 或离开这台 node 后,不再被引用的文件随即删除。node 重启时只用这些文件恢复出口,不访问 ctrl。 -
列出全部出口进程:
systemctl list-units 'kibotos-egress-*' -
直出的 Resolver 上游由出口进程从宿主的网络命名空间以
kibotos-egress用户连接,按宿主路由离开。部署方在宿主上限制 node 自身出站时,这些连接一并受限。上游写成主机名时,出口进程用 node 配置的 resolver 列表依次解析,不使用宿主的/etc/resolv.conf。 -
node 重启时接管正在运行的出口进程,已有连接不受影响。node 换成同一发布线上的其他构建时,启动新的出口进程接受新连接与新查询;旧进程不再接受新连接、不再读取新查询、丢弃凭证,已经收到的查询在 4 秒内答完,已有连接按 node 下发的截止时间继续或关闭,全部结束后退出。跨发布线时旧进程被结束,连接断开。
-
同时最多有两代出口进程。上一次换构建留下的旧进程还在排空时再换一次构建,最老的那一代被结束,它持有的连接随即断开,日志
egress generation ended的reason是a newer generation is about to drain。要连续换构建又不断开连接,先用上面的命令确认只剩一个出口进程。 -
guest 的连接由透明 listener 接受。listener 是 node 创建的,属于 node 服务单元的 cgroup,这个归属在 listener 存续期间不变。所以给
kibotos-node.service加的 systemd IP 访问限制(IPAddressDeny=、IPAddressAllow=)也作用于经出口转发的连接,会把它们一起挡住。需要这类限制时,放行ipv4_pool与ipv6_pool;不要用它来切断 node 与 ctrl 的连接。 -
出口进程意外退出时,经出口的连接断开,node 在几秒内启动新的出口进程。其间被引到出口的新连接失败,不改为直出;引用 Resolver 的 Workspace 的查询得不到应答,其余 Workspace 的解析不受影响。
启动记录与接管
- 启动 VMM 之前,node 在 Instance 目录中写入
systemd.json,记录本次启动的唯一标识、服务单元名和预期命令。启动之后补上 InvocationID、MainPID、进程启动时间、可执行文件身份、cgroup 和网络命名空间。 - 服务启动成功后,还要通过后端与 guest 的就绪检查,Instance 才算可用。node 记下这时认领的 guest:它的启动标识与声明的能力。
- 启动记录还固定了 VMM 使用的 runtime 安装(runtime installation)的身份。runtime 安装位于
<storage.root>/installations/sha256-<身份>/,目录里的installation.json描述它自己。 - node 重启时把这些记录与实际运行的服务逐一比对,接管匹配的 Instance。启动响应丢失时查询原来的启动记录;记录与实际不符,或存在没有记录的服务时,node 停止启动。
- 接管运行中的 Instance 不等 guest 回应。存储、网络与 VMM 接回后 Instance 就照常运行,数据面按记下的 guest 继续服务:
- node 在后台把启动时的配置与本次的 DNS 再交给 guest。guest 暂时不回应就按退避一直重试,期间 Workspace 仍是
AVAILABLE,guest 按不再回应报告。 - node 离线期间重启过的 guest,按启动时的配置重新配置。
- guest 回应但拒绝,例如协议版本不在本版本支持的范围内,Instance 标为
FAILED,VMM 继续运行。
- node 在后台把启动时的配置与本次的 DNS 再交给 guest。guest 暂时不回应就按退避一直重试,期间 Workspace 仍是
- 网络接回时,node 按记录的网络策略用当前版本重新生成该 Instance 的防火墙规则,在一次 nftables 事务中整体替换,已建立的连接不断开。上一版 node 启动的 Instance 的规则可能与当前版本不同,接管时一并改写,日志
slot table rewritten from the recorded network policy记下 Instance、命名空间与差异。 - 下列情况 node 不改写规则,而是隔离该 Instance 的网络并把它标为
FAILED,VMM 继续运行:- Instance 的网络命名空间里没有 node 的规则表,或有不属于 node 的 nftables 表;
- 规则表里的地址集合与记录或当前配置不符,resolver 地址除外。例如改了
management_cidrs,或上联地址变了; - 有手工添加的规则或链;
- 当前版本无法执行记录的策略,例如 node 不再提供公网出口。
- 重启时还没创建完的 Instance 例外:它的 guest 从没通过就绪检查,接管时仍等它就绪,超时即创建失败。
- 新启动的 Instance 总是使用当前版本自带的 runtime 安装。上一版 node 启动的 Instance 可能记录着更早的安装,node 照常接管它,接管时使用它记录的那个安装。条件是:
- 该安装目录完整,文件摘要与
installation.json一致; - 它的 runtime 版本在当前 node 仍能驱动的范围内。范围是当前版本固定的 runtime,加上同一发布线上更早版本和上一个 MINOR 固定的 runtime。
- 该安装目录完整,文件摘要与
- 条件不满足时 node 无法接管该 Instance:Instance 被标记为
FAILED,它的 VMM 继续运行。处理方法见排查。 - Workspace 停止后再次启动,就换到当前版本的 runtime。node 每次启动时删除没有任何 Instance 使用的旧安装目录;接管失败的 Instance 仍然记录着的安装目录会保留。
- 删除 Instance 时先停止对应的服务,确认整个 cgroup 已空,再回收挂载、网络和文件。
- 删除一个未被接管、但服务仍在运行的 Instance 时,node 先核对归属:启动记录、服务绑定、node 自己的 Instance 记录和实际服务必须指向同一个进程。全部一致时 node 停止该服务再回收;任何一项不一致,删除失败,服务保持运行。
- 停止 node 不等于删除 Instance,也不会设置或取消 drain。
排查启动失败时,保留原有记录,查看 systemctl show <unit>、journalctl -u <unit> 和保留的控制台与 VMM 日志。不要删除绑定记录、修改 PID 或按名称批量结束进程,这些操作会让 node 失去判断进程归属的依据。
已结束 Instance 的记录
Instance 结束后,node 在 <storage.root>/runtime/registry/ 留着它的记录,直到 ctrl 确认不再需要。
- ctrl 每分钟检查一次每台 node 上的记录。Instance 的 Allocation 已释放、所属 Workspace 没有处理中的命令、Workspace 也不再以它为当前 Instance 时,ctrl 先存下 node 观测到的清理结论,再让 node 删除记录,每台 node 每次最多 100 条。
- 记录删除之后,
kibo workspace cleanup-status读 ctrl 存下的结论。运行时几项的状态不变,观测时间是 node 观测到清理完成的那一刻。 - Volume、可写层与 Template 准备命令记在同一目录的
volume-commands.json,每条保留 24 小时。
不要手工删除记录:清理结论存下之前删掉,这个 Workspace 的清理状态里运行时几项会显示为未知。
中断的写入留下的临时文件
node 先把记录、块文件与 Transfer 归档写到临时文件,写完再改名。中途退出时临时文件留在原处,任何版本都不读取它们。池里的临时文件名以写入它的 node 的 ID 开头,v0.1.x 写的不带:
| 位置 | 例子 |
|---|---|
| node 本地目录 | registry/.record-2826134621.tmp |
| 池里 | volumes/data/.node-a.import-1438876543.tmp、volumes/transfer-staging/.node-a.download-import-3170412187.tar,以及从另一个池复制对象时的 volumes/data/.node-a.move-vol-06gdxke0wxw4xezwfa185wp24w-2004318071.tmp |
池里,v0.1.x 写的 | volumes/data/.import-1438876543.tmp |
node 启动时删除这些文件:
| 位置 | 删除哪些 |
|---|---|
| node 本地目录 | 全部 |
| 独立池 | 全部。启动时不可用的池,等它变为可用时删除 |
| 共享池 | 只删以本 node 的 ID 开头的。别的 node 的可能正在写 |
共享池里另外两种要手工删除:
- 不带 node ID 的:先停止声明这个池的所有 node。
- 以已永久移除的 node 的 ID 开头的:那台 node 不再运行之后。
控制台与 VMM 日志
Instance 结束时,node 在删除运行时目录之前,把 guest 控制台与 VMM 日志的末尾复制到 <storage.root>/machine-logs/<instance_id>/,node 日志只写一条 instance machine exited。保留的范围、配额与读取方法见日志。
替换程序前的检查:check-maintenance
替换 node 程序不需要停止 Workspace:VMM 独立运行,新的 node 启动后按记录接管它们。目标版本换了 VM runtime 时也一样,按旧 runtime 启动的 Instance 继续使用旧的安装。只有目标版本驱动不了的 Instance 需要先停止,由目标版本的 check-upgrade 逐个列出。
停止 node 服务后,用当前已安装的 node 执行维护检查:
sudo kibotos-node check-maintenance --config /etc/kibotos/node/config.toml --operation upgrade
sudo kibotos-node check-maintenance --config /etc/kibotos/node/config.toml --operation remove| 操作 | 允许的状态 |
|---|---|
upgrade | 允许 Instance 继续运行,前提是每个 Instance 的记录都能与实际服务对上:服务单元、InvocationID、PID、启动时间、可执行文件的路径与 dev:ino、cgroup、网络命名空间全部一致,且没有进行到一半的网络切换、目标轮换或停机。允许保留已解绑的 Volume 和保留的可写层,包括扩容还没有完成的 Volume |
remove | 要求没有运行中的 Instance,也没有已解绑的 Volume 和保留的可写层 |
两种操作都不检查、也不结束数据面进程。卸载 node 包时,包脚本在 --operation remove 的检查通过之后结束全部数据面进程。
两种操作对宿主机残留的要求相同:Instance 目录、后端状态目录、沙箱目录、网络记录和挂载中的每一项都必须属于某个已被接管的 Instance。找不到归属的残留会导致检查失败,因为新程序既不能理解它们,也没有人负责清理它们。
注意:
- 配置、PKI、已安装的 runtime 和可重建的缓存可以保留。Firecracker 的共享沙箱根目录只有在它是 root 所有、权限 0700 的 tmpfs,且其下只有已接管 Instance 的目录时,才算可保留的缓存。
- 中断的写入留下的临时文件不算残留,检查不因此失败,见中断的写入留下的临时文件。
- 检查只读取配置、独占锁、持久记录和进程信息,不修复状态、不递增启动序号、不创建目录。
- 配置缺失、记录损坏、辅助进程仍在运行或归属不明,都返回非零退出码。
- 存储池里持有者记录写着别的 node 的对象不让检查失败,不论池是否共用。换过宿主的池因此可以正常升级,见池换宿主。
- 这个检查只回答「当前资源是否静止且有归属」。新版本能否读取这些记录,由新版本自己的
check-upgrade回答。
替换前用目标程序检查
check-maintenance 由当前版本执行,它不知道新版本能否读取磁盘上的记录。按准备目标版本的程序把新版本解到安装目录之外,所有服务停止后,用新版本的二进制执行:
sudo /tmp/kibotos-next/usr/bin/kibotos-node check-upgrade --config /etc/kibotos/node/config.toml --runtime-dir /tmp/kibotos-next/usr/lib/kibotos/node它使用与 upgrade 相同的静止判据,并额外检查四件事:
- node 身份、注册记录和启动记录的格式版本都在新版本能读取的范围内。
- 目标 runtime 目录中有新版本要求的 VM runtime 可执行文件。
- 每个仍在运行的 Instance 使用的 runtime 安装,新版本都能保留并驱动:要么就是新版本将使用的安装,要么该安装目录完整,且 runtime 版本在新版本能驱动的范围内。
- 每个仍在运行的 guest 通过自己的 VMM 响应
GuestInfo,其协议版本在新版本接受的范围内。
报告逐项列出阻塞对象与原因;有阻塞项时返回非零退出码。
注意:
- 运行中的 guest 缺少新版本新增的能力不算阻塞。这样的 Instance 照常被接管,只是用到该能力的操作在它重启前会失败;报告会列出这些能力。
- guest 协议版本超出范围算阻塞。对应的 Workspace 必须先正常停止。
- 运行中的 Instance 使用旧的 runtime 安装本身不算阻塞。报告写明 Instance、它使用的安装和 runtime 版本,以及它下次启动时换到的安装。
- 旧的安装目录损坏或缺失,或者它的 runtime 版本超出新版本能驱动的范围,才算阻塞。对应的 Workspace 必须先正常停止;再次启动时使用新的 runtime。
- 检查是只读的:不创建目录、不递增启动序号、不迁移数据、不修改 guest。
- 检查结果只描述当次停机时刻的状态。任何写状态的进程重新启动过,就需要重新停机、重新检查。
停机之前先用 --preview 问一遍
上面那条命令要求 node 已经停止:它和 check-maintenance 一样要取 node 的独占锁,服务还在跑时只会报「runtime 已被占用」。加 --preview 时,它在仍在运行的 node 上判定与宿主是否静止无关的几项:本地记录的格式版本、目标 runtime 目录、运行中 Instance 使用的 runtime 安装,以及运行中 guest 的协议版本与能力。判据与停机后逐条相同,所以预检就能回答需要先正常停止哪些 Workspace。何时、怎样执行见停机前预检的 node 一节。
报告分三类:
| 前缀 | 含义 |
|---|---|
ok: | 已取得答案且满足要求 |
blocked: | 已取得答案但不满足要求。退出码非零,对应的 Workspace 要在窗口之前处理 |
pending: | 因为 node 还在工作而拿不到答案:记录正在改写、Instance 正在启动或停止、guest 未应答。不影响退出码,停机后重新判定 |
预检不取独占锁、不写任何文件,可以随时重复执行。它不判定静止与归属——辅助进程、无主残留、宿主挂载、staging 目录、Volume 与可写层的归属都不在结论里,报告末尾会重复这一点。它不替代停机后的完整检查:替换之前仍然要在停止服务后执行不带 --preview 的同一条命令。
排查
| 现象 | 原因 | 处理 |
|---|---|---|
升级后 Instance 变为 FAILED,错误为 recover instance process: ... compatible runtime installation unavailable,VMM 服务仍在运行 | Instance 按旧的 runtime 安装启动,而新 node 保留不了这个安装:目录损坏或缺失,或 runtime 版本超出新 node 能驱动的范围。node 启动日志里的 earlier runtime installation cannot be retained 写明了具体原因 | 用 kibo workspace stop --force <workspace> 回收:node 核对归属一致后先停止旧的 VMM 服务,再释放资源。Workspace 进入 STOPPED,Volume 与可写层保留,之后可以再次 start。升级前执行 check-upgrade 可以提前发现这种情况 |
回收上述 Instance 失败,错误包含 stop instance process this node did not take over | 启动记录、服务绑定、node 记录与实际服务至少有一项对不上,node 无法确认该服务属于这个 Instance | 保留 Instance 目录中的记录,用 systemctl show 与 journalctl -u 查看错误里提到的服务。确认它就是该 Instance 的 VMM 后,只停止这一个单元,再用同一个 --command-id 重试原来的命令 |
node 重启后一批 Instance 变为 FAILED,失败阶段为 PREPARING_RETAINED_LAYER 或 ATTACHING_VOLUME,错误包含 storage pool is unavailable,VMM 服务仍在运行 | 接管时存储池不可用。node 启动时先等这些 Instance 所在的池完成首次打开,最多等 5 分钟;池在这段时间里没有打开,或打开后报告不可用,接管就失败。启动日志里对应有 storage pool not opened for takeover,或这个池的 storage pool availability changed 变为 unavailable。每个 Instance 只接管一次,池之后恢复也不会重试 | 先按 storage pool availability changed 的 reason 修好存储。再从 instance takeover failed 记录取出 instance_id,用 kibo workspace list --instance <instance_id> 找到 Workspace,执行 kibo workspace stop --force <workspace> 后再 start。存储修好后 ctrl 最多 1 分钟才看到池恢复,在此之前 start 报 no admitted node is ready,稍后重试即可。guest 内的进程与内存状态不保留,Volume 与可写层保留 |
node 重启后日志反复出现 instance guest sync pending,Workspace 仍是 AVAILABLE,guest 显示为不再回应 | node 接管了 Instance,但 guest 不回应,还没能按本次重启重新配置它。重启前就已卡住的 guest 常见这种情况,例如内存耗尽或 CPU 被占满 | 不用处理:guest 恢复回应后 node 自动完成,同名记录带 recovered=true。一直不恢复时用 kibo workspace stop --force 收回,再启动 |
Workspace 停止失败并停在 FAILED,错误以 prepare guest stop: 开头,例如 deadline_exceeded、retained filesystem sync failed 或 retained filesystem freeze failed,VMM 服务仍在运行 | guest 没有在期限内结束受管进程,或者同步、冻结可写层与 Volume 失败,node 不能确认数据已经落盘,所以不停止 VM。同步或冻结失败后,guest 让这些文件系统保持冻结,不再接受写入 | 用 kibo workspace stop --force <workspace> 停止它:node 请 guest 做一次短时间同步后停止这个 VMM 并释放资源,Workspace 进入 STOPPED,Volume 与可写层保留。结果标为未经落盘确认。node 上已经没有这条 Instance 时(例如 node 重启后没有接管它),ctrl 会自行释放资源,Workspace 仍是 FAILED,可以直接 start |
VMM 意外退出(被杀、崩溃等)后 Workspace 停在 FAILED,错误为 node reports the Instance as no longer running;start 被拒绝,提示状态不允许。宿主 OOM 杀掉的 VMM 不属于这种情况:它的 Workspace 由 ctrl 自行停止,直接 start 即可;只有 OOM 发生在 node 停机期间、systemd 已经清掉了那个 VMM 单元时,才会停在这里 | node 保留了这条已失败的 Instance,它仍占着 Volume、网络与资源预留,只有销毁才释放。旧 Instance 可能还占着存储时,ctrl 不启动新的执行 | 用 kibo workspace stop --force <workspace>:ctrl 让 node 销毁这条 Instance、释放资源,Workspace 进入 STOPPED,Volume 与可写层保留,然后再 start。结果标为未经落盘确认 |
停止或删除后 Instance 长期停在 STOPPING 或 DESTROYING,node 日志反复出现 instance cleanup pending | VMM 进程卡在不可中断的等待里,systemd 的停止任务超时后 unit 已经结束而它的 cgroup 仍有进程。多数情况下它在等存储:块文件所在的挂载不响应 | 不用管 node,它会一直重试;先修存储。进程消失后 node 自己完成释放并进入终态。kibo workspace cleanup-status <workspace> 显示释放从哪一刻起欠着、哪些部分仍被持有 |
check-maintenance 或卸载 node 包被 is active or requires cleanup 拒绝,而那条 Instance 停在 FAILED 且仍挂着 Volume | 旧版本把一次没做完的释放判成了失败,从此不再重试 | 先用控制面删除对应的 Workspace,让旧 node 完成释放,再执行检查或卸载。换到本版本之后这类记录会自行收敛,不再需要这一步 |
| node 停着时新的数据面连接 TLS 握手失败,已有连接正常 | node 停机期间数据面证书过期,数据面进程没有新证书可用 | 启动 node。node 续期证书后下发给数据面进程,新连接恢复 |
systemctl list-units 'kibotos-dataplane-*' 里有两个数据面进程 | 一个在服务,另一个在等已有连接结束 | 不用处理。等待的那个在连接都结束后退出,再换一次时会被结束 |
升级 node 包时 dpkg 报 old kibotos-node package pre-removal script subprocess returned error exit status 1,随后 trying script from the new package instead,最后安装成功 | 旧版本的包在升级时用旧二进制做资源检查。它拒绝后,dpkg 改调新包的脚本,新包升级时只要求服务已停止 | 这是预期行为,不是检查被绕过。新版本能否接管,看替换前目标版本 check-upgrade 的结论 |