主题
04 · 运维手册:虚拟机迁移与高可用
环境:Harvester v1.8.2(Helm on RKE2)、KubeVirt
1.7.4-150700.3.24.2、Longhorn 1.11.2、3 节点。 结论:本环境当前只支持冷迁移(已实测通过),热迁移被 Longhorn RWX 数据面阻塞(已定位)。
1. 能力矩阵(本环境实测)
| 能力 | 状态 | 前置条件 | 实测证据 |
|---|---|---|---|
| 冷迁移(停机 → 换节点启动) | ✅ 可用 | RWO+Filesystem 卷、目标节点可调度 | sza122101 → sza122102,VMI Running,Longhorn 卷 attached/healthy,新 virt-launcher |
| 热迁移(不停机换节点) | ❌ 不可用 | 所有 PVC 为 RWX + Longhorn RWX 数据面可用 | KubeVirt DisksNotLiveMigratable;Longhorn RWX 卷无 shareEndpoint、无 share-manager Pod,CDI qemu-img 写盘失败 |
| 节点维护模式 | ⚠️ 需改策略 | 默认策略是 Migrate,依赖热迁移 | 见 §4,本环境应改为 ShutdownAndRestartAfterEnable |
| 快照创建 | ✅ 可用 | gate=Snapshot(单数);实测 5~11s readyToUse=true | 见 03-运维手册-虚拟机生命周期.md §6 |
| 快照恢复 | ⚠️ 不可靠 | type: snap 下源卷被 GC 与 full-copy 克隆竞态(4 次实测 2 失败、2 成功但卷 degraded);须 type: bak + backup target | 02 案例 17、03 §6.4 |
| 存储高可用(多副本) | ✅ 可用 | numberOfReplicas ≤ 可调度节点数 | harvester-longhorn = 3;Longhorn 3 节点均 schedulable、available≈110GB |
2. 冷迁移(★ 当前标准做法)
2.1 原理
VM 停机 → VMI 与 virt-launcher Pod 被回收 → 卷 detach → 修改调度约束 → 启动 → KubeVirt 在满足约束的节点重建 Pod,Longhorn 把卷 attach 到新节点。
2.2 操作流程(已实测)
bash
VM=default/vm-demo
FROM=sza122101.local
TO=sza122102.local
# 1) 停机(注意:是 Halted,不是 Stopped;不要用 spec.running)
kubectl patch vm -n ${VM%%/*} ${VM##*/} --type=merge -p '{"spec":{"runStrategy":"Halted"}}'
# 2) 等待 VMI 完全回收(无 OS 的空盘 VM 需等 terminationGracePeriodSeconds,约 30s)
kubectl -n ${VM%%/*} wait vmi/${VM##*/} --for=delete --timeout=180s
# 3) 绑定目标节点
kubectl patch vm -n ${VM%%/*} ${VM##*/} --type=merge \
-p "{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"kubernetes.io/hostname\":\"${TO}\"}}}}}"
# 4) 启动
kubectl patch vm -n ${VM%%/*} ${VM##*/} --type=merge -p '{"spec":{"runStrategy":"Always"}}'
# 5) 验证
kubectl get vmi -n ${VM%%/*} ${VM##*/} -o wide # NODE 应为 TO
kubectl get pod -n ${VM%%/*} -l kubevirt.io/domain=${VM##*/} -o wide
PV=$(kubectl get pvc -n ${VM%%/*} ${VM##*/}-disk-0 -o jsonpath='{.spec.volumeName}')
kubectl get volumes.longhorn.io -n longhorn-system "$PV" \
-o jsonpath='state={.status.state} robustness={.status.robustness} node={.status.currentNodeID}{"\n"}'2.3 实测结果
迁移前 node=sza122101.local pod=virt-launcher-…-6l2x6 卷 state=attached robustness=healthy
停机 runStrategy=Halted → VMI 回收(空盘 VM 因无 ACPI 响应,等待约 48s,符合 grace period)
迁移后 node=sza122102.local pod=virt-launcher-…-5zqpv 卷 state=attached robustness=healthy ✓
VMI phase=Running VM printableStatus=Running nodeSelector=sza122102.local ✓耗时:停机约 48s(受 terminationGracePeriodSeconds=30 + 回收时间影响)+ 启动约 20s。
2.4 注意事项
- 停机慢是空盘/无 guest agent 的特性;有正常 OS 与 qemu-guest-agent 时会快得多;
- 迁移后
nodeSelector会永久生效,若希望恢复自由调度需移除:
bash
kubectl patch vm -n default <vm> --type=json \
-p '[{"op":"remove","path":"/spec/template/spec/nodeSelector/kubernetes.io~1hostname"}]'- 冷迁移会造成业务中断,需在变更窗口执行;关键 VM 建议先打快照(见 03 §6)。
3. 热迁移(当前不可用,含修复路线)
3.1 硬性前提
| 前提 | 说明 | 本环境状态 |
|---|---|---|
LiveMigration feature gate | GA 且无法关闭(不在可禁用列表) | ✓ 无需处理 |
migration 网络 | 未显式配置时自动使用 Pod 网络 | ✓ 无需处理 |
所有 PVC 为 ReadWriteMany | KubeVirt 迁移前置检查强制要求 | ✗ 当前用 RWO |
| RWX 卷数据面可用 | Longhorn RWX 依赖 share-manager/NFS | ✗ 阻塞点 |
3.2 实测报错链
① RWO 卷直接迁移:
reason=DisksNotLiveMigratable
"Some disks don't support live migration, please make sure all PVCs have access mode: ReadWriteMany"
② 改用 RWX(Filesystem)后 —— CDI 导入阶段就失败:
could not create raw image with size 2147483648 in qemu-img format.
qemu-img: Could not open '/mnt/…/disk.img': Could not open '…': No such file or directory
qemu-img execution failed: exit status 1
③ 改用 RWX(Block):
blockdev: cannot open /dev/cdi-block-volume: Permission denied
(Longhorn RWX 走 NFS 共享文件系统,不提供块设备)
④ 根因(Longhorn 数据面):
kubectl get volumes.longhorn.io -n longhorn-system <rwx-vol> -o wide
state=detached shareEndpoint=<空> shareState=<空>
kubectl get pod -n longhorn-system | grep share-manager
<无输出>
kubectl get engines.longhorn.io -n longhorn-system <vol> -o wide
mode="" currentState=""3.3 修复路线(若业务确需热迁移)
- 确认节点镜像具备 NFS 客户端能力: Harvester OS 内建 share-manager 组件,但本环境是 Helm 部署在通用 RKE2 节点上, 节点镜像未必包含
nfs-utils/ NFS 内核模块:bash# 在每个节点执行 rpm -q nfs-utils || dpkg -l | grep nfs-common lsmod | grep -E '^nfs|nfsd' mount -t nfs -V - 检查 Longhorn RWX 相关组件与配置:bash
kubectl -n longhorn-system get pod | grep -iE 'share-manager|csi-plugin' kubectl -n longhorn-system get ds longhorn-csi-plugin -o wide # 期望 3/3 kubectl -n longhorn-system get settings.longhorn.io | grep -iE 'share-manager|nfs' - 手工验证 share-manager 能否拉起:创建一个独立 RWX PVC(不挂 VM)观察是否出现
share-manager-<vol>Pod 且shareEndpoint被填充;据此定位是 NFS 客户端问题还是 Longhorn 控制器问题。 - 修好后复测顺序:RWX PVC 独立挂载 → CDI 导入 RWX 卷成功 → VM 以 RWX 运行 → 发起热迁移 → 确认
VirtualMachineInstanceMigration进入Succeeded且 VMInodeName变更。
3.4 源码侧参考(避免误判)
go
// vendor/kubevirt.io/kubevirt/pkg/util/hardware/hardware.go:131
LiveMigration = featuregate.FeatureGate{
Name: LiveMigrationGate, FeatureSpec:
featuregate.FeatureSpec{Default: true, PreRelease: featuregate.GA}, // GA,不可禁用
}
// pkg/util/virtualmachine/virtualmachine.go:169
func volumeSupportRWXForVM(accessModes []corev1.PersistentVolumeAccessMode, provisioner string) bool {
if provisioner == util.CSIProvisionerLonghorn {
// Longhorn provisioner does not support RWX volume for VM
return false
}
return slices.Contains(accessModes, corev1.ReadWriteMany)
}说明:Harvester 自身在 Longhorn 场景不认可 VM 使用 RWX,这与 KubeVirt 热迁移要求 RWX 存在 张力 —— 即在 Longhorn 形态下,官方路径本就是"冷迁移/维护模式关机重启",热迁移并非默认支持能力。
4. 节点维护模式(★ 本环境必须调整策略)
4.1 v1.8 的维护模式模型(与旧版本不同)
v1.8.2 不是通过 Harvester Node CR 的 spec.maintenanceMode 字段,而是 节点 action + 注解/标签 模型(源码:pkg/api/node/formatter.go、pkg/controller/master/node/maintain_controller.go):
| 项 | 值 |
|---|---|
| 状态注解 | harvesterhci.io/maintain-status = running / completed |
| 节点 action | enableMaintenanceMode、disableMaintenanceMode、cordon、uncordon、listUnhealthyVM、maintenancePossible、powerAction(shutdown/poweron/reboot)、enableCPUManager/disableCPUManager |
| 驱逐请求注解 | harvesterhci.io/drain-requested、harvesterhci.io/drain-forced |
| KubeVirt 驱逐键 | kubevirt.io/drain |
| 可行性预检 | drainhelper.DrainPossible() → 不可行时报 draining of this node is not possible |
| 策略标签 | harvesterhci.io/maintain-mode-strategy |
| 策略归属注解 | harvesterhci.io/maintain-mode-strategy-node-name |
4.2 维护模式策略(决定 VM 的命运)
源码 pkg/util/constants.go:
go
MaintainModeStrategyMigrate = "Migrate"
MaintainModeStrategyShutdownAndRestartAfterEnable = "ShutdownAndRestartAfterEnable"
MaintainModeStrategyShutdownAndRestartAfterDisable = "ShutdownAndRestartAfterDisable"
MaintainModeStrategyShutdown = "Shutdown"
MaintainModeStrategyDefault = MaintainModeStrategyMigrate // ★ 默认 Migrate| 策略 | 行为 | 本环境可用性 |
|---|---|---|
Migrate(默认) | 热迁移 VM 到其他节点 | ❌ 不可用(热迁移被 RWX 阻塞) |
ShutdownAndRestartAfterEnable | 进入维护时关机,维护结束后自动重启 | ✅ 推荐 |
ShutdownAndRestartAfterDisable | 进入维护时关机,退出维护时重启 | ✅ 可用 |
Shutdown | 仅关机,不自动重启 | ✅ 可用(需人工启动) |
关键运维结论:本环境沿用默认
Migrate会让维护模式卡在"无法迁移 VM", 因此必须显式指定ShutdownAndRestartAfterEnable(或Shutdown)。 修复 Longhorn RWX 后才可回到Migrate实现无中断维护。
4.3 操作流程
bash
NODE=sza122102.local
NS=default
# 1) 预检:该节点能否进入维护(会检查 VM 可迁移性/可关闭性)
kubectl annotate node $NODE harvesterhci.io/drain-requested="true" --overwrite # 触发 drain 预检/流程
kubectl get node $NODE -o jsonpath='{.metadata.annotations}{"\n"}' | tr ',' '\n' | grep -i maintain
# 2) 指定 VM 的维护策略(避免默认 Migrate)
for vm in $(kubectl get vm -n $NS -o name); do
kubectl -n $NS label $vm harvesterhci.io/maintain-mode-strategy=ShutdownAndRestartAfterEnable --overwrite
done
# 3) 观察维护进度:注解由 running → completed
kubectl get node $NODE -o jsonpath='{.metadata.annotations.harvesterhci\.io/maintain-status}{"\n"}'
# 4) 维护完成后退出(清除注解 / 调 disableMaintenanceMode action / uncordon)
kubectl annotate node $NODE harvesterhci.io/maintain-status-
kubectl uncordon $NODE
kubectl get node $NODE -o jsonpath='{.spec.unschedulable}{"\n"}' # 应为空/falseDashboard 操作:Hosts → 选中节点 → 右上 ⋮ → Enable Maintenance Mode; 退出时同一菜单 Disable Maintenance Mode。UI 会调用上述 action 并展示 maintenancePossible 结果。
4.4 手工 drain(不使用维护模式时)
bash
# Harvester 的 drain 是 Longhorn/KubeVirt 感知的(drainhelper 基于 k8s.io/kubectl/pkg/drain + 过滤器)
kubectl drain $NODE --ignore-daemonsets --delete-emptydir-data --force
# 完成后
kubectl uncordon $NODE直接
kubectl drain不会处理 VM 的维护策略;对含 VM 的节点,优先使用维护模式。
5. 高可用与容量规划
5.1 存储副本与调度
bash
kubectl get sc harvester-longhorn -o yaml | grep -E 'numberOfReplicas|dataLocality|migratable|fromBackup'
# numberOfReplicas: "3" ← 3 节点集群的上限;节点 < 3 时必须下调,否则卷无法调度
# dataLocality: disabled
# migratable: "true"
# fromBackup: ""
kubectl get nodes.longhorn.io -n longhorn-system -o wide # 关注 schedulable / available| 集群节点数 | 建议 numberOfReplicas |
|---|---|
| 1 | 1 |
| 2 | 2(或 1,视可用性要求) |
| ≥3 | 3 |
5.2 备份与容灾
bash
# 备份目标(生产必配;否则 type: bak 快照会失败)
kubectl get settings.longhorn.io -n longhorn-system backup-target -o jsonpath='{.value}'
kubectl get settings.longhorn.io -n longhorn-system backup-target-credential-secret -o jsonpath='{.value}'- 未配置 backup target 时,Longhorn 快照只能走
type: snap(卷内快照), 既不满足容灾要求(数据仍在集群内),也无法可靠地用于 VM 恢复: restore 会替换 PVC → 源卷被删 → LonghornSnapshotCR 因 ownerReference 被 GC, 与full-copy克隆形成竞态;4 次实测 2 次失败(卷卡initiated/failed、VM 起不来), 2 次成功但卷遗留robustness=degraded(3 副本只建出 1 个)。 取证与机制见02-故障排查与修复记录.md案例 17、03手册 §6.4; - 因此 配置 backup target 不是"锦上添花",而是 VM 快照可靠恢复的前提: 生产环境应配置 NFS/S3 backup target,并用
type: bak语义做 VM 快照; - 建议演练:定期做一次"快照 → 恢复 → 启动 → 数据校验",确认恢复链路真实可用 (
scripts/vm-e2e-test.sh已自动化,且会在克隆不收敛时反查源卷给出根因)。
5.3 计算层高可用
| 组件 | 副本 | 说明 |
|---|---|---|
harvester | 3 | Deployment,滚动更新(maxSurge=1/maxUnavailable=1) |
harvester-webhook | 3 | 校验/默认化 webhook |
kubevirt 控制器(virt-controller/virt-api/virt-handler) | 见 kube-system | virt-handler 为 DaemonSet,每节点一个 |
vm-import-controller | 2 | Harvester 的 VirtualMachineImport(v2v)控制器 |
bash
kubectl get deploy,ds -n harvester-system -o wide
kubectl get deploy,ds -n kube-system | grep -E 'virt-|cdi|snapshot'5.4 巡检与告警建议
bash
bash scripts/healthcheck.sh # 平台健康度(约 20s,非零退出码可用于告警)
bash scripts/vm-e2e-test.sh # VM 端到端能力验证(创建→冷迁移→快照→清理)建议纳入监控的指标/信号:
- Longhorn 各节点
available(磁盘水位)、卷robustness != healthy数量; csi-snapshotter是否出现backup target is not available(配置漂移);- kubevirt CR
spec.configuration.developerConfiguration.featureGates是否仍含Snapshot(helm upgrade 会覆盖 CR,导致 gate 静默失效 —— 本环境实际发生过); - 默认 StorageClass 是否仍为
harvester-longhorn(可能被其他 chart 抢占); - VM
printableStatus长期停留在Provisioning/Scheduling/DataVolumeError。