Skip to content

02 · 故障排查与修复记录(Harvester v1.8.2 on RKE2)

按"现象 → 定位过程 → 根因 → 修复 → 验证"记录本次实施遇到的全部问题, 含实测输出与源码级证据,可直接作为同类环境的排障手册。 标注 [非致命] 者属预期噪声,按案例 11/12 甄别后可忽略。

索引

#现象关键字严重度类别
1未指定 SC 的 PVC 绑到 longhorn 而非 harvester-longhorn存储
2longhorn VolumeSnapshotClass存储/快照
3snapshot feature gate not enabledKubeVirt
4backup target default is not available快照语义
5恢复"成功"但 VM 卡 Scheduling / GuestNotRunning极高快照恢复
6DisksNotLiveMigratable:热迁移被拒迁移
7blockdev: cannot open /dev/cdi-block-volume: Permission denied存储/CDI
8RWX 卷 qemu-img 失败、无 share-manager存储/RWX
9running and runstrategy are mutually exclusive / Invalid RunStrategy (Stopped)VM 操作
10VolumeSnapshot 删除卡住(finalizer)快照
11harvester-aggregation 拨号 /v3/connect 失败 [非致命]Rancher/Steve
12ssl-certificates 反复 requeue [非致命]证书/Ingress
13镜像导入卡在 99.62% 后失败(nbdkit curl)镜像源
14kubectl get vsc 查到 contents 而非 classes工具陷阱
15Failed to sync Longhorn VolumeAttachment [非致命]Longhorn
16巡检脚本自身误报:7 个 FAIL 全是假警报工具/脚本
17type: snap 与 VM 恢复结构性不兼容(源卷被 GC → 克隆永不收敛)极高快照恢复
18harvester 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 即双默认

根因

  1. Longhorn 子 chart 默认 persistence.defaultClass=true,会创建第二个默认类 longhorn
  2. Harvester chart 的 _helpers.tpl 渲染 harvester-longhorn 的默认注解时会 lookup 集群现状, 双默认共存时结果不确定。

修复

yaml
# values 层面根治(推荐)
longhorn:
  persistence:
    defaultClass: false
bash
# 现场校正
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 longhorndriver.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=DeployedobservedGeneration 已追平、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.typeLonghorn 行为依赖
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)   ← 源卷已不存在

根因(三个事实叠加)

  1. type: snapLonghorn 卷内快照:数据留在源卷内部,快照对象只是一个引用 (handle 形如 snap://<源卷>/<快照名>);
  2. 从这种快照恢复时,Longhorn 新建的卷 dataSource=snap://…,需要从源卷克隆数据;
  3. 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.iocloneStatus / 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.gopkg/data/template.gopkg/image/cdi/common.go 中都硬编码 ReadWriteMany

修复方向:把 VM 磁盘改为 ReadWriteMany。但要注意 volumeMode 与 Longhorn RWX 的兼容性 (案例 7、8),三者组合的实测结论:

accessModesvolumeMode实测结果
ReadWriteOnceFilesystem(默认)✓ 完全可用:导入/引导/稳定运行 5h+/快照/恢复流程均通过;但不支持热迁移
ReadWriteManyBlock✗ CDI importer 报 blockdev: cannot open /dev/cdi-block-volume: Permission denied
ReadWriteManyFilesystem✗ 本环境 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:

VMaccessModesvolumeMode结果
vm-rwo-blank-testReadWriteOnceFilesystemDV Succeeded 100%(75s);VM/VMI Running
vm-migrate-testReadWriteManyFilesystemDataVolumeError;importer 重启 5 次 ✗

处置建议(按优先级)

  1. 短期:VM 磁盘统一用 RWO + Filesystem(功能完整),迁移改用冷迁移(已实测通过);
  2. 中期:修复 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 热迁移
  3. 长期:若确认节点镜像不含 NFS 客户端,则该形态下热迁移不可用, 应在运维规范中把"冷迁移 + 节点维护模式"定为标准做法。

案例 9 · VM 启停语义:runningrunStrategy 互斥;停机值是 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-protectionwrangler.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。)

