VM Memory Overhead#
KubeVirt adds memory overhead on top of a VM's guest memory when sizing the virt-launcher pod. This overhead accounts for the QEMU/KVM process stack, KubeVirt daemons, page tables, video RAM, and other per-VM infrastructure. Understanding how it is calculated, applied, and exposed is essential for capacity planning, Kubernetes QoS tuning, and debugging OOMKill events.
Key source files:
pkg/hypervisor/kvm/hypervisorbackend.go—GetMemoryOverhead(), fixed process constantspkg/virt-controller/services/renderresources.go—WithMemoryOverhead(),WithMemoryRequests(),WithAutoMemoryLimits()pkg/virt-controller/services/template.go—CalculateMemoryOverhead(), pod annotation injectionpkg/virt-controller/watch/vmi/lifecycle.go—updateMemoryOverheadStatusFromPod(), VMI status update
Overhead Calculation#
The entry point is GetMemoryOverhead() in the KVM hypervisor backend. It computes a total overhead value by summing several components:
Fixed process overhead#
| Process | Constant |
|---|---|
virt-launcher-monitor | 25 Mi |
virt-launcher | 100 Mi |
virtlogd | 25 Mi |
virtqemud | 40 Mi |
qemu (base, beyond guest RAM) | 30 Mi |
These are observable RSS values rounded up for safety . The QemuOverhead constant is subject to periodic revision as QEMU grows with new virtio features and guest OS sizes increase — it was bumped from 30 Mi to 50 Mi in PR #18388 to resolve CI flakes .
Dynamic overhead components#
All computed inside GetMemoryOverhead():
- Page tables: 1 bit per 512 bytes of guest RAM
- vCPU tables: 8 Mi per vCPU + 8 Mi static for IOThreads
- Video RAM: 32 Mi if
AutoattachGraphicsDeviceis enabled (default) - ARM64 UEFI pflash: +128 Mi on
arm64 - VFIO devices: +1 Gi to cover DMA-locked guest RAM + MMIO space
- DownwardMetrics disk: +1 Mi for memory-backed emptyDir
- Exec probes: +100 Mi base + 10 Mi per probe (
virt-probebinary overhead) - SEV/SEV-SNP/SEV-ES: +256 Mi for encrypted memory regions
- TPM (swtpm): +53 Mi
- Dedicated CPU / Guaranteed QoS: +100 Mi
- Custom reserved overhead: additive field from
spec.domain.memory.reservedOverhead.addedOverhead - Additional ratio multiplier: an optional cluster-level ratio applied last to scale the entire overhead value
Applying overhead to the pod#
WithMemoryOverhead() in renderresources.go adds the computed overhead to the pod's memory request, and always adds it to the memory limit (even when overcommitGuestOverhead: true prevents it from being added to the request) . For hugepages VMIs, WithHugePages() routes the overhead into the ordinary memory request rather than hugepages pages.
Auto memory limits default to 2× the memory request if no explicit limit is set, controllable per-namespace via the label alpha.kubevirt.io/auto-memory-limits-ratio .
Overcommit Strategies and QoS#
KubeVirt offers three memory overcommit strategies to increase node VM density, each with a different QoS trade-off :
1. Overcommit guest overhead (overcommitGuestOverhead: true)#
Setting spec.domain.resources.overcommitGuestOverhead: true on a VMI excludes the calculated overhead from the pod request (it is still included in the limit). This reduces the per-VMI Kubernetes accounting cost. Best for short-lived or bursty VMs where temporary OOM risk is acceptable .
2. Explicit guest memory overcommit#
A VMI can be configured with a guest memory size larger than the Kubernetes memory request (e.g., spec.domain.memory.guest: 2Gi but spec.domain.resources.requests.memory: 1Gi). Kubernetes assigns the pod a Burstable QoS class, making it the first to be evicted under node memory pressure .
3. Implicit cluster-level overcommit (memoryOvercommit)#
The KubeVirt CR field spec.configuration.developerConfiguration.memoryOvercommit accepts a percentage (default 100). When set above 100, WithMemoryRequests() automatically scales down the pod memory request to (guest_memory × 100) / overcommit_percent, allowing more VMs per node without per-VMI changes .
Overcommit is only applied when all of: memory requests are not explicitly set on the VMI, guest memory or hugepages are present, and the overcommit config is not 100. Explicitly set memory requests are preserved to allow the user to achieve Guaranteed QoS .
QoS classes#
| Scenario | QoS Class | Eviction Priority |
|---|---|---|
| requests = limits | Guaranteed | Last |
| requests < limits | Burstable | First |
For Guaranteed QoS, an extra 100 Mi is added to GetMemoryOverhead() , and sidecar containers also have their requests set equal to limits .
Reducing overhead to save memory#
Disabling the graphical device (spec.domain.devices.autoattachGraphicsDevice: false) removes the 32 Mi video RAM overhead per VMI . For high-density nodes, enabling KSM via node labels can deduplicate shared memory pages across VMs.
Overhead Visibility: VMI Status and Pod Annotation#
Feature gate#
The VmiMemoryOverheadReport feature gate (Beta) controls overhead exposure. When enabled:
- Pod annotation:
CalculateMemoryOverhead()stores the computed value askubevirt.io/memory-overhead-byteson the virt-launcher pod . - VMI status:
updateMemoryOverheadStatusFromPod()reads the annotation and writes it toVMI.status.memory.memoryOverheadduring the lifecycle reconciliation loop .
Recording the overhead on the pod at creation time (rather than recalculating it) ensures the value remains consistent across KubeVirt upgrades and cluster config changes .
Live migration#
During live migration a target virt-launcher pod is created, possibly on a node with different hardware (e.g., different arch, different enabled features). The target pod's overhead is tracked separately at VMI.status.migrationState.targetMemoryOverhead for the duration of the migration, then promoted to VMI.status.memory.memoryOverhead once migration completes .
Usage#
- Metrics collectors and monitoring agents can read
status.memory.memoryOverheadwithout recalculating it, getting a stable value tied to the actual running pod. - Schedulers and capacity planners can sum
spec.domain.resources.requests.memory+status.memory.memoryOverheadto determine real node memory consumption per VMI. - The annotation
kubevirt.io/memory-overhead-byteson virt-launcher pods can be used for direct pod-level queries.