--- # KubeVirt platform-health alerts (docs/10, R-OBS-04): "VM crash-loops / failed launches" as a # first-class platform signal, alongside the node/Longhorn/Flux/cert/VPN/backup/identity rules in # platform/monitoring/alerts.yaml. PrometheusRule is a CR of the monitoring.coreos.com CRD installed # by kube-prometheus-stack, so this is applied by the `platform-config` Flux Kustomization (dependsOn: # platform), NOT the controllers pass (F-MON1) — identical wiring to platform/monitoring/alerts.yaml. # # The `release: kube-prometheus-stack` label makes the operator's Prometheus select these rules. Metric # names follow KubeVirt's `kubevirt_*` series; where a series depends on the deployed exporter it is # flagged to confirm at bring-up (same convention as platform/monitoring/alerts.yaml). apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: platform-health-kubevirt namespace: monitoring labels: release: kube-prometheus-stack platform-health: "true" spec: groups: - name: platform-health.kubevirt rules: # Failed launches: a VMI that reached the Failed phase (scheduling/launch/qemu failure). Any # sustained Failed VMI is page-worthy — the IaaS path is broken for that workload. # # METRIC CORRECTED 2026-09-13 (was `kubevirt_vmi_phase_count{phase="Failed"}`). That series # DOES NOT EXIST on the deployed KubeVirt, so this rule was structurally INERT — unlike the # Longhorn/cert-manager/Flux gaps in the same audit, NOT for want of a scrape target: the # `kubevirt-prometheus-metrics` job is up and its siblings below (virt_api_ready_status, # virt_controller_ready_status, cdi_import_pods_high_restart) all resolve. KubeVirt simply # retired the phase-count gauge in favour of an info-style series carrying `phase` as a label. # VERIFIED live on prod 2026-09-13: # count(kubevirt_vmi_phase_count) => (empty) # count(kubevirt_vmi_info) => 1 # kubevirt_vmi_info{...,name="unifi",namespace="unifi",phase="running",...} => 1 # Note `phase="running"` — KubeVirt LOWERCASES the phase in this label, so the matcher is # "failed", not "Failed". A capital-F matcher here would silently select nothing and leave the # rule just as inert as the metric name did; that is the whole point of this class of defect. - alert: KubeVirtVMLaunchFailing expr: sum by (namespace) (kubevirt_vmi_info{phase="failed"}) > 0 for: 5m labels: severity: critical annotations: summary: "KubeVirt VM(s) failing to launch in {{ $labels.namespace }}" description: "One or more VirtualMachineInstances are in the Failed phase in namespace {{ $labels.namespace }} for 5m — a failed VM launch (image import, scheduling, or qemu error). Check `kubectl get vmi -n {{ $labels.namespace }}` and the virt-launcher pod events." runbook_url: "https://${BASE_DOMAIN}/runbooks/kubevirt-launch-failed" # Crash-loop: the VM's virt-launcher pod is restarting repeatedly (guest kernel panic, OOM, or a # crashing qemu). >3 restarts in 15m is a crash-loop, not a one-off reboot. - alert: KubeVirtVMCrashLooping expr: | increase(kube_pod_container_status_restarts_total{pod=~"virt-launcher-.*"}[15m]) > 3 for: 10m labels: severity: warning annotations: summary: "KubeVirt VM crash-looping: {{ $labels.namespace }}/{{ $labels.pod }}" description: "virt-launcher pod {{ $labels.namespace }}/{{ $labels.pod }} has restarted more than 3 times in 15m — the guest VM is crash-looping (panic / OOM / qemu crash)." runbook_url: "https://${BASE_DOMAIN}/runbooks/kubevirt-crashloop" # Control plane down: no virt-controller or virt-api replica reports ready, so no VM lifecycle # operations can proceed. The metrics are `kubevirt_virt_{controller,api}_ready_status` (per-pod # 1=ready); sum()==0 means every replica is unready. The absent() clause also fires if the whole # KubeVirt metrics target disappears — its scrape job label is `kubevirt-prometheus-metrics` (the # Service name), NOT `kubevirt`. - alert: KubeVirtControlPlaneDown expr: | (sum(kubevirt_virt_controller_ready_status) == 0) or (sum(kubevirt_virt_api_ready_status) == 0) or (absent(up{job="kubevirt-prometheus-metrics"} == 1)) for: 10m labels: severity: critical annotations: summary: "KubeVirt control plane degraded" description: "The KubeVirt control plane (virt-controller/virt-api) is not ready or its metrics are absent for 10m — VM lifecycle operations (create/start/stop) will fail (docs/06)." runbook_url: "https://${BASE_DOMAIN}/runbooks/kubevirt-control-plane" # CDI import stuck: a DataVolume import/clone still in flight after 30m blocks the VM it backs. # kubevirt_cdi_import_pods_high_restart counts importer pods stuck restarting; a positive value # sustained means an import is not progressing (metric name confirmed against CDI at bring-up). - alert: CDIDataVolumeImportStuck expr: sum(kubevirt_cdi_import_pods_high_restart) > 0 for: 30m labels: severity: warning annotations: summary: "CDI DataVolume import stuck: {{ $labels.namespace }}/{{ $labels.name }}" description: "A CDI DataVolume import/clone in {{ $labels.namespace }} has not completed after 30m — the VM disk it backs cannot start. Check the importer pod (often an egress/default-deny block on the image source, or a slow mirror)." runbook_url: "https://${BASE_DOMAIN}/runbooks/cdi-import-stuck"