规避(生产建议,按优先级)

  1. 不要把公网 URL 当作生产镜像源:先把镜像下载入库为 Harvester VirtualMachineImage (UI: Images → Create,或 harvesterhci.io/v1beta1 VirtualMachineImage),VM 创建时引用本地镜像;
  2. 内网搭建镜像仓库/HTTP 源(Nginx、Nexus、S3 兼容存储)并固定使用;
  3. 已有可用卷时用 CDI clone / DataVolume source.pvc 复用(全程内网,不依赖外部源);
  4. 临时验证可用 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.iovolumesnapshotclassesvolumesnapshotcontents 的短名冲突,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-attacherlonghorn-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 创建/冷迁移/快照/恢复全部成功 —— 说明是巡检脚本错了,不是集群错了

根因(六类,均为"取值方式"缺陷)

#缺陷说明
1grep -o '"type":"Available"[^}]*"status":"True"'kubectl -o json 输出的是带空格的格式化 JSON,且键按字母序排列(statustype 之前),模式必然失配
2grep -o '"featureGates":\[[^]]*\]'实际输出为 "featureGates": [(冒号后有空格)→ 取到空值
3awk '$2!~/^([0-9]+)\/\1$/'awk 正则不支持反向引用 \1 → 条件恒为真,所有 Running Pod 都被判"未就绪"
4helm history | awk '{print $3"="$4}'UPDATED 列是 2026-09-05 12:34:56.789 +0800 CST含空格导致列号整体错位
5kubectl -n <不存在的ns> get deploy 判退出码命名空间不存在时仍输出 No resources found返回 0 → "已部署 ingress-nginx"误判
6kubectl get vm -A$3 取状态列序是 NAMESPACE NAME AGE STATUS READYSTATUS 是 $4$3 是 AGE
7按"配置名"查 webhookwebhook 名在配置内部:配置叫 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=initiatedrobustness=unknown、Longhorn 卷删除滞后几秒,都会造成瞬时误报; vm-e2e-test.sh 已改为轮询等待(克隆 ≤120s、卷回收 ≤90s)后再下结论。

结果对比

修复前修复后
FAIL7(全部误报)0
WARN8(含 3 条误报)6(均为真实/已知非致命)
PASS2330

修复后剩余 WARN 全部可解释:harvester Pod 历史重启 10 次(本次多次升级/重启所致)、 存在 2 个 Longhorn VSC(longhornlonghorn-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=True

4 次重复实测(同一脚本、同一环境,结果不一致 → 证明是竞态)

#VM检查时 cloneStatusrobustness恢复后 VM结论
Avm-demo(案例 7)failedattemptCount=0, sourceVolume=""unknown / detachedScheduling✗ 失败
Bvm-e2e-demoinitiatedunknown → 后续可用Running✓ 成功
Cvm-e2e-finalinitiated(120s 无变化)degraded240s 未 RunningFailedAttachVolume✗ 失败
Dvm-e2e-v3copy-completed-awaiting-healthydegraded(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; 只看 PVC Bound / DV Succeeded / VolumeSnapshot readyToUse 会漏掉整类故障。

案例 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 拒绝

  1. 本部署禁用了 Harvester 的 csi-snapshotter 子 chart(RKE2 自带 rke2-snapshot-controller, 两者并存会冲突),因此 longhorn 这个 VolumeSnapshotClass 不会由 chart 自动创建
  2. harvester server 启动时要建立/引用名为 longhorn 的 VolumeSnapshotClass;
  3. 此时 Harvester 自己的 admission webhook(validator.harvesterhci.io)已经 Ready, 对该请求做校验时发现 VSC 不存在拒绝
  4. harvester 把该错误当作 fatal → 进程退出(exitCode=1)→ CrashLoopBackOff → 累计 10 次;
  5. 直到 VolumeSnapshotClass 被创建(scripts/post-install.sh / manifests/volumesnapshotclass-longhorn.yaml), 启动才成功,重启计数停止增长。

时间线佐证:Pod startTime=13:37Z,最后一次崩溃 finishedAt=13:59Z,此后 约 12 小时无新增重启 (巡检实测显示"上次=Error/exit=1, 710 分钟前";同期 harvester-webhookrestarts=0) → 属部署窗口内的一次性引导问题,非持续故障。

注意 finishedAtUTC 时间,换算"多久之前"时别按本地时区直接相减(本例曾误算为 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