RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
KubeVirt VMI Virt-Launcher Crash Monitor
02. Root Cause Vector

KubeVirt virt-launcher pod crashes leaving VMI stuck in non-ready state, halting VM workloads and cascading into node resource exhaustion.

03. Rapid Prescription

Sort lock acquisition order deterministically, or configure explicit session timeouts.

Zero-Trust Client IDE/ Performance

KubeVirt VMI Virt-Launcher Crash Monitor

KubeVirt virt-launcher pod crashes leaving VMI stuck in non-ready state, halting VM workloads and cascading into node resource exhaustion.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(90L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ============================================
# KubeVirt VMI Configuration for PCI-Secure Zone
# Managed by: legacy-virt-ops-team@my-registry.internal
# Last Updated: 2022-11-30 (probably outdated)
# ============================================

# TODO: This entire file is a temporary workaround for the network policy mismatch in prod.
# We should migrate to the new CNI but the security team hasn't approved yet.
# VULNERABILITY: The readiness probe timeout is set lower than the VM's typical boot time under load, causing the virt-launcher to be killed before the VMI reports ready.

apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
  name: legacy-payment-processor-vmi
  namespace: pci-secure-zone
  annotations:
    # Old annotation from the pre-migration era, keep for compatibility
    kubevirt.io/legacy-boot-sequence: "true"
    # TODO: Remove after Q1 next year (unlikely)
    # kubevirt.io/preferredPodNetwork: "bridge" # commented out but still referenced somewhere
spec:
  domain:
    resources:
      requests:
        memory: "4Gi"
      limits:
        memory: "4Gi"
    devices:
      disks:
      - disk:
          bus: virtio
        name: containerdisk
      - disk:
          bus: virtio
        name: cloudinitdisk
      # Redundant interface left from the old multus config
      # - name: default
      #   bridge: {}
      interfaces:
      - name: default
        bridge: {}
    # This image has a slow init system that sometimes takes >60s to boot
    # but the team insisted we keep it for audit logging compatibility.
    machine:
      type: "" # Intentionally left blank to inherit cluster default (q35), but the image expects pc-q35-5.1 explicitly
  volumes:
  - name: containerdisk
    containerDisk:
      image: my-registry.internal/legacy-api/centos7-slowboot:v2.1.4-legacy
      # Embedded dummy secret for registry pull (never rotate)
      imagePullSecret: pci-registry-secret
  - name: cloudinitdisk
    cloudInitNoCloud:
      userData: |
        #cloud-config
        users:
          - name: admin
            ssh-authorized-keys:
              - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfKp4L...dummykey...mN9U9Xxw== legacy-key-2020
        # Dummy DB URI for the app config (not actually used here but the template requires it)
        write_files:
          - path: /etc/payment-app/db.conf
            content: |
              DB_URI=postgresql://payments:AKIADUMMYEXAMPLEKEY@old-db.internal.pci:5432/transactions?sslmode=require
              STRIPE_API_KEY=sk_live_51HdummyEXAMPLEKEYdummy1234567890
  # The readiness probe is too aggressive for the slow-booting image under CPU contention
  readinessProbe:
    tcpSocket:
      port: 22
    initialDelaySeconds: 30
    periodSeconds: 5
    timeoutSeconds: 2  # Too low; SSH might not respond within 2s during boot spikes
    failureThreshold: 3  # After 3 failures, the pod is considered not ready and may be restarted
  # Old termination grace period left from debugging
  terminationGracePeriodSeconds: 180
  # Global policy from the namespace enforces a 60s readiness timeout, conflicting with our probe
  # but we override it here implicitly via the probe settings, causing a race.
  # VULNERABILITY HINT ENDS

---
# Legacy secret manifest (kept for reference, not applied)
# apiVersion: v1
# kind: Secret
# metadata:
#   name: pci-registry-secret
#   namespace: pci-secure-zone
# type: kubernetes.io/dockerconfigjson
# data:
#   .dockerconfigjson: ewogICJhdXRocyI6IHsKICAgICJteS1yZWdpc3RyeS5pbnRlcm5hbCI6IHsKICAgICAgInVzZXJuYW1lIjogImxlZ2FjeS11c2VyIiwKICAgICAgInBhc3N3b3JkIjogIlN1cGVyU2VjcmV0UGFzc3dvcmQxMjM0NTY3ODkhQCMkJV4mKigpIgogICAgfQogIH0KfQ==
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

Fixing KubeVirt 'VMI Not Ready' Virt-Launcher Pod Crashes: Root Cause & Recovery Guide

Threat/Impact Level: HIGH | Downtime Risk: HIGH | Time to Fix: 15–45 mins depending on root cause

TL;DR

  • What broke: The virt-launcher pod for your KubeVirt VirtualMachineInstance crashed or was OOMKilled, leaving the VMI in a perpetual Scheduling or Pending phase with VMI not ready surfaced by the virt-controller.
  • How to fix it: Identify the crash reason via kubectl logs and kubectl describe on the virt-launcher pod — fix is one of: bump resource limits, patch the VMI spec's CPU/memory, correct the PVC binding, or resolve a libvirt/QEMU socket permission issue.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor your failing VMI YAML — paste it in, get the corrected spec back without sending your config to a third-party server.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What Does the Error Mean?)

Raw signal you'll see across the stack:

# kubectl get vmi <vmi-name> -n <namespace>
NAME         AGE   PHASE     IP    NODENAME   READY
my-vm-0      4m    Scheduling                  False

# kubectl describe vmi my-vm-0 -n <namespace>
...
Warning  SyncFailed   3m   virt-controller  Error creating virt-launcher pod: ...
Warning  VMINotReady  2m   virt-handler     VMI not ready: virt-launcher pod is not running

# kubectl describe pod virt-launcher-my-vm-0-<hash> -n <namespace>
  Last State: Terminated
    Reason:   OOMKilled (or Error, CrashLoopBackOff)
    Exit Code: 137

Immediate consequence: The VM never boots. The VMI object exists but no QEMU process was ever spawned. If this is a StatefulSet-backed VM or a KubeVirt VirtualMachine with runStrategy: Always, the controller will keep respawning the pod, hammering the node with failed pod launches until you intervene.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is not a single-pod problem. Here's the cascade:

  1. CrashLoopBackOff storm: virt-controller respawns virt-launcher on each reconcile loop. Each failed pod leaves ephemeral storage debris and consumes API server write bandwidth.
  2. Node resource starvation: OOMKilled launchers that die mid-QEMU-init can leave orphaned /dev/kvm file descriptors and leaked tap interfaces (k6t-eth0) on the node. Confirmed via ip link show | grep k6t.
  3. Noisy neighbor impact: On a shared node, repeated pod churn from a single broken VMI elevates kubelet reconcile pressure for every other workload on that node.
  4. Data integrity risk: If the VMI was attached to a ReadWriteOnce PVC that didn't cleanly detach before the crash, the PVC can remain in a locked state, blocking any future VMI from mounting it — effectively bricking the volume until manual intervention.

Primary root causes ranked by frequency:

Cause Signal
OOMKill (memory limit too low) Exit Code 137, Reason: OOMKilled
Missing or unbound PVC FailedMount, PVC in Pending
libvirt socket permission / securityContext permission denied in virt-launcher logs
Node doesn't support KVM (nested virt disabled) /dev/kvm no such file
CPU model incompatibility QEMU unsupported machine type

How to Fix It (The Solution)

Step 1 — Pull the actual crash reason

# Get the terminated container logs (not the running one)
kubectl logs virt-launcher-my-vm-0-<hash> -n <namespace> --previous -c compute

# Also check virt-handler on the affected node
kubectl logs -n kubevirt -l kubevirt.io=virt-handler --field-selector spec.nodeName=<node>

Basic Fix — OOMKill: Raise Memory Limits on the VMI

 apiVersion: kubevirt.io/v1
 kind: VirtualMachineInstance
 metadata:
   name: my-vm-0
 spec:
   domain:
     resources:
       requests:
-        memory: 64M
+        memory: 2Gi
       limits:
-        memory: 128M
+        memory: 2Gi
     cpu:
       cores: 1

Rule: virt-launcher itself needs overhead on top of the guest RAM. KubeVirt adds an overhead buffer automatically, but if you hard-cap limits.memory below requests.memory + overhead, the pod is killed before QEMU initializes. Never set memory limits below 1Gi for any non-trivial guest.


Basic Fix — Unbound PVC

kubectl get pvc -n <namespace>
# If STATUS = Pending, your StorageClass is not provisioning
kubectl describe pvc <pvc-name> -n <namespace>
 volumes:
   - name: rootdisk
     persistentVolumeClaim:
-      claimName: my-vm-pvc-typo
+      claimName: my-vm-rootdisk-pvc

Enterprise Best Practice — Enforce Resource Floors with LimitRange + VMI Validation Webhook

Problem: Individual teams submit VMI specs with dangerously low memory. No guardrails exist at admission time.

Solution: Deploy a LimitRange in each VM namespace AND enforce a KubeVirt ValidatingWebhookConfiguration (or OPA/Gatekeeper policy) that rejects VMIs below safe thresholds.

# LimitRange for VM namespaces
 apiVersion: v1
 kind: LimitRange
 metadata:
   name: kubevirt-vm-limits
   namespace: vm-workloads
 spec:
   limits:
-  # No limits defined — any value accepted
+  - type: Container
+    min:
+      memory: 512Mi
+      cpu: "250m"
+    default:
+      memory: 2Gi
+      cpu: "1"
+    defaultRequest:
+      memory: 2Gi
+      cpu: "1"
# OPA Rego — Gatekeeper ConstraintTemplate excerpt
- # No policy — virt-launcher OOMKills silently
+ deny[msg] {
+   input.review.object.kind == "VirtualMachineInstance"
+   mem := input.review.object.spec.domain.resources.requests.memory
+   to_number(trim_suffix(mem, "Mi")) < 512
+   msg := "VMI memory request below 512Mi — virt-launcher will OOMKill"
+ }

Fix — KVM Not Available on Node

# On the suspect node
ls -la /dev/kvm
# If missing: nested virtualization is disabled in the hypervisor or BIOS

# For AWS (enable nested virt on metal instances only — .metal instance types)
# For GKE, use n2 or c2 node pools with --enable-nested-virtualization

# Verify KubeVirt node labels
kubectl get nodes -l kubevirt.io/schedulable=true
# If your VMI was scheduled to a non-KVM node due to missing nodeSelector:
 spec:
+  nodeSelector:
+    kubevirt.io/schedulable: "true"
   domain:
     ...

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

Gate broken VMI specs before they hit the cluster.

1. Checkov — Static VMI YAML Scanning

pip install checkov
checkov -f vmi-manifest.yaml --framework kubernetes
# Custom check: flag any VMI with memory request < 512Mi

2. Kyverno Policy — Enforce KVM Node Affinity

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-kvm-node-selector
spec:
  validationFailureAction: enforce
  rules:
    - name: check-kvm-nodeselector
      match:
        resources:
          kinds: [VirtualMachineInstance]
      validate:
        message: "VMI must target kubevirt.io/schedulable=true nodes"
        pattern:
          spec:
            nodeSelector:
              kubevirt.io/schedulable: "true"

3. Pre-commit Hook — Validate PVC References Exist

#!/bin/bash
# pre-commit: validate PVC names in VMI specs match existing PVCs
PVC_NAMES=$(kubectl get pvc -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}')
REF=$(yq e '.spec.volumes[].persistentVolumeClaim.claimName' vmi-manifest.yaml)
if [[ ! " $PVC_NAMES " =~ " $REF " ]]; then
  echo "ERROR: PVC '$REF' not found in namespace $NAMESPACE"
  exit 1
fi

4. Alerting — PagerDuty/AlertManager Rule

- alert: KubeVirtVMINotReady
  expr: kubevirt_vmi_phase_count{phase="Scheduling"} > 0
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "VMI stuck in Scheduling phase for >5min — likely virt-launcher crash"
    runbook: "https://your-wiki/kubevirt-virt-launcher-crash"

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.Why does the virt-launcher pod get OOMKilled even when my VM's guest memory is small?
The root cause is a fundamental misalignment between the VMI's memory limit and KubeVirt's internal overhead calculation. The virt-launcher pod's memory limit must account for both the guest RAM and the QEMU process overhead, which KubeVirt automatically adds (typically 100–350Mi). If you manually set a hard `limits.memory` in the VMI spec that is less than the sum of guest RAM and this overhead, the kernel will OOMKill the pod during boot. The correct practice is to set `limits.memory` equal to `requests.memory` and allow KubeVirt to manage the overhead buffer, never capping it below the total required size. This is a critical configuration nuance that, if misunderstood, leads to persistent boot failures.
Q2.How do I recover a PVC that got stuck in a locked state after a virt-launcher crash?
Manually recovering a locked PVC is a high-risk, multi-step procedure that exposes your cluster to significant blast radius. You must first ensure the VMI and its pod are fully deleted, then inspect the PVC for stale `volumeAttachment` references, and potentially force-detach the volume at the storage provider level (e.g., AWS EBS). Each manual command (`kubectl delete volumeattachment`, storage CLI operations) carries the risk of data corruption, accidental deletion of the wrong resource, or leaving the storage layer in an inconsistent state, creating security vulnerabilities through orphaned attachments. Instead of risking manual intervention, drop the broken VMI and PVC manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix that safely orchestrates the cleanup without human error.
Q3.How can I tell which node is causing repeated virt-launcher crashes across multiple VMIs?
Identifying a problematic node requires correlating failed virt-launcher pods to specific nodes via field selectors and then digging into virt-handler logs to check for issues like missing `/dev/kvm`, full ephemeral disks, or kernel incompatibilities. While this process works for a single incident, performing this manual triage across large mono-repos or at scale—especially in regulated environments—becomes a compliance nightmare. Tracking, documenting, and remediating these node-level issues manually is unsustainable and error-prone. For automated, scalable remediation, upgrade to the StackEngine CI/CD Automator (Team Edition). It provides on-prem, automated CI/CD pipeline integration to detect, correlate, and fix these systemic node failures across your entire infrastructure, ensuring continuous compliance and operational resilience.