Skip to content

04 · 部署实战 B:容器化 KubeVirt 装进既有 K8s

形态:不换底座,只叠加能力。在已运行的 RKE2 集群上用 Helm 部署 Harvester v1.8.2 (promote 模式),把 KubeVirt + Longhorn + CDI + harvester 控制器作为一组子 chart 装进去。

实测结果:控制面全绿、VM 端到端(创建 → 冷迁移 → 快照 → 恢复 → 清理)全流程通过, 平台巡检 PASS=30 / WARN=6 / FAIL=0。 本章命令均可直接复制执行;标注 ⚠️/❌ 的是已定位的阻塞点与坑。

03 ISO 一体机 的取舍见 10 五方对比。


1. 选型与架构

1.1 两种形态:promote 到底是什么

形态说明适用网络参数来源
Harvester OS(ISO)节点 Harvester,内置 RKE2,全托管全新裸金属虚拟化集群装机时自己建(config.yaml
Helm 装进既有 RKE2(本章)把 Harvester 作为一组 chart 叠加到现有集群已有 K8s,想加虚拟化能力★ 必须用 promote 告知既有集群的真实 CIDR

promote 的三个键(clusterDNS / clusterPodCIDR / clusterServiceCIDR)之所以是硬性要求: Harvester 的 kube-vipharvester-network-controllerharvester-load-balancer按这些值计算路由与 LB 后端。ISO 形态下集群是它自己建的,参数天然一致; 而叠加部署时若沿用默认值,这三个组件会算错路由——症状是 LB/VIP 不通、网络配置下发失败, 且报错点离根因很远。

⚠️ 官方支持矩阵以 Harvester OS 为主线,Helm 叠加部署属「可行但需自行兜底」的路径: 本章所有固化动作(§5)与验收(§6)都是为此而设。

1.2 组件拓扑(实测)

                        ┌───────────────────────────────────────────────┐
  浏览器 / kubectl ───► │ Service harvester(NodePort 32735, https)      │
       (★ 必须带 SNI)    └──────────────────┬────────────────────────────┘

                    ┌──────────────────────┴──────────────────────┐
                    │ harvester / harvester-webhook (Deployment)   │ ← Steve 聚合 API + Admission
                    └──────────────────────┬──────────────────────┘
                                           │ APIService (harvesterhci.io …)
  ┌────────────────────────────────────────┴─────────────────────────────────────┐
  │ kube-apiserver(RKE2 提供,★ 不是 Harvester 自带)                              │
  └──┬──────────────┬──────────────┬──────────────┬──────────────┬───────────────┘
     │              │              │              │              │
 kubevirt CR     cdi CR      longhorn CR    harvesterhci.io  snapshot.storage.k8s.io
     │              │              │              │              │
 virt-operator  cdi-operator longhorn-mgr   harvester 控制器  rke2-snapshot-controller
 virt-api       cdi-deploy   longhorn-csi    (20+ 个)         (RKE2 自带 ✓)
 virt-controller cdi-upload  instance-mgr
 virt-handler(DS)

 virt-launcher Pod(每 VMI 一个,内含 qemu-kvm)

与 ISO 形态的关键差别:K8s 控制面、CNI、snapshot-controller 都由既有集群提供, Harvester 只带来「虚拟化 + 存储 + 网络管理」这一层。因此冲突预判(§2.3)比装机更重要。

1.3 版本矩阵与子 chart 清单(实测)

组件版本 / 值来源
Harvester chart1.8.2(appVersion v1.8.2deploy/charts/harvester
Helm releaseharvester @ harvester-system,REVISION 1→5helm get metadata
KubeVirt1.7.4-150700.3.24.2registry.suse.com/suse/sles/15.7/virt-*
Longhorn1.11.2子 chart longhorn-1.11.2.tgz
CDI子 chart 0.3.0charts/cdi
集群RKE2 v1.34.4-rc11+rke2r1 / containerd;3 节点,各 8C / ~32Gkubectl get node
网络Pod 10.42.0.0/16,Svc 10.43.0.0/16,DNS 10.43.0.10Calico VXLAN实测
入口NodePort 32735 + SNI harvester.local实测

10 个子 chart 必须齐备(缺一个就会在运行期以「某功能不可用」的形式暴露):

cdi, harvester-load-balancer, harvester-network-controller, harvester-networkfs-manager,
harvester-node-manager, kube-vip, kubevirt, kubevirt-operator, longhorn, whereabouts

2. 前置条件与冲突预判

2.1 硬性前置

  • [ ] RKE2 集群健康:kubectl get node 全部 Ready
  • [ ] 节点数 ≥ 3:Longhorn 默认 numberOfReplicas=3,节点不足会让卷永久 Pending
  • [ ] 每节点 Longhorn 数据盘空间充足(实测 max ≈127 GB/节点,路径 /var/lib/longhorn
  • [ ] helm v3 / kubectl / curl 可用,执行账号具备 cluster-admin
  • [ ] 放行 NodePort(本环境 32735);时钟同步(Longhorn 与 TLS 均敏感)
  • [ ] ★ 节点支持硬件虚拟化:egrep -c '(vmx|svm)' /proc/cpuinfo > 0 且 /dev/kvm 存在 (在 VM 里跑则需宿主开嵌套虚拟化,见 02 §1.2)

2.2 ★ CIDR 必须实测探测,不要抄任何文档

bash
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml

# clusterDNS:CoreDNS Service 的 ClusterIP
kubectl -n kube-system get svc rke2-coredns-rke2-coredns -o jsonpath='{.spec.clusterIP}'; echo

# clusterPodCIDR:节点级 podCIDR 通常是 /24,★ 必须归一化为集群级 /16
kubectl get node -o jsonpath='{.items[0].spec.podCIDR}'; echo      # 10.42.6.0/24 → 10.42.0.0/16

# clusterServiceCIDR:由 kubernetes Service 的 ClusterIP 反推
kubectl -n default get svc kubernetes -o jsonpath='{.spec.clusterIP}'; echo   # 10.43.0.1 → 10.43.0.0/16
探测项探测方式常见错误
clusterDNSCoreDNS Service 的 ClusterIP抄成节点 /etc/resolv.conf 里的宿主 DNS
clusterPodCIDRnode.spec.podCIDR 归一化到 /16直接填 /24 → 跨节点 Pod 路由算错
clusterServiceCIDRkubernetes Service ClusterIP 反推与 CNI 实际配置不一致

💡 deploy-harvester.sh 把这三项做成自动探测 + 渲染 values,探测不到直接 die, 从机制上消灭「手填 CIDR 填错」这一类故障。

2.3 与既有组件的冲突预判(实测结论)

既有组件是否冲突处置
Calico(VXLAN)❌ 不冲突Harvester 不替换 CNI;VM 走 masquerade + Pod 网络,实测跨节点可达
RKE2 rke2-snapshot-controller⚠️ 会冲突csi-snapshotter.enabled=false,复用 RKE2 自带;代价见 §4.3
ingress-nginx / traefik⚠️ 需注意用 NodePort 暴露 harvester Service,避开既有 ingress 的 80/443
既有默认 StorageClass⚠️ 会冲突longhorn.persistence.defaultClass=false,保证 harvester-longhorn唯一默认类
既有 Longhorn⚠️ 版本需核对子 chart 会装 1.11.2;若集群已有 Longhorn,先核对版本与 defaultDataPath
既有 KubeVirt⚠️ 不要共存一套集群只应有一个 virt-operator,否则 KubeVirt CR 互相覆盖
Rancher(嵌入模式)❌ 本方案关闭rancherEmbedded: false,避免引入 Rancher/Steve 隧道与 harvester-aggregation 拨号错误

2.4 官方安装模式 vs promote 模式(对照)

维度官方模式(ISO / Harvester OS)promote 模式(本章)
K8s 控制面Harvester 自建 RKE2复用既有 RKE2
CIDR装机时确定,天然一致★ 必须探测并显式声明
snapshot-controller自带 csi-snapshotter复用 RKE2 的,子 chart 要关
默认 StorageClass装机即唯一手工校正(§5 A)
VolumeSnapshotClasschart 自动建★ 子 chart 关掉后必须手工补建(§5 B)
入口VIP(kube-vip)+ DashboardNodePort + SNI 主机名
升级Harvester ISO 升级链路helm upgrade(★ 之后必须重跑 post-install)
支持强度官方主线可行但需自行兜底

这张表就是「为什么本章有 §5 这一节」的答案:promote 模式下有四件事不会自动发生, 必须部署后立刻固化,否则会在几天后以「快照不可用 / PVC 绑错类」的形式爆出来。


3. 部署步骤

3.1 一键部署(推荐)

bash
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml
bash scripts/deploy-harvester.sh --chart /path/to/harvester --nodeport 32735
参数 / 环境变量默认说明
--chart无(则 git clone 对应 tag 取 deploy/charts/harvester目录或 .tgz离线环境必须显式指定
--versionv1.8.2仅在需要 clone 时生效
--namespace / HV_NAMESPACEharvester-system
--release / HV_RELEASEharvester
--nodeport / HV_NODEPORT留空=chart 自动分配★ 生产建议固定,便于防火墙放行
--sni-host / HV_SNI_HOSTharvester.localAPI/UI 必须带该主机名访问
--skip-post / SKIP_POST=10只装 chart,不做安装后修复(不建议
RUN_HOOKS=10跨版本升级时才需要跑 Helm hooks

3.2 脚本的六个阶段(每一步都可手工复现)

阶段做什么失败即停的关键判据
1/6 前置检查工具、KUBECONFIG、节点数、containerd、K8s 版本节点 ❤️ 只 warn(Longhorn 副本无法调度);连不上集群 die
1.1 探测网络自动取 clusterDNS / clusterPodCIDR(归一化 /16)/ clusterServiceCIDR三项任一探测不到 → die,提示手工在 values 指定
2/6 准备 chart.tgz 自动解压;定位 Chart.yaml核对子 chart 齐备charts/die(提示 helm dependency build
3/6 渲染 values把探测值写进 values;按集群现状决定两个开关
3.1 dry-run 预检helm … --dry-run 后 grep 渲染结果①未渲染出 - Snapshotdie(values 路径写错)②默认 SC 注解数 ≠1 → warn
4/6 安装 / 升级helm upgrade --install … --no-hooks --wait --timeout 20m--wait 超时不退出,转逐项就绪检查
4.1 / 4.2 等就绪10 个 Deployment + 3 个 DaemonSet 逐个 rollout status未就绪项汇总打印,并附 Pod/Events 摘要
5/6 安装后修复post-install.sh(§5)返回非 0 只 warn,要求人工复核
6/6 验收12 项 chk(§6.4)+ 异常 Pod + SNI 连通性任一失败 RC=1

两个自动决策开关(脚本按集群现状推导,不要手填):

bash
# ① RKE2 已自带 snapshot-controller → 禁用 Harvester 的 csi-snapshotter(避免双控制器抢锁)
kubectl -n kube-system get deploy rke2-snapshot-controller && CSI_SNAPSHOTTER_ENABLED=false

# ② 集群已有别的默认 StorageClass → Longhorn 不设为默认(避免双默认类)
kubectl get sc -o json | grep -c 'is-default-class": "true"'

3.3 手工等价命令(排障 / 无脚本环境)

bash
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml

# ① 探测三项 CIDR(见 §2.2),填入 values-harvester.yaml

# ② dry-run 预检(★ 强烈建议:能在不落盘的情况下发现 values 路径写错)
helm upgrade --install harvester <chart> -n harvester-system --create-namespace \
  -f values-harvester.yaml --no-hooks --dry-run > /tmp/dryrun.txt
grep -q '^\s*- Snapshot$' /tmp/dryrun.txt && echo "gate OK"
grep -c 'is-default-class: "true"' /tmp/dryrun.txt          # 期望 1

# ③ 安装
helm upgrade --install harvester <chart> -n harvester-system --create-namespace \
  -f values-harvester.yaml --no-hooks --wait --timeout 20m

# ④ 部署后固化(★ 必做,见 §5)
bash scripts/post-install.sh

# ⑤ 验收
bash scripts/healthcheck.sh
bash scripts/vm-e2e-test.sh

⚠️ --no-hooks 的理由:Longhorn 的 pre/post-upgrade 作业在版本不变的配置变更时并无必要, 且可能对存量卷做多余操作;harvester-cdi-namespace-modify 作业首次安装后会自动完成。 跨版本升级时才设 RUN_HOOKS=1(去掉该 flag)。

3.4 就绪观察

bash
# 10 个 Deployment
for dp in harvester harvester-webhook harvester-load-balancer harvester-network-controller \
          virt-operator virt-api virt-controller cdi-operator cdi-deployment cdi-uploadproxy; do
  kubectl -n harvester-system rollout status deploy/$dp --timeout=300s
done

# 3 个 DaemonSet(注意 longhorn-manager 可能在 longhorn-system)
for ds in virt-handler longhorn-manager harvester-node-manager; do
  kubectl -n harvester-system rollout status ds/$ds --timeout=300s \
    || kubectl -n longhorn-system rollout status ds/$ds --timeout=300s
done

💡 组件未就绪时先看「上次崩溃」而不是当前日志kubectl logs <pod> --previous --tail=12,配合 lastState.terminated.{reason,exitCode,finishedAt}exitCode=1 + level=fatal 是应用级致命错误,137 才是 OOM——两者处置完全不同。


4. values 关键项逐条解释

4.1 完整 values(实测可用)

yaml
# --- 1. 部署形态 -------------------------------------------------------------
# false = 独立 Harvester(不内嵌 Rancher)。内嵌 Rancher 会引入 Rancher/Steve 隧道,
# 产生 harvester-aggregation 连接错误(非致命但噪声大)。
rancherEmbedded: false

# --- 2. 复用既有 RKE2 集群网络(promote 模式关键项)--------------------------
# 必须与 RKE2 实际 CIDR 完全一致,否则 kube-vip / network-controller 会算错路由。
promote:
  clusterDNS: 10.43.0.10          # kubectl -n kube-system get svc rke2-coredns-rke2-coredns
  clusterPodCIDR: 10.42.0.0/16    # ★ 集群级 /16,不是节点级 /24
  clusterServiceCIDR: 10.43.0.0/16

# --- 3. API/UI 暴露方式 ------------------------------------------------------
# NodePort:无 LB/Ingress 环境下最稳妥,配合 SNI 主机名访问(见 §6.1)。
service:
  harvester:
    type: NodePort                # 本环境实际端口:32735
    # nodePorts:
    #   harvester: 32735          # 固定端口时取消注释

# --- 4. 禁用 Harvester 自带 csi-snapshotter 子 chart -------------------------
# 原因:RKE2 已自带 kube-system/rke2-snapshot-controller(含 CRD 安装作业),
#       再装一份会造成 snapshot-controller 双实例抢锁与 CRD 冲突。
# ★ 代价:该子 chart 同时负责创建名为 longhorn 的 VolumeSnapshotClass,
#         禁用后必须手工补建(§5 B)。
csi-snapshotter:
  enabled: false

# --- 5. Longhorn 存储 --------------------------------------------------------
longhorn:
  persistence:
    # 关键:不要让用户态 longhorn SC 成为默认 StorageClass。
    # Harvester 依赖 harvester-longhorn 为【唯一】默认类;双默认类会让
    # 未指定 storageClassName 的 PVC 绑到错误的类。
    defaultClass: false
  defaultSettings:
    defaultDataPath: /var/lib/longhorn   # 需为独立磁盘/分区挂载点,预留足够容量

# --- 6. KubeVirt 特性门 ------------------------------------------------------
# ★ 路径务必是 kubevirt.spec.configuration...(子 chart 模板渲染的是 .Values.spec)
kubevirt:
  spec:
    configuration:
      developerConfiguration:
        featureGates:
          - CPUManager
          - DeclarativeHotplugVolumes
          - ExpandDisks
          - EnableVirtioFsConfigVolumes
          - HostDevices
          - Snapshot        # ← VM 快照必需;单数!(Beta since v1.3.0)

4.2 ★ 六个「写错就静默失效」的点

#错误写法为什么静默正确写法 / 验证方法
1kubevirt.configuration.…生成一个无人读取的键,Helm 不报错kubevirt.spec.configuration.…helm --dry-rungrep -q '^\s*- Snapshot$'
2gate 写成 Snapshots(复数)KubeVirt 当未知 gate 静默忽略Snapshot(单数);webhook 不再报 snapshot feature gate not enabled
3clusterPodCIDR 填节点级 /24路由计算错,症状出现在 LB/网络控制器,离根因很远归一化为集群级 /16
4忘了 csi-snapshotter.enabled=false双 snapshot-controller 抢 leader lock,快照时好时坏关掉子 chart + 手工补建 VSC
5忘了 longhorn.persistence.defaultClass=false出现两个默认 SC,PVC 绑错类kubectl get sc -o json | grep -c 'is-default-class": "true"' → 必须 1
6kubectl edit kubevirt 改配置下次 helm upgrade回滚(CR 带 meta.helm.sh/release-name 归属注解)配置一律落在 values(§7)

💡 判据:凡是「改了没报错、但行为没变」,先怀疑键路径归属注解, 再怀疑组件本身。helm get values + helm get manifest | grep 是最快的对照手段。

4.3 ★ VolumeSnapshotClass 的 type 选型(决定快照能不能恢复)

type语义依赖恢复可靠性
snapLonghorn 卷内快照无(不需要 backup target)⚠️ 恢复需从源卷克隆;源卷被删则 cloneStatus=failedVM 起不来
bakLonghorn 备份★ 必须配置 backup target(NFS/S3)✅ 数据在集群外,恢复不依赖源卷存活(生产推荐
yaml
kind: VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
metadata:
  name: longhorn                      # ★ 名字必须是 longhorn,harvester 启动时按名引用
driver: driver.longhorn.io
deletionPolicy: Delete
parameters:
  type: snap                          # 生产改 bak,并先配置 backup target

为什么名字必须是 longhorncsi-snapshotter 子 chart 被禁用后,harvester server 启动时 仍会引用名为 longhorn 的 VolumeSnapshotClass;不存在时它自己的 webhook (validator.harvesterhci.io)会拒绝该请求并把错误当 fatal

level=fatal msg="failed to create harvester server: admission webhook \"validator.harvesterhci.io\"
             denied the request: volumesnapshotclasses.snapshot.storage.k8s.io \"longhorn\" not found"

实测后果:三个 harvester 副本各重启 10 次exitCode=1,非 OOM), 直到 VSC 被补建才停止增长——这是一个部署窗口内的一次性引导问题,不是持续故障。

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

⚠️ type: snap 的致命陷阱:CSI 层的 VolumeSnapshot.readyToUse=true 只表示 「快照曾创建成功」,不会在 Longhorn 后端快照被 GC 后回写为 false。 于是会出现「控制面全绿、数据面全红」: VirtualMachineRestoreComplete 成功 → 源 PV 被删(VolumeFailedDelete)→ 新卷 cloneStatus 卡在 initiatedattemptCount 递增)→ VMI Ready=False / GuestNotRunning必须查 Longhorn 层四元组status.state + status.robustness + status.replicas[*].mode + status.cloneStatus.state。 详见 06 存储实战。


5. ★ 部署后必做的 4 项固化(post-install.sh 全自动)

promote 模式下有 4 件事不会自动发生,不做就会在几天后爆出来:

#项目不做的后果脚本段落
A默认 StorageClass 校正为唯一harvester-longhorn未指定 SC 的 PVC 绑错类;VM 磁盘行为异常A.
B补建 VolumeSnapshotClass longhorn 并确认 typeVM 快照无类可用 / harvester server 启动 fatal 反复重启B.
CKubeVirt featureGates 增加 Snapshot(走 values 持久化)快照被 webhook 拒绝:snapshot feature gate not enabledC.
D滚动重启 virt-api(逐个删 Pod,不断流)CR 已改但 gate 不生效,快照仍被拒D.
E服务端 dry-run 探测 webhook 是否真的放行「以为改好了」——无法证明 gate 生效E.
bash
bash scripts/post-install.sh                 # 默认 type=snap
VSC_TYPE=bak bash scripts/post-install.sh    # 备份型(需先配置 backup target)
CHART_PATH=/opt/harvester-chart/harvester bash scripts/post-install.sh   # ★ 用 helm 持久化 gate(推荐)

5.1 A · 默认 StorageClass

bash
kubectl get sc                                                    # 期望只有 harvester-longhorn 带 (default)
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 若未带 longhorn.persistence.defaultClass=false 会被改回。 根治必须在 values(§4.1 第 5 段)。

5.2 B · VolumeSnapshotClass

见 §4.3。要点:名字必须是 longhornparameters.type 决定快照语义,生产用 bak

5.3 C · Snapshot feature gate(三层叠加,需逐层排除)

检查命令
① gate 名必须是 Snapshot(单数,Alpha v0.30.0 / Beta v1.3.0kubectl get kubevirt -n harvester-system kubevirt -o jsonpath='{.spec.configuration.developerConfiguration.featureGates}'
② values 路径必须是 kubevirt.spec.configuration.…(漏 spec 静默失效)helm get values harvester -n harvester-system
virt-api 是否重启gate 仅在启动时加载kubectl -n harvester-system get pod -l kubevirt.io=virt-api 看 AGE

持久化写法(推荐,CHART_PATH 已设时脚本自动走这条):

bash
helm upgrade harvester <chart> -n harvester-system --reuse-values --no-hooks \
  --set 'kubevirt.spec.configuration.developerConfiguration.featureGates={CPUManager,DeclarativeHotplugVolumes,ExpandDisks,EnableVirtioFsConfigVolumes,HostDevices,Snapshot}'

改完等 virt-operator 调和:status.observedGeneration == metadata.generation,且 phase=Deployed

5.4 D · virt-api 滚动重启(★ 最容易漏的一步)

实测:即使 KubeVirt CR 已含 Snapshotphase=Deployed,virt-api 不重启, webhook 仍持续返回 snapshot feature gate not enabled

做法上的两个讲究:

讲究理由
逐个删 Pod,而不是 rollout restart不改 Deployment 模板,避免与 virt-operatorinstall-strategy 注解冲突
全部就绪再删下一个webhook 不断流;期间只有 VM 创建/删除等写操作可能短暂被拒,存量 VM 不受影响
bash
for p in $(kubectl -n harvester-system get pod -l kubevirt.io=virt-api -o jsonpath='{.items[*].metadata.name}'); do
  kubectl -n harvester-system delete pod "$p" --wait=false
  kubectl -n harvester-system rollout status deploy/virt-api --timeout=120s
done

5.5 E · 用服务端 dry-run 证明 gate 真的生效

bash
cat > /tmp/vmsnap-probe.yaml <<'EOF'
apiVersion: snapshot.kubevirt.io/v1beta1
kind: VirtualMachineSnapshot
metadata: {name: gate-probe, namespace: default}
spec:
  source: {apiGroup: kubevirt.io, kind: VirtualMachine, name: __nonexistent_vm__}
EOF
kubectl apply --dry-run=server -f /tmp/vmsnap-probe.yaml

判读(★ 关键:故意用一个不存在的源 VM):

返回结论
snapshot feature gate not enabled❌ gate 未生效 → 回查 §5.3 三层
… "__nonexistent_vm__" not found / deniedgate 已生效(请求已越过 gate 校验,仅因源 VM 不存在而失败)

💡 --dry-run=server 会完整经过 admission 链但不落盘,是验证 webhook/gate 类配置 最快、最安全的手段。凡是「改了配置想确认生效」,优先想它。


6. 访问与验证

6.1 ★ API/UI 必须带 SNI 主机名

Harvester 的 API/UI 校验 SNI/Host,直接用 IP 访问会失败:

bash
NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
PORT=$(kubectl -n harvester-system get svc harvester \
       -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')

curl -k --resolve harvester.local:${PORT}:${NODE_IP} \
     https://harvester.local:${PORT}/dashboard/            # → 200 ✓
curl -k --resolve harvester.local:${PORT}:${NODE_IP} \
     https://harvester.local:${PORT}/v1/harvester          # → schema 列表 ✓

浏览器访问:本机 hosts<NODE_IP> harvester.local,再打开 https://harvester.local:32735/

聚合层是否正常,用「能枚举到哪些资源」来判断

snapshot.kubevirt.io.virtualmachinesnapshots / virtualmachinesnapshotcontents / virtualmachinerestores
harvesterhci.io.virtualmachinerestores / volumeremoterestores
groupsnapshot.storage.k8s.io.volumegroupsnapshot{classes,contents,snapshots}
k3s.cattle.io.etcdsnapshotfiles

结论(实测):Harvester v1.8 的 VM 快照走 KubeVirt 原生 snapshot.kubevirt.io API不存在 Harvester 自有的 VirtualMachineSnapshot CRD。找错 API group 会白排查半天。

6.2 平台健康巡检:healthcheck.sh

20 s只读、不创建任何 VM;非零退出码可直接接告警。实测 PASS=30 / WARN=6 / FAIL=0

检查内容
1节点与运行时(Ready、containerd、/dev/kvm、虚拟化扩展)
2Harvester 控制面 Pod(含重启计数分级:近 1 小时内 → FAIL,历史 → WARN)
3Helm release(STATUS=deployed、REVISION)
4KubeVirt / CDI 自定义资源(Available=Truephase=DeployedfeatureGates 含 Snapshot
5存储:StorageClass(默认类唯一)/ VolumeSnapshotClass / Longhorn
6Webhook / APIService
7虚拟机与卷
8已知非致命日志噪声甄别 + API/UI 连通性
bash
bash scripts/healthcheck.sh                # 完整报告
BRIEF=1 bash scripts/healthcheck.sh        # 只出结论行
VERBOSE=1 bash scripts/healthcheck.sh      # 附带证据明细

已甄别为非致命的噪声(降级 WARN,不计入 FAIL):

噪声为什么非致命
harvester-aggregation 反复拨号 /v3/connect 失败rancherEmbedded=false,本来就没有 Rancher 隧道
ssl-certificates 控制器反复 requeue未配置自定义证书时的预期行为
longhorn-manager Failed to sync Longhorn VolumeAttachment卷生命周期瞬态,自愈

6.3 能力端到端验证:vm-e2e-test.sh

自建自清,6 个阶段(实测全流程通过):

阶段内容实测基线
0前置条件检查(SC/VSC/gate/节点数)
1创建 VM(RWO + Filesystem + blank 空盘DV Succeeded → VMI Running
2冷迁移(停机 → 换节点 → 启动)落点节点变化 ✓
3创建 VM 快照(snapshot.kubevirt.io/v1beta1~5 s
4从快照恢复(停机 → 恢复 → 数据面核对卷四元组核对通过
5清理测试对象并核查无残留卷数归 0
bash
bash scripts/vm-e2e-test.sh                          # 全流程
bash scripts/vm-e2e-test.sh --skip-migrate           # 跳过冷迁移
bash scripts/vm-e2e-test.sh --keep                   # 结束后保留测试 VM 便于人工检查
# 其他:--vm NAME / --ns NS / --size SIZE / --skip-snapshot

★ 第 4 步内置「克隆 120 s 未收敛 → 自动反查 spec.dataSource 源卷是否还存在 → 直接输出根因与整改建议」的判定,不会只给一句模糊 WARN(对应 §4.3 的 snap 陷阱)。

6.4 验收清单(deploy-harvester.sh 第 6 阶段的 12 项)

  • [ ] helm get metadataSTATUS: deployed
  • [ ] KubeVirt CR Available=True
  • [ ] featureGatesSnapshot
  • [ ] CDI CR Available=True
  • [ ] 默认 StorageClass 是 harvester-longhorn
  • [ ] 默认类唯一grep -c 'is-default-class": "true"'1
  • [ ] VolumeSnapshotClass longhorn 存在且 driver=driver.longhorn.io
  • [ ] Harvester APIService 数量 ≥1
  • [ ] KubeVirt 校验 webhook 存在
  • [ ] harvester-system 无异常 Pod(非 Running/Completed)
  • [ ] 无高频重启 Pod(RESTARTS > 5 告警)
  • [ ] curl --resolve …/dashboard/200

7. 升级与回滚

7.1 升级前留档(强烈建议)

bash
helm get values harvester -n harvester-system > values-backup-$(date +%F).yaml
kubectl get kubevirt -n harvester-system kubevirt -o yaml > kubevirt-cr-$(date +%F).yaml
kubectl get sc,volumesnapshotclasses.snapshot.storage.k8s.io -o yaml > storage-backup-$(date +%F).yaml

7.2 变更配置 / 升级

bash
# 变更配置(示例:加 feature gate)——★ 走 values,不要 kubectl edit
helm upgrade harvester <chart> -n harvester-system --reuse-values --no-hooks \
  --set 'kubevirt.spec.configuration.developerConfiguration.featureGates={CPUManager,DeclarativeHotplugVolumes,ExpandDisks,EnableVirtioFsConfigVolumes,HostDevices,Snapshot}'

# 跨版本升级:需要跑 hooks(Longhorn pre/post-upgrade 作业)
RUN_HOOKS=1 bash scripts/deploy-harvester.sh --chart <new-chart> --nodeport 32735

7.3 回滚

bash
helm history harvester -n harvester-system
helm rollback harvester <REV> -n harvester-system --no-hooks

7.4 ★★ 每次 helm upgrade 之后必须重跑 post-install.sh

bash
bash scripts/post-install.sh && bash scripts/healthcheck.sh

原因:upgrade 会用 chart 内的 CR 覆盖集群中的 KubeVirt CR,导致后加的 Snapshot gate 静默失效(本环境实际发生过)。同理,现场用 annotate 改的默认 SC 也会被改回。

会被 upgrade 冲掉的现场修改持久做法
kubectl patch kubevirt 加的 gate写进 values 的 kubevirt.spec.configuration.…
kubectl annotate sc 改的默认类values 的 longhorn.persistence.defaultClass=false
手工建的 VolumeSnapshotClass保留清单文件,upgrade 后 apply 一次(post-install.sh 已含)
手工重启过的 virt-apiupgrade 后再重启一次(gate 仅启动时加载)

💡 判断某个修改会不会被冲掉:看资源上有没有 meta.helm.sh/release-name / app.kubernetes.io/managed-by: Helm 归属注解。 有 → 归 Helm 管,必须改 values;没有 → 是你自己的资源,upgrade 不会动它。


8. 本形态特有的坑(按「会不会丢数据」排序)

#症状根因处置
1★★ type: snap 恢复不可靠恢复「成功」但 VM 卡 Scheduling / GuestNotRunningKubeVirt 恢复完成后删除原 DV/PVCsnap 恢复卷的 dataSource=snap://<源卷>/<快照> 需从源卷克隆,源卷被 GC 后 cloneStatus 永不收敛(initiated + attemptCount 递增)生产用 type: bak + backup target;snap 只验证到 readyToUse=true 为止
2热迁移被拒DisksNotLiveMigratable"PVC xxx is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)"磁盘 PVC 是 ReadWriteOnce迁移用途的 VM 磁盘用 RWX + Filesystem;本环境 RWX 数据面另有阻塞(见 3)
3RWX 数据面不可用blockdev: cannot open /dev/cdi-block-volume: Permission denied(Block);改 Filesystem 后 qemu-img execution failed: exit status 1、importer 重启 3~5 次RWX 卷虽 state=attached/robustness=healthy,但 shareEndpointshareState 均为空、集群内无任何 share-manager Pod → NFS 导出端点从未建立,数据面不完整短期用 RWO + Filesystem + 冷迁移(实测通过);中期按「节点 NFS 客户端能力 → share-manager 镜像可拉取 → Longhorn 是否尝试创建 share-manager → 普通 Pod 挂载 RWX 复测」四步排查;热迁移路线见 08 高可用与迁移
4harvester Pod 重启 10 次三副本 RESTARTS=10exitCode=1 137/OOM)启动期顺序依赖:VSC longhorn 不存在 → 自己的 webhook 拒绝 → level=fatal部署后立即 post-install.sh 补建 VSC;判读重启用 lastState.terminated.* + logs --previous
5gate 静默失效快照报 snapshot feature gate not enabled①写成复数 Snapshots ②values 路径漏 spec ③virt-api 未重启 ④upgrade 覆盖§5.3 三层排除 + §5.5 服务端 dry-run 证明
6双默认 StorageClass未指定 SC 的 PVC 绑到 longhorn 而非 harvester-longhornLonghorn 子 chart 默认 persistence.defaultClass=truevalues 置 false;现场 annotate 校正(不持久)
7镜像导入卡 99.62% 后失败DV 进度回退 99.62% → 2.5% 重头再来公网镜像源不稳定,CDI(nbdkit/curl)重试并从头计时内网镜像源;先用 blank/pvc 验证平台链路
8kubectl get vsc 查错对象看到的是 volumesnapshotcontents 而非 classes短名冲突用全名 volumesnapshotclasses.snapshot.storage.k8s.io

第 1、3 条是唯一会真正丢数据/丢可用性的两类,必须在上线前用 vm-e2e-test.sh + Longhorn 卷四元组核对实测验证,不能只看控制面状态。


9. 快速参考卡

bash
export KUBECONFIG=/etc/rancher/rke2/rke2.yaml

# ── 部署 ────────────────────────────────────────────────
bash scripts/deploy-harvester.sh --chart <chart> --nodeport 32735   # 六阶段一键
bash scripts/post-install.sh                                        # ★ 必做(4 项固化 + dry-run 验证)

# ── 验收 ────────────────────────────────────────────────
bash scripts/healthcheck.sh          # 巡检 ~20s,只读;期望 FAIL=0
bash scripts/vm-e2e-test.sh          # 端到端:建机→冷迁移→快照→恢复→清理(自建自清)

# ── 访问(★ 必须带 SNI)────────────────────────────────
NODE_IP=$(kubectl get node -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
PORT=$(kubectl -n harvester-system get svc harvester -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')
curl -k --resolve harvester.local:$PORT:$NODE_IP https://harvester.local:$PORT/dashboard/

# ── 关键状态 ────────────────────────────────────────────
helm get metadata harvester -n harvester-system                 # STATUS / REVISION
kubectl get kubevirt -n harvester-system kubevirt \
  -o jsonpath='{.status.phase} {.spec.configuration.developerConfiguration.featureGates}'
kubectl get sc                                                  # 默认类必须唯一 = harvester-longhorn
kubectl get volumesnapshotclasses.snapshot.storage.k8s.io longhorn \
  -o jsonpath='{.driver} {.parameters.type}'
kubectl -n harvester-system get pod                             # 期望全 Running、RESTARTS 不增长

# ── 升级 ────────────────────────────────────────────────
helm get values harvester -n harvester-system > values-backup.yaml   # 先留档
helm upgrade harvester <chart> -n harvester-system --reuse-values --no-hooks -f values.yaml
bash scripts/post-install.sh && bash scripts/healthcheck.sh          # ★ upgrade 后必重跑
helm rollback harvester <REV> -n harvester-system --no-hooks         # 回滚

三条速记

  1. CIDR 靠探测,不靠抄promote 三项写错 → 路由算错,症状离根因很远)。
  2. 配置改 values,不改 CRmeta.helm.sh/release-name 归属注解决定谁说了算)。
  3. 改了 gate 必须重启 virt-api,并用服务端 dry-run 证明(否则永远是「以为生效了」)。

10. 小结与下一步

本章要点一句话
形态Helm 把 Harvester 叠加进既有 RKE2,K8s/CNI/snapshot-controller 都复用
promote三项 CIDR 必须实测探测并显式声明,否则 kube-vip / network-controller / LB 算错路由
冲突预判csi-snapshotter 要关(复用 RKE2)、longhorn.defaultClass=false(避免双默认类)、rancherEmbedded=false
必做固化默认 SC / VSC longhorn / Snapshot gate / virt-api 滚动重启,最后用服务端 dry-run 证明
升级铁律每次 helm upgrade 之后重跑 post-install.sh,否则 gate 静默失效
数据面type: snap 恢复不可靠 → 生产用 bak + backup target;RWX 阻塞 → 暂用冷迁移

接下来

  • 建机/镜像/cloud-init/快照/清理的日常操作 → 05 虚拟机生命周期实战
  • 磁盘规格、snap vs bak、恢复后数据面核对 → 06 存储实战
  • 热迁移阻塞的完整修复路线、维护模式、HA → 08 高可用与迁移实战
  • 两种形态怎么选(含成本模型与决策树)→ 10 五方对比
  • 本形态 18 个实测案例原始记录 → worker/harvester-container/02-故障排查与修复记录.md