主题
02 · 故障排查与修复记录(Harvester v1.8.2 on RKE2)
按"现象 → 定位过程 → 根因 → 修复 → 验证"记录本次实施遇到的全部问题, 含实测输出与源码级证据,可直接作为同类环境的排障手册。 标注
[非致命]者属预期噪声,按案例 11/12 甄别后可忽略。
索引
| # | 现象关键字 | 严重度 | 类别 |
|---|---|---|---|
| 1 | 未指定 SC 的 PVC 绑到 longhorn 而非 harvester-longhorn | 高 | 存储 |
| 2 | 无 longhorn VolumeSnapshotClass | 高 | 存储/快照 |
| 3 | snapshot feature gate not enabled | 高 | KubeVirt |
| 4 | backup target default is not available | 高 | 快照语义 |
| 5 | 恢复"成功"但 VM 卡 Scheduling / GuestNotRunning | 极高 | 快照恢复 |
| 6 | DisksNotLiveMigratable:热迁移被拒 | 高 | 迁移 |
| 7 | blockdev: cannot open /dev/cdi-block-volume: Permission denied | 高 | 存储/CDI |
| 8 | RWX 卷 qemu-img 失败、无 share-manager | 高 | 存储/RWX |
| 9 | running and runstrategy are mutually exclusive / Invalid RunStrategy (Stopped) | 中 | VM 操作 |
| 10 | VolumeSnapshot 删除卡住(finalizer) | 中 | 快照 |
| 11 | harvester-aggregation 拨号 /v3/connect 失败 [非致命] | 低 | Rancher/Steve |
| 12 | ssl-certificates 反复 requeue [非致命] | 低 | 证书/Ingress |
| 13 | 镜像导入卡在 99.62% 后失败(nbdkit curl) | 中 | 镜像源 |
| 14 | kubectl get vsc 查到 contents 而非 classes | 低 | 工具陷阱 |
| 15 | Failed to sync Longhorn VolumeAttachment [非致命] | 低 | Longhorn |
| 16 | 巡检脚本自身误报:7 个 FAIL 全是假警报 | 中 | 工具/脚本 |
| 17 | type: snap 与 VM 恢复结构性不兼容(源卷被 GC → 克隆永不收敛) | 极高 | 快照恢复 |
| 18 | harvester Pod 重启 10 次:启动期 fatal 与 VSC 顺序依赖 | 中 | 控制面/部署顺序 |
案例 1 · 默认 StorageClass 不唯一 / 非 harvester-longhorn
现象:未显式指定 storageClassName 的 PVC 可能绑定到 longhorn, 而 Harvester 期望的是带自身参数的 harvester-longhorn。
定位:
bash
kubectl get sc
kubectl get sc -o json | grep -c 'is-default-class": "true"' # >1 即双默认根因:
- Longhorn 子 chart 默认
persistence.defaultClass=true,会创建第二个默认类longhorn; - Harvester chart 的
_helpers.tpl渲染harvester-longhorn的默认注解时会lookup集群现状, 双默认共存时结果不确定。
修复:
yaml
# values 层面根治(推荐)
longhorn:
persistence:
defaultClass: falsebash
# 现场校正
kubectl annotate sc longhorn storageclass.kubernetes.io/is-default-class-
kubectl annotate sc harvester-longhorn storageclass.kubernetes.io/is-default-class=true --overwrite⚠️ 现场校正不持久:下次
helm upgrade若未带defaultClass=false会被改回。
验证:创建一个不写 storageClassName 的 PVC → 实测绑定到 harvester-longhorn ✓(验证后清理)
自动化:scripts/post-install.sh 段落 A.;巡检:scripts/healthcheck.sh 第 5 节。
案例 2 · 缺失 longhorn VolumeSnapshotClass
现象:VM 快照无法创建,VolumeSnapshot 找不到可用类。
根因链条:
values 里 csi-snapshotter.enabled=false
→ 该子 chart 既提供 snapshot-controller,也提供 VolumeSnapshotClass 模板
(csi-snapshotter/templates/snapshotclass.yaml:name=longhorn,driver=driver.longhorn.io,无 parameters)
→ 类随子 chart 一起消失并且确认:Longhorn 子 chart(longhorn-1.11.2.tgz)自身不含任何 VolumeSnapshotClass 模板 → 禁用后无兜底。
为什么仍要禁用该子 chart:RKE2 自带 kube-system/rke2-snapshot-controller (含 helm-install-rke2-snapshot-controller-crd Job),再装一份会双控制器抢锁 + CRD 冲突。实测:
kube-system rke2-snapshot-controller-7bdcdcdfb-79l45 1/1 Running
kube-system helm-install-rke2-snapshot-controller-crd-* Completed
longhorn-system csi-snapshotter-5957cbfbb6-{g45x8,gxpf5,hk7p2} 1/1 Running ← Longhorn CSI sidecar(另一回事)修复:手工补建 scripts/manifests/volumesnapshotclass-longhorn.yaml
yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: longhorn
labels:
app.kubernetes.io/name: harvester
app.kubernetes.io/managed-by: manual-for-harvester-csi-driver-config
driver: driver.longhorn.io
deletionPolicy: Delete
parameters:
type: snap # 语义选择见案例 4 / 5验证:kubectl get volumesnapshotclasses.snapshot.storage.k8s.io longhorn → driver.longhorn.io / Delete / {"type":"snap"} ✓
补充:Harvester 自身不校验
parameters.type,该参数完全由 Longhorn CSI 解释。
案例 3 · snapshot feature gate not enabled(三层叠加,需逐层排除)
现象:
Error from server: error when creating "vm-snap.yaml": admission webhook
"virtualmachinesnapshot-validator.snapshot.kubevirt.io" denied the request:
snapshot feature gate not enabled第 1 层|先确认快照走哪套 API
bash
curl -sk --resolve harvester.local:32735:<node-ip> https://harvester.local:32735/v1/harvester \
| tr ',' '\n' | grep -i 'snapshot.kubevirt.io'实测 → Harvester v1.8 走 KubeVirt 原生 API,无自有 VirtualMachineSnapshot CRD:
snapshot.kubevirt.io.virtualmachinesnapshots
snapshot.kubevirt.io.virtualmachinesnapshotcontents
snapshot.kubevirt.io.virtualmachinerestores故清单必须写 apiVersion: snapshot.kubevirt.io/v1beta1(写成 harvesterhci.io/… 会 NotFound)。
第 2 层|gate 名写错(静默失效)
按直觉写成复数 Snapshots:CR 里确实出现该字符串,webhook 却仍拒绝。源码定论:
go
// vendor/kubevirt.io/kubevirt/pkg/virt-config/featuregate/active.go:30-33
// Owner: sig-storage
// Alpha: v0.30.0
// Beta: v1.3.0
SnapshotGate = "Snapshot" // ← 单数未知 gate 名不报错、只被忽略 → 必须写 Snapshot。
同时 values 路径必须带 spec 层(子 chart 模板渲染的是 .Values.spec):
yaml
# charts/kubevirt/templates/kubevirt.yaml
spec:
{{- if .Values.spec }}
{{ toYaml .Values.spec | indent 2 }}
{{- end }}正确:kubevirt.spec.configuration.developerConfiguration.featureGates
错误:kubevirt.configuration.developerConfiguration.featureGates ← 无模板读取,静默失效排错技巧(先看渲染,再看集群):
bash
helm upgrade harvester <chart> -n harvester-system --reuse-values \
--set 'kubevirt.spec.configuration.developerConfiguration.featureGates={…,Snapshot}' \
--dry-run | grep -A9 featureGates第 3 层|virt-api 未重启(gate 只在进程启动时加载)
CR 改完且 phase=Deployed、observedGeneration 已追平、virt-operator 报 All KubeVirt components ready,webhook 仍然拒绝。证据是 Pod 启动时间远早于 CR 变更:
bash
kubectl -n harvester-system get pod -l kubevirt.io=virt-api \
-o jsonpath='{range .items[*]}{.metadata.name} started={.status.startTime}{"\n"}{end}'
# virt-api-7ddcb4b77b-wbtk5 started=2026-09-05T13:20:44Z ← CR 变更在 23:33处置(逐个删 Pod,等全部就绪再删下一个 → webhook 不断流):
bash
for p in $(kubectl -n harvester-system get pod -l kubevirt.io=virt-api -o name); do
kubectl -n harvester-system delete $p --wait=false
kubectl -n harvester-system wait pod -l kubevirt.io=virt-api --for=condition=Ready --timeout=120s
done不建议
kubectl rollout restart:它改 Pod 模板注解,而 virt-operator 用kubevirt.io/install-strategy-identifier等注解托管 Deployment,直接删 Pod 更稳。
验证:
bash
kubectl apply -f vm-snap.yaml # → created ✓
kubectl get virtualmachinesnapshots.snapshot.kubevirt.io -n default vm-cirros-snap \
-o jsonpath='{.status.phase} {.status.readyToUse}'
# Succeeded true ✓免落盘的快速验证(建议纳入巡检):
bash
kubectl apply --dry-run=server -f <源 VM 不存在的 VirtualMachineSnapshot>
# 仍报 gate not enabled → 未生效;改报 VM not found → gate 已生效 ✓案例 4 · backup target default is not available(VSC 参数语义踩坑)
现象(gate 已生效后):
phase=InProgress readyToUse=false
error: VolumeSnapshot vmsnapshot-…-volume-disk-0 error: Failed to check and update snapshot
content: failed to take snapshot of the volume pvc-…:
"rpc error: code = Aborted desc = backup target default is not available"定位:查 KubeVirt 实际选中的类及其参数
bash
kubectl get volumesnapshots.snapshot.storage.k8s.io -n default \
-o jsonpath='{range .items[*]}{.metadata.name}{" class="}{.spec.volumeSnapshotClassName}{"\n"}{end}'
# → class=longhorn
kubectl get volumesnapshotclasses.snapshot.storage.k8s.io -o wide
# longhorn driver.longhorn.io Delete {"type":"bak"} ← 被选中
# longhorn-snapshot driver.longhorn.io Delete {"type":"snap"}根因:环境里存在两个同 driver、语义不同的类:
| parameters.type | Longhorn 行为 | 依赖 |
|---|---|---|
snap | 卷内本地快照,秒级完成 | 无(但见案例 5 的恢复陷阱) |
bak | 备份到 backup target | 必须先配置备份目标 |
KubeVirt 在同 driver 的多个类中按名称字典序取第一个(longhorn < longhorn-snapshot), 选中了需要 target 的那个,而本环境 target 为空:
bash
kubectl get setting -n longhorn-system backup-target # VALUE 空
kubectl get settings.harvesterhci.io -n harvester-system backup-target -o jsonpath='{.value}' # 空修复(二选一):
bash
# A. 改本地快照语义(本次采用;parameters 可原地 patch)
kubectl patch volumesnapshotclasses.snapshot.storage.k8s.io longhorn \
--type=merge -p '{"parameters":{"type":"snap"}}'
# 并删除语义冲突的多余类,避免字典序误选
kubectl delete volumesnapshotclasses.snapshot.storage.k8s.io longhorn-snapshot
# B. 保留备份语义 → 在 UI Settings → Backup Target 配置 NFS/S3 目标后重试验证(实测输出):
VirtualMachineSnapshot : phase=Succeeded readyToUse=true error=<空>
VolumeSnapshot : class=longhorn readyToUse=true restoreSize=2Gi
VolumeSnapshotContent : handle=snap://pvc-63e77fab-…/snapshot-5a993416-…
↑ snap:// 前缀 = 卷内快照(非备份)✓
VM SnapshotContent : vmsnapshot-content-d1e2c11b-… readyToUse=true案例 5 · 恢复"成功"却起不来:type: snap 本地快照的致命陷阱 ⚠️
严重度:极高(会造成"以为有备份、实际无法恢复"的假安全感)
现象:VirtualMachineRestore 报告完全成功,但 VM 再也起不来。
# 恢复侧一切"正常"
virtualmachinerestore: complete=true
conditions: Progressing=False:Operation complete Ready=True:Operation complete
restores: [{dataVolumeName: restore-52856d85-…-disk-0,
volumeSnapshotName: vmsnapshot-d1e2c11b-…-volume-disk-0}]
datavolume: restore-52856d85-…-disk-0 Succeeded
pvc : restore-52856d85-…-disk-0 Bound pvc-64684c60-… RWO
# 但 VM 起不来
vm : Starting / Provisioning
vmi: phase=Scheduling Ready=False reason=GuestNotRunning
Events: AttachVolume.Attach failed for volume "pvc-64684c60-…" :
rpc error: code = Aborted desc = volume pvc-64684c60-… is not ready for workloads定位(一路挖到 Longhorn 卷内部):
bash
kubectl get volumes.longhorn.io -n longhorn-system pvc-64684c60-… -o jsonpath='
state={.status.state} robustness={.status.robustness} node={.status.currentNodeID}
dataSource={.spec.dataSource}
cloneStatus={.status.cloneStatus}
specReplicas={range .spec.replicas[*]}{.name} {end}'实测输出:
state=detached robustness=unknown node=<空>
numberOfReplicas=3 specReplicas=<空> ← 0 副本!
dataSource=snap://pvc-63e77fab-…/snapshot-5a993416-…
cloneStatus={"attemptCount":0,"sourceVolume":"","state":"failed"} ← 克隆失败
kubectl get volumes.longhorn.io -n longhorn-system pvc-63e77fab-…
# Error from server (NotFound) ← 源卷已不存在根因(三个事实叠加):
type: snap是 Longhorn 卷内快照:数据留在源卷内部,快照对象只是一个引用 (handle 形如snap://<源卷>/<快照名>);- 从这种快照恢复时,Longhorn 新建的卷
dataSource=snap://…,需要从源卷克隆数据; - KubeVirt 恢复完成后会删除被替换的原 DataVolume/PVC → 源 Longhorn 卷随之消失 → 克隆失去数据源:
cloneStatus.state=failed、副本数 0、卷detached/unknown→ CSI attach 返回Aborted: volume … is not ready for workloads→ VMI 永远卡Scheduling。
为什么各层都显示"成功":
VolumeSnapshot.readyToUse=true:快照创建那一刻确实成功了;DataVolume Succeeded/PVC Bound:K8s 层面的绑定与 CDI 流程正常;VirtualMachineRestore complete=true:KubeVirt 只负责创建新 PVC 并改写 VM 引用;- 真正的失败发生在 Longhorn 卷数据面,只有
volumes.longhorn.io的cloneStatus/spec.replicas/robustness才能看出来。
修复与规避:
bash
# 1) 现场恢复:删除损坏的恢复产物,重建 VM(本环境实测)
kubectl delete vm -n default <vm>
kubectl delete virtualmachinerestores.snapshot.kubevirt.io -n default <restore>
kubectl delete dv,pvc -n default restore-<uid>-disk-0
kubectl get volumes.longhorn.io -n longhorn-system | grep <pv> # 确认无残留
# 2) 根治:需要"可恢复"的快照时,必须用备份型(type: bak)+ 已配置 backup target
kubectl patch volumesnapshotclasses.snapshot.storage.k8s.io longhorn \
--type=merge -p '{"parameters":{"type":"bak"}}'
# 并在 Harvester UI Settings → Backup Target 配置 NFS/S3
# 备份数据落在集群外,恢复不依赖源卷是否存活 ✓
# 3) 若坚持用 type: snap,必须在恢复前确保源卷不会被删除
# (例如先对源 PVC 做独立留存 / 关闭 KubeVirt 对原 DV 的清理),风险高,不推荐选型结论(写进运维规范):
| 用途 | 推荐 VSC parameters | 理由 |
|---|---|---|
| 临时校验、快速回滚点(源卷保留) | type: snap | 秒级、无需备份目标 |
| 生产可恢复快照 / 容灾 | type: bak + backup target | 数据在集群外,恢复不依赖源卷 |
| Harvester VM Backup 功能 | 由 Harvester backup 控制器走 Longhorn backup API | 与 VSC 无关 |
附:这也解释了 Harvester chart 自带的类为何不带 parameters —— 交给 Longhorn 默认语义, 并要求使用者先配置 backup target(Harvester UI 的 VM 快照功能同样以备份目标为前提)。
案例 6 · 热迁移被拒:DisksNotLiveMigratable
现象:
Error from server: error when creating "vm-migrate.yaml": admission webhook
"migration-create-validator.kubevirt.io" denied the request: Cannot migrate VMI,
Reason: DisksNotLiveMigratable, Message: cannot migrate VMI: PVC vm-cirros-test-disk-0
is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)定位:
bash
kubectl get pvc -n default <vm-disk> -o jsonpath='{.spec.accessModes} {.spec.volumeMode}'
# ["ReadWriteOnce"] ← RWO
kubectl get sc harvester-longhorn -o jsonpath='{.parameters}'
# {"migratable":"true", "numberOfReplicas":"3", …} ← 已开启 migratable,但仍被拒根因:
- KubeVirt 的迁移校验 webhook 要求 VMI 的所有 PVC 都是共享(RWX);
- Longhorn StorageClass 的
migratable: "true"不足以绕过该校验(实测); LiveMigration本身在 KubeVirt 已 GA,无需 feature gate:go// vendor/kubevirt.io/kubevirt/pkg/virt-config/featuregate/inactive.go:102 RegisterFeatureGate(FeatureGate{Name: LiveMigrationGate, State: GA})- Harvester 侧的约定同样是 RWX:
pkg/api/vm/handler.go、pkg/data/template.go、pkg/image/cdi/common.go中都硬编码ReadWriteMany。
修复方向:把 VM 磁盘改为 ReadWriteMany。但要注意 volumeMode 与 Longhorn RWX 的兼容性 (案例 7、8),三者组合的实测结论:
| accessModes | volumeMode | 实测结果 |
|---|---|---|
ReadWriteOnce | Filesystem(默认) | ✓ 完全可用:导入/引导/稳定运行 5h+/快照/恢复流程均通过;但不支持热迁移 |
ReadWriteMany | Block | ✗ CDI importer 报 blockdev: cannot open /dev/cdi-block-volume: Permission denied |
ReadWriteMany | Filesystem | ✗ 本环境 RWX 数据面未就绪(无 share-manager),qemu-img 写盘失败 |
因此本环境当前可用的迁移方式是冷迁移(已实测通过,见 04-运维手册-迁移与高可用.md §3):
迁移前 node=sza122101.local → 迁移后 node=sza122102.local phase=Running
Longhorn 卷 pvc-88bab4da-… state=attached robustness=healthy currentNodeID=sza122102.local ✓案例 7 · RWX + Block:CDI importer 打不开块设备
现象:VM 磁盘改成 ReadWriteMany + volumeMode: Block 后,导入立刻失败:
vm : DataVolumeError
pod : importer-prime-661291de-… 0/1 Error restarts=3
DV condition:
E importer.go:137] exit status 1,
blockdev: cannot open /dev/cdi-block-volume: Permission denied
→ importer.GetAvailableSpaceBlock (pkg/importer/file.go:79)
→ GetAvailableSpaceByVolumeMode (pkg/importer/util.go:64)根因:Longhorn 的 RWX 通过 share-manager(NFS)导出文件系统,不提供块设备; volumeMode: Block 期望的 /dev/cdi-block-volume 在该路径下不可用。
Harvester 源码对此有明确表述:
go
// pkg/util/virtualmachine/virtualmachine.go:142,169
func CheckBlockRWXVolumeForVM(...) {
if volumeSupportRWXForVM(targetAccessMode, targetProvisioner) { ... }
}
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)
}修复:VM 磁盘不要用 RWX+Block。可选组合:
RWO + Filesystem:本环境验证可用,功能完整,仅不支持热迁移;RWO + Block:Longhorn 支持,但同样不满足热迁移的 RWX 要求;RWX + Filesystem:热迁移所需的正确组合,但依赖 Longhorn RWX 数据面正常 → 见案例 8。
案例 8 · RWX + Filesystem:卷"已挂载"但数据面不可用
现象:改成 RWX + Filesystem 后,PVC 能 Bound、Longhorn 卷显示健康,但写入失败:
# 连 blank 源(无需任何下载)都失败:
dv condition:
Unable to create blank image: could not create raw image with size 946864128
in /data/disk.img: qemu-img execution failed: exit status 1
→ image.(*qemuOperations).CreateBlankImage (pkg/image/qemu.go:307)
pod: importer-prime-70c98387-… 0/1 Error restarts=3~5
# HTTP 源同样在写盘阶段失败(进度冲到 99.62% 后回退重来):
Conversion to Raw failed … qemu-img execution failed: exit status 1关键定位证据:
bash
kubectl get volumes.longhorn.io -n longhorn-system -o json \
| grep -E 'accessMode|shareState|shareEndpoint|"state"'
# "accessMode": "rwx"
# "state": "attached" robustness=healthy
# "shareEndpoint": "" ← 空!
# "shareState": "" ← 空!
kubectl get pod -A | grep -i share-manager
# (无任何 share-manager Pod)RWX 卷虽被 attach,却从未建立 NFS 导出端点 → 数据面不完整,Pod 内对 /data 的写入失败。
已排除的干扰项(均做过验证,避免误判):
| 怀疑 | 验证方式 | 结论 |
|---|---|---|
| 存储空间不足 | nodes.longhorn.io:available≈110GB / max≈127GB / scheduled≈5.4GB(每节点) | 排除 |
| share-manager 镜像不可用 | longhorn-manager 的 pre-pull-share-manager-image 容器 ready: true(个别 exitCode=1 后自愈) | 排除 |
| VolumeAttachment 同步失败 | 日志为乐观锁冲突:the object has been modified; please apply your changes to the latest version and try again | 良性,排除 |
| 仍是 Block 权限问题 | 已确认 volumeMode=Filesystem | 排除 |
对照实验(决定性):同一 blank 源,只改 accessMode:
| VM | accessModes | volumeMode | 结果 |
|---|---|---|---|
vm-rwo-blank-test | ReadWriteOnce | Filesystem | DV Succeeded 100%(75s);VM/VMI Running ✓ |
vm-migrate-test | ReadWriteMany | Filesystem | DataVolumeError;importer 重启 5 次 ✗ |
处置建议(按优先级):
- 短期:VM 磁盘统一用
RWO + Filesystem(功能完整),迁移改用冷迁移(已实测通过); - 中期:修复 Longhorn RWX 前置条件后回归热迁移,排查清单:bash
# a) 每个节点是否具备 NFS 客户端能力(Longhorn RWX 硬前提:nfs-utils / nfs-common + 内核模块) # b) share-manager 镜像能否在所有节点拉取 kubectl -n longhorn-system get pod -l app=longhorn-manager \ -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.initContainerStatuses[0].state}{"\n"}{end}' # c) 观察 Longhorn 是否尝试创建 share-manager kubectl -n longhorn-system logs -l app=longhorn-manager --tail=500 --prefix | grep -i share # d) 复测:建 RWX+Filesystem PVC,用普通 Pod 挂载并 touch 写入;通过后再回归 VM 热迁移 - 长期:若确认节点镜像不含 NFS 客户端,则该形态下热迁移不可用, 应在运维规范中把"冷迁移 + 节点维护模式"定为标准做法。
案例 9 · VM 启停语义:running 与 runStrategy 互斥;停机值是 Halted
现象 1:用 running 停机被 Harvester webhook 拒绝
kubectl patch vm … --type=merge -p '{"spec":{"running":false}}'
Error from server (InternalError): admission webhook "validator.harvesterhci.io" denied
the request: running and runstrategy are mutually exclusive根因:创建时写了 running: true,KubeVirt 默认化会补出 spec.runStrategy: Always; 两者互斥,此后只能操作 runStrategy。
现象 2:想当然写 Stopped 被 API 拒绝
kubectl patch vm … --type=merge -p '{"spec":{"runStrategy":"Stopped"}}'
The request is invalid: spec.runStrategy: Invalid RunStrategy (Stopped)根因:RunStrategy 合法值中没有 Stopped,"停机"对应 Halted。
正确操作:
bash
kubectl patch vm -n default <vm> --type=merge -p '{"spec":{"runStrategy":"Halted"}}' # 停机
kubectl patch vm -n default <vm> --type=merge -p '{"spec":{"runStrategy":"Always"}}' # 启动附带经验(实测):
- 用
--type=json删除/spec/running时,若该字段已不存在会导致整个请求失败 (the server rejected our request due to an error in our request)→ 先查后改; - 空盘/无 OS 的 VM 停机偏慢:ACPI 关机无人响应,需等
terminationGracePeriodSeconds(本例 30s)到期强制终止;期间 VMI 带着deletionTimestamp仍短暂显示Running属正常; - 停机过程中不要立刻改回
Always:KubeVirt 会先回收旧 VMI 再建新的,观察窗口内易误判。
案例 10 · VolumeSnapshot 删除卡住(finalizer 无法摘除)
现象:删除失败的 VirtualMachineSnapshot 后,底层对象长期滞留(5 分钟以上):
volumesnapshot vmsnapshot-…-volume-disk-0 false …
volumeSnapshotcontent snapcontent-… false Delete driver.longhorn.io
deletionTimestamp=2026-09-05T23:44:24Z
finalizers=["snapshot.storage.kubernetes.io/volumesnapshot-bound-protection"]
finalizers=["snapshot.storage.kubernetes.io/volumesnapshotcontent-bound-protection"]根因:Longhorn 的 csi-snapshotter sidecar 对创建失败的 content 持续重试 CreateSnapshot, 形成死循环,永远走不到删除分支:
E snapshot_controller.go:148] checkandUpdateContentStatus [snapcontent-…]: error occurred
failed to take snapshot of the volume pvc-…: "rpc error: code = Aborted desc = backup target …"
E snapshot_controller_base.go:299] could not sync content "snapcontent-…": …
I snapshot_controller.go:339] createSnapshotWrapper: Creating snapshot for content snapcontent-…修复顺序(先修因,再摘锁):
bash
# 1) 先修根因(本例把 VSC 参数 bak → snap);有时重试会自行成功并完成清理
kubectl patch volumesnapshotclasses.snapshot.storage.k8s.io longhorn \
--type=merge -p '{"parameters":{"type":"snap"}}'
# 2) 仍卡住 → 摘除 content 的 finalizer
# 实测:content 删除后,VolumeSnapshot 会被自动释放(无需再单独 patch)
kubectl patch volumeSnapshotcontents.snapshot.storage.k8s.io snapcontent-<uid> \
--type=merge -p '{"metadata":{"finalizers":null}}'
# 3) 确认干净
kubectl get volumesnapshots.snapshot.storage.k8s.io -A --no-headers
kubectl get volumeSnapshotcontents.snapshot.storage.k8s.io --no-headers⚠️ 摘 finalizer 会跳过底层清理,可能残留 Longhorn 快照/备份。事后核对
kubectl get volumes.longhorn.io -n longhorn-system,必要时进卷清理多余快照。另:PVC 上的
snapshot.storage.kubernetes.io/volume-snapshot-bound-protection会由控制器在快照释放后自动摘除(实测 PVC 最终只剩kubernetes.io/pvc-protection与wrangler.cattle.io/persistentvolumeclaim-controller)。
案例 11 · [非致命] harvester-aggregation 反复拨号 /v3/connect 失败
现象:harvester-system/harvester 日志持续出现
… harvester-aggregation … https://10.43.183.188/v3/connect …
dial tcp … / TLS handshake error / connection refused(反复重试)根因:这是 Rancher/Steve 隧道(remotedialer) 的聚合客户端。 rancherEmbedded: false 且未把集群接入 Rancher 时,目标 v3/connect 端点不存在/不可达, 客户端会按退避策略无限重试。
影响判定(实测):仅影响"被 Rancher 纳管"的能力,不影响本地 Harvester API/UI:
bash
curl -k --resolve harvester.local:32735:<node-ip> https://harvester.local:32735/dashboard/
# HTTP 200 ✓
curl -k --resolve harvester.local:32735:<node-ip> https://harvester.local:32735/v1/harvester
# 正常返回 schema 列表 ✓处置:
- 不需要 Rancher 集成 → 记录并忽略(建议加入告警白名单,避免噪声淹没真实故障);
- 需要接入 Rancher → 配置 Rancher
server-url与集群注册 token,使v3/connect可达; - 巡检脚本已把
rancher|steve|v3相关的 APIService 不可用降级为WARN而非FAIL。
案例 12 · [非致命] ssl-certificates 控制器反复 requeue
现象:harvester 日志中 ssl-certificates 相关的 reconcile 反复入队/失败。
根因:该设置依赖 ingress-nginx 生成的默认证书 Secret(Harvester OS 自带 ingress)。 本环境 RKE2 未启用 ingress-nginx:
bash
kubectl -n kube-system get ds rke2-ingress-nginx-controller # NotFound且 API/UI 走 NodePort 暴露,使用的是 harvester svc 自签证书。
处置:
- 保持 NodePort 形态 → 忽略(
healthcheck.sh会显式标注"未部署 ingress-nginx,属预期非致命"); - 需要正规证书/域名 → 部署 ingress-nginx 或前置 LB,再更新
ssl-certificates设置 (kubectl -n harvester-system edit settings.harvesterhci.io ssl-certificates)。
案例 13 · 镜像导入卡在 99.62% 后失败(公网镜像源不稳定)
现象:CDI HTTP 导入长时间停在同一进度,随后整体回退重来:
dv: ImportInProgress 99.62% → 2.50%(重新计时)
pod: importer-prime-… restarts=2
lastState.terminated.message:
Unable to process data: Unable to convert source data to target format:
Conversion to Raw failed: could not convert image to raw
Log line from nbdkit: nbdkit: curl[1]: error: problem doing HEAD request to fetch size of URL
[https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img]:
SSL connect error: Recv failure: Connection reset by peer
… qemu-img execution failed: exit status 1根因:外部镜像源 download.cirros-cloud.net 连接被重置(限流/链路不稳定)。 nbdkit+curl 在转换阶段需要再次访问 URL,一旦失败整个导入重来。 (注意:这与存储无关 —— 同期 Longhorn 各节点 available≈110GB。)
规避(生产建议,按优先级):
- 不要把公网 URL 当作生产镜像源:先把镜像下载入库为 Harvester
VirtualMachineImage(UI: Images → Create,或harvesterhci.io/v1beta1 VirtualMachineImage),VM 创建时引用本地镜像; - 内网搭建镜像仓库/HTTP 源(Nginx、Nexus、S3 兼容存储)并固定使用;
- 已有可用卷时用 CDI clone / DataVolume source.pvc 复用(全程内网,不依赖外部源);
- 临时验证可用
source: {blank: {}}建空盘(不下载,秒级完成,适合验证调度/迁移/存储链路)。
验证(本环境实测):
vm-rwo-blank-test DV: Succeeded 100.0%(75s) → VM Running / VMI Running ✓即:换成不依赖公网的源后立刻成功,反证根因在镜像源而非平台。
案例 14 · kubectl get vsc 查到的不是 VolumeSnapshotClass
现象:用 vsc 短名查快照类,结果看到的是 VolumeSnapshotContent, 甚至出现 Error from server (NotFound): volumesnapshotcontents.snapshot.storage.k8s.io "longhorn-snapshot" not found。
根因:snapshot.storage.k8s.io 下 volumesnapshotclasses 与 volumesnapshotcontents 的短名冲突,kubectl 会解析到其中之一(本集群解析为 contents)。
规范:排查快照类一律用全名:
bash
kubectl get volumesnapshotclasses.snapshot.storage.k8s.io -o wide
kubectl get volumesnapshotcontents.snapshot.storage.k8s.io -o wide该陷阱曾导致一次误判(以为类参数正确,实际看的是 content),务必注意。
案例 15 · [非致命] longhorn-manager Failed to sync Longhorn VolumeAttachment
现象:
level=warning msg="Failed to sync Longhorn VolumeAttachment"
LonghornVolumeAttachment=longhorn-system/pvc-fe50f5e5-…
controller=longhorn-volume-attachment
error="… Operation cannot be fulfilled on volumeattachments.longhorn.io \"pvc-…\":
the object has been modified; please apply your changes to the latest version and try again"
level=warning msg="Failed to get volume attachment for volume pvc-…"
func=controller.(*SnapshotController).enqueueVolumeChange
error="volumeattachment.longhorn.io \"pvc-…\" not found"判定:
- 前者是 Kubernetes 乐观并发冲突,控制器会自动重试 → 良性;
- 后者出现在卷/附件生命周期的边界(对象刚被删除或尚未创建)→ 瞬时;
- 二者不影响卷可用性:同期相关卷
state=attached robustness=healthy✓。
处置:仅在持续大量出现且伴随卷 faulted/attach 失败时才深入排查 (检查 csi-attacher、longhorn-manager 与节点连通性)。
案例 16 · 巡检脚本自身误报:7 个 FAIL 全是假警报 ⚠️
现象
scripts/healthcheck.sh 首版在功能完全正常的集群上报出 7 个 FAIL:
[FAIL] KubeVirt Available=unknown
[FAIL] featureGates 缺少 Snapshot → VM 快照会被 webhook 拒绝
[FAIL] CDI Available=unknown
[WARN] 默认 StorageClass 为 (空)
[FAIL] 缺少 webhook 配置:validator.harvesterhci.io
[FAIL] 缺少 webhook 配置:mutator.harvesterhci.io
[FAIL] 未发现 KubeVirt webhook
[FAIL] 异常状态虚拟机:default/vm-e2e-demo=78s ← 78s 其实是 AGE
[WARN] 容器未全就绪:cdi-apiserver(1/1) virt-api(1/1) … ← 1/1 明明是就绪
[WARN] 存在异常发布记录:Sep=5 Sep=5 … ← 解析到了日期而同一时刻 vm-e2e-test.sh 实测:VM 创建/冷迁移/快照/恢复全部成功 —— 说明是巡检脚本错了,不是集群错了。
根因(六类,均为"取值方式"缺陷)
| # | 缺陷 | 说明 |
|---|---|---|
| 1 | grep -o '"type":"Available"[^}]*"status":"True"' | kubectl -o json 输出的是带空格的格式化 JSON,且键按字母序排列(status 在 type 之前),模式必然失配 |
| 2 | grep -o '"featureGates":\[[^]]*\]' | 实际输出为 "featureGates": [(冒号后有空格)→ 取到空值 |
| 3 | awk '$2!~/^([0-9]+)\/\1$/' | awk 正则不支持反向引用 \1 → 条件恒为真,所有 Running Pod 都被判"未就绪" |
| 4 | helm history | awk '{print $3"="$4}' | UPDATED 列是 2026-09-05 12:34:56.789 +0800 CST,含空格导致列号整体错位 |
| 5 | kubectl -n <不存在的ns> get deploy 判退出码 | 命名空间不存在时仍输出 No resources found 并返回 0 → "已部署 ingress-nginx"误判 |
| 6 | kubectl get vm -A 按 $3 取状态 | 列序是 NAMESPACE NAME AGE STATUS READY,STATUS 是 $4,$3 是 AGE |
| 7 | 按"配置名"查 webhook | webhook 名在配置内部:配置叫 harvester-validator/harvester-mutator,内部条目才是 validator.harvesterhci.io/mutator.harvesterhci.io |
修复
bash
# 1) 一律用 jsonpath 取值,不要 grep -o json
kubectl -n harvester-system get kubevirt kubevirt \
-o jsonpath='{range .status.conditions[?(@.type=="Available")]}{.status}{end}' # → True
kubectl -n harvester-system get kubevirt kubevirt \
-o jsonpath='{.spec.configuration.developerConfiguration.featureGates}' | tr -d '[]"' # 归一化后逐元素比对
# 2) 数组/集合用“计数”而非退出码
DEF_LIST=$(kubectl get sc -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}{"\n"}{end}' \
| awk '$2=="true"{printf "%s ",$1}') # 注解键含点号需转义 \.
# 3) READY 用 split 数值比较,替代反向引用
awk '$3=="Running"{split($2,a,"/"); if (a[1]!=a[2]) print $1"("$2")"}'
# 4) helm history 改为“整行不含 deployed/superseded 即异常”,不依赖列号
helm history harvester -n harvester-system --max 5 | awk 'NR>1' | grep -viE '\b(deployed|superseded)\b'
# 5) webhook 汇总“内部条目名”再比对
kubectl get validatingwebhookconfigurations \
-o jsonpath='{range .items[*]}{range .webhooks[*]}{.name}{"\n"}{end}{end}' | grep -qx validator.harvesterhci.io另外补一条通用原则:异步状态必须等收敛再判定。 cloneStatus=initiated、robustness=unknown、Longhorn 卷删除滞后几秒,都会造成瞬时误报; vm-e2e-test.sh 已改为轮询等待(克隆 ≤120s、卷回收 ≤90s)后再下结论。
结果对比
| 修复前 | 修复后 | |
|---|---|---|
| FAIL | 7(全部误报) | 0 |
| WARN | 8(含 3 条误报) | 6(均为真实/已知非致命) |
| PASS | 23 | 30 |
修复后剩余 WARN 全部可解释:harvester Pod 历史重启 10 次(本次多次升级/重启所致)、 存在 2 个 Longhorn VSC(longhorn 与 longhorn-snapshot,语义需统一)、 集群当前无 VM(已清理)、Steve/Rancher 聚合拨号、ssl-certificates requeue、 longhorn-manager VolumeAttachment 同步告警。
经验
巡检工具本身也需要被验证。 判断"FAIL 是真故障还是脚本缺陷"的最快方法: 用另一条独立路径(本例是端到端实测脚本)交叉验证同一结论。 当"巡检说坏了、实测是好的"时,先怀疑巡检的取值方式(JSON 格式、列序、退出码语义、异步收敛), 而不是先去动集群。
案例 17 · type: snap 的 VM 恢复不可靠:源卷被 GC 与 full-copy 克隆的竞态 ⚠️
案例 5/7 已指出"
type: snap恢复可能失败"。本案例把完整因果链查清,并用 4 次重复实测 给出准确定性:根因是结构性的(源卷必然被删除),但结果取决于"克隆 vs GC"的竞态, 因此表现为不可靠 —— 4 次实测中 2 次失败、2 次成功,且成功者也遗留卷degraded。
现象
vm-e2e-test.sh 端到端验证中,前四步全部正常:
创建 dv -> Succeeded (22s) vm -> Running 卷 state=attached robustness=healthy ✓
冷迁移 sza122100 → sza122101 vm -> Running 卷 robustness=healthy ✓
快照 phase=Succeeded readyToUse=true (11s) ✓
恢复 VirtualMachineRestore complete=true ✓但恢复出的新卷永久不收敛:
恢复出的卷:state=attached robustness=degraded node=sza122100.local cloneStatus=initiated
(轮询 120s 无变化;VM 随后 Stopped/Scheduling,磁盘不可用)逐层取证
bash
# ① 新卷的克隆状态:一直 initiated,attemptCount 递增,下次重试时间早已过期
kubectl get volumes.longhorn.io -n longhorn-system <new-vol> -o jsonpath='{.status.cloneStatus}'
# {"attemptCount":1,"nextAllowedAttemptAt":"2026-09-06T01:27:11Z",
# "snapshot":"snapshot-69ceba6a-…","sourceVolume":"pvc-5c8937fb-…","state":"initiated"}
# ② 新卷的 dataSource 与克隆模式
kubectl get volumes.longhorn.io -n longhorn-system <new-vol> -o jsonpath='{.spec.dataSource}'
# snap://pvc-5c8937fb-…/snapshot-69ceba6a-… ← 依赖“源卷 + 源快照”
# admission webhook 日志同时显示:{"op":"replace","path":"/spec/cloneMode","value":"full-copy"}
# ③ 源卷还在吗?——【不在】
kubectl get volumes.longhorn.io -n longhorn-system pvc-5c8937fb-…
# Error from server (NotFound): volumes.longhorn.io "pvc-5c8937fb-…" not found
# ④ 源 Longhorn Snapshot CR 还在吗?——【也不在】
kubectl get snapshots.longhorn.io -n longhorn-system --no-headers
# No resources found in longhorn-system namespace.
# ⑤ 但 CSI 层 VolumeSnapshot 仍是 readyToUse=true(假象!)
kubectl get volumesnapshots.snapshot.storage.k8s.io -n default
# vmsnapshot-1924a5e9-…-volume-disk-0 true vm-e2e-final-disk-0 2Gi longhorn snapcontent-69ceba6a-…
# ⑥ 副本存在但没有模式 → 引擎无法把它纳入 RW
kubectl get replicas.longhorn.io -n longhorn-system -o custom-columns='NAME:.metadata.name,MODE:.spec.mode'
# pvc-9cb0efed-…-r-29cc6b5c <none>
kubectl get volumes.longhorn.io -n longhorn-system <new-vol> -o jsonpath='{.status.replicas}'
# (空)→ 因此 robustness=degraded,且 numberOfReplicas=3 只建出 1 个副本创建源快照时的 admission 日志给出了 GC 的关键证据:
Kind=Snapshot, name: snapshot-69ceba6a-…, operation: CREATE patchOps:
{"op":"replace","path":"/metadata/labels","value":{"longhornvolume":"pvc-5c8937fb-…"}}
{"op":"replace","path":"/metadata/ownerReferences","value":[
{"apiVersion":"longhorn.io/v1beta2","kind":"Volume","name":"pvc-5c8937fb-…","uid":"629a54c4-…"}]}根因链
1. VM 快照(type: snap)→ 在【源卷】内创建 Longhorn Snapshot CR
该 CR 的 ownerReference 指向【源卷】 ← 决定命运的一步
2. KubeVirt VirtualMachineRestore 的语义是“**替换** VM 的卷”:
新建 restore-<uid>-disk-0 PVC(dataSource=snap://源卷/源快照,cloneMode=full-copy)
→ VM 改挂新 PVC → **原 PVC 被删除** → 源 Longhorn 卷被回收
3. 源卷消失 → K8s GC 按 ownerReference **级联删除 Longhorn Snapshot CR**
4. full-copy 克隆与步骤 2/3 形成【竞态】:
· 拷贝抢在 GC 之前完成 → cloneStatus 走到 copy-completed-awaiting-healthy → 卷可用(但常 degraded)
· GC 抢先 → 克隆失去数据源 → cloneStatus 永远 initiated(attemptCount 递增)或 failed
5. 失败分支:新卷只有 1 个无模式副本 → robustness=degraded、status.replicas 为空
→ VM 卡 Scheduling,事件 FailedAttachVolume "volume … is not ready for workloads"
6. 无论成功失败,控制面都全绿:restore complete=true、CSI VolumeSnapshot readyToUse=true、
DV Succeeded、甚至 VMI DataVolumesReady=True4 次重复实测(同一脚本、同一环境,结果不一致 → 证明是竞态)
| # | VM | 检查时 cloneStatus | 卷 robustness | 恢复后 VM | 结论 |
|---|---|---|---|---|---|
| A | vm-demo(案例 7) | failed(attemptCount=0, sourceVolume="") | unknown / detached | 卡 Scheduling | ✗ 失败 |
| B | vm-e2e-demo | initiated | unknown → 后续可用 | Running | ✓ 成功 |
| C | vm-e2e-final | initiated(120s 无变化) | degraded | 240s 未 Running,FailedAttachVolume | ✗ 失败 |
| D | vm-e2e-v3 | copy-completed-awaiting-healthy | degraded(attached) | Running | ✓ 成功(但降级) |
关键观察:成功的那两次,卷也停在
robustness=degraded—— 即numberOfReplicas=3只建出 1 个副本,冗余已经受损,只是"能用"。 也就是说type: snap恢复即便成功,也不等于恢复到健康状态。
Longhorn 克隆状态机(v1.11.2,实测观察到的取值)
initiated → copy-completed-awaiting-healthy → completed (正常路径)
initiated → failed (源已消失)
initiated(长期不变,attemptCount 递增,nextAllowedAttemptAt 不断推后) ← 源被 GC 后的卡死态判读要点:initiated 不是瞬时态的可靠信号;若 60~120s 内不前进, 基本可判定源已丢失,应直接按失败处理。
为什么 CSI 层看起来一切正常
CSI VolumeSnapshot/VolumeSnapshotContent 只记录"快照曾创建成功"的元数据, 不会在 Longhorn 后端快照被 GC 后回写 readyToUse=false。 所以 readyToUse=true 与"能否用它恢复"是两件事 —— 必须查 Longhorn 层 (snapshots.longhorn.io 是否存在、新卷 cloneStatus/robustness/replicas)。
实测事件流把这种"控制面全绿、数据面全红"的反差体现得淋漓尽致:
8m54s Normal VirtualMachineRestoreComplete virtualmachinerestore/vm-e2e-final-restore
Successfully completed VirtualMachineRestore vm-e2e-final-restore ← 绿
8m53s Warning VolumeFailedDelete persistentvolume/pvc-5c8937fb-…
persistentvolume pvc-5c8937fb-… is still attached to node sza122101.local ← 源 PV 在此被销毁
(VMI 条件)DataVolumesReady=True reason=AllDVsReady
All of the VMI's DVs are bound and ready ← 绿(但磁盘其实不可用)
(VMI 条件)Ready=False reason=GuestNotRunning
Guest VM is not reported as running ← 红
23s Warning FailedAttachVolume pod/virt-launcher-vm-e2e-final-hff9b
AttachVolume.Attach failed for volume "pvc-9cb0efed-…" :
rpc error: code = Aborted desc = volume pvc-9cb0efed-… is not ready for workloads ← 红
(virt-launcher 一直停在 Init: guest-console-log → PodInitializing)注意 VolumeFailedDelete 这一行:源 PV 是在"仍 attach 在旧节点"的状态下被删除的, 这正是源卷/源快照消失的时间点。
处置
| 场景 | 做法 |
|---|---|
| 生产(唯一可靠) | VolumeSnapshotClass 用 parameters.type: bak + 配置 Longhorn backup target(NFS/S3)。备份数据落在集群外,恢复不依赖源卷存活 |
| 仅验证快照创建能力 | 可用 type: snap,但只验证到 readyToUse=true 为止,不要验证恢复 |
| 已经踩坑(卷 degraded) | 该卷不可修复(源已 GC):删 VM/PVC 让卷回收,改用 bak 重做 |
| 想用 snap 又要能恢复 | 需保证源卷在克隆完成前不被删除(例如恢复到另一台 VM、且源 VM 保持存活),属绕行方案,不建议作为标准流程 |
配置 type: bak 的前置:
bash
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 default is not available(见案例 5)自动化检测
scripts/vm-e2e-test.sh 第 4 步已内置该判定:克隆 120s 未收敛时,自动反查 spec.dataSource 里的源卷是否仍存在,若已被删除则直接输出上述根因与整改建议, 不再只给一句模糊的 WARN。
经验
"控制面成功"与"数据面可用"必须分别验证(案例 7 已提出,本案例给出机制解释)。 对 Longhorn 卷,最小可信检查是这四元组:
status.state+status.robustness+status.replicas[*].mode+status.cloneStatus.state; 只看 PVCBound/ DVSucceeded/ VolumeSnapshotreadyToUse会漏掉整类故障。
案例 18 · harvester Pod 重启 10 次:启动期 fatal 与 VolumeSnapshotClass 的顺序依赖
现象
巡检发现控制面 Pod 重启次数偏高(且三个副本完全一致,说明是同一原因而非偶发):
NAME READY STATUS RESTARTS
harvester-74977d46-82v8s 1/1 Running 10
harvester-74977d46-rlwkr 1/1 Running 10
harvester-74977d46-sstnj 1/1 Running 10当前全部 Running 1/1,业务功能(API/UI/VM 生命周期)实测正常。
定位
bash
# 1) 先判断是否 OOM:看上次终止原因与退出码
kubectl -n harvester-system get pod -l app.kubernetes.io/name=harvester \
-o jsonpath='{range .items[*]}{.metadata.name}{" restarts="}{.status.containerStatuses[0].restartCount}{" lastState="}{.status.containerStatuses[0].lastState.terminated.reason}{" exitCode="}{.status.containerStatuses[0].lastState.terminated.exitCode}{" finishedAt="}{.status.containerStatuses[0].lastState.terminated.finishedAt}{"\n"}{end}'
# restarts=10 lastState=Error exitCode=1 finishedAt=2026-09-05T13:59:11Z
# → exitCode=1(Error)而非 137(OOMKilled);且 Pod 只有 requests 没有 limits
# (requests: cpu 250m / memory 256Mi,实测用量 484~550Mi)→ 排除 OOM
# 2) 取崩溃前日志(关键一步)
kubectl -n harvester-system logs harvester-74977d46-82v8s --previous --tail=12崩溃前的最后几行:
level=info msg="Waiting for ValidatingWebhookConfiguration harvester-validator..."
level=info msg="Waiting for MutatingWebhookConfiguration harvester-mutator..."
level=info msg="Admission webhooks are ready"
I0905 13:59:11 leaderelection.go:271] successfully acquired lease kube-system/harvester-controllers
level=fatal msg="failed to create harvester server: admission webhook \"validator.harvesterhci.io\"
denied the request: volumesnapshotclasses.snapshot.storage.k8s.io \"longhorn\" not found"根因
启动期顺序依赖 + 自建 webhook 拒绝:
- 本部署禁用了 Harvester 的 csi-snapshotter 子 chart(RKE2 自带
rke2-snapshot-controller, 两者并存会冲突),因此longhorn这个 VolumeSnapshotClass 不会由 chart 自动创建; - harvester server 启动时要建立/引用名为
longhorn的 VolumeSnapshotClass; - 此时 Harvester 自己的 admission webhook(
validator.harvesterhci.io)已经 Ready, 对该请求做校验时发现 VSC 不存在 → 拒绝; - harvester 把该错误当作
fatal→ 进程退出(exitCode=1)→ CrashLoopBackOff → 累计 10 次; - 直到 VolumeSnapshotClass 被创建(
scripts/post-install.sh/manifests/volumesnapshotclass-longhorn.yaml), 启动才成功,重启计数停止增长。
时间线佐证:Pod startTime=13:37Z,最后一次崩溃 finishedAt=13:59Z,此后 约 12 小时无新增重启 (巡检实测显示"上次=Error/exit=1, 710 分钟前";同期 harvester-webhook 为 restarts=0) → 属部署窗口内的一次性引导问题,非持续故障。
注意
finishedAt是 UTC 时间,换算"多久之前"时别按本地时区直接相减(本例曾误算为 19 小时)。
处置与预防
| 项 | 做法 |
|---|---|
| 已发生的重启 | 无需处理(已自愈);确认 lastState.terminated.finishedAt 不再更新即可 |
| 预防 | 部署完成后立即执行 scripts/post-install.sh,它会补建 VolumeSnapshotClass(这是"部署后必做 4 项"之一,见 01 手册 §6) |
| 巡检 | 用 logs --previous 而非当前日志定位崩溃原因;exitCode=1 + level=fatal 是应用级致命错误,137 才是 OOM |
| 判读重启告警 | 必须区分历史与进行中:对比重启计数是否仍在增长 / finishedAt 距今多久。healthcheck.sh 已按"近 1 小时"分级(进行中→FAIL,历史→WARN) |
经验
RESTARTS是一个只增不减的累计值,单看数字必然产生误报。 判断是否需要处置的唯一依据是"最近一次崩溃发生在什么时候、原因是什么":restartCount+lastState.terminated.{reason,exitCode,finishedAt}+logs --previous。
排障通用手法(本次沉淀)
| 手法 | 用途 | 示例 |
|---|---|---|
helm … --dry-run | grep | 先确认渲染结果,再谈集群状态 | 定位 featureGates 静默失效 |
kubectl apply --dry-run=server | 走完整 admission 链但不落盘 | 验证 KubeVirt gate 是否生效 |
读 vendor/ 源码 | 拿到常量与判定的权威定义 | SnapshotGate="Snapshot"、LiveMigration=GA |
| 对照实验(只改一个变量) | 快速隔离变量 | RWO vs RWX、snap vs bak、blank vs http |
| 逐层下钻到数据面 | K8s 层"成功"不等于数据面成功 | DV Succeeded 但 Longhorn cloneStatus=failed |
| 时间戳比对 | 判断组件是否加载了新配置 | virt-api startTime 早于 CR 变更时间 |
| 全名而非短名 | 避免资源歧义 | volumesnapshotclasses.snapshot.storage.k8s.io |