VMExport#
Overview#
VirtualMachineExport (VMExport) is a KubeVirt API resource that exports VM disk volumes for authenticated download. When created, the virt-export-controller spins up a per-export virt-exportserver pod that serves disk data over HTTPS, and a cluster-level virt-exportproxy reverse proxy routes external requests to it.
The status API exposes both access paths via VirtualMachineExportLinks:
internal— in-cluster ClusterIP/.svcURL (direct pod-to-pod)external— ingress-routed URL (via OpenShift Route or similar)
Each link carries a cert field (public CA, base64-encoded) and lists the available volumes, backups, and manifests .
The export server container listens on port 8443; the Kubernetes Service exposes it on port 443 . On OpenShift, the virt-operator manages a Route named virt-exportproxy with TLS re-encryption termination that fronts the proxy .
Route Reconciliation on OpenShift#
The virt-operator owns the virt-exportproxy OpenShift Route. Route lifecycle is managed in pkg/virt-operator/resource/apply/routes.go:
- Create path : If no cached route exists, it is created via the OpenShift Route API.
- Update path :
syncRoute()callsresourcemerge.EnsureObjectMeta()to reconcile labels/annotations from the desired state, then patches if anything differs. The desired state is generated byNewExportProxyRoute(), which sets only TLS re-encryption config and no custom annotations.
Annotation stripping problem: Because the patch is always generated from the operator's desired route spec, any annotation manually applied to the live Route (e.g., haproxy.router.openshift.io/timeout) is overwritten whenever cert rotation (~every 12 hours) or an operator restart triggers a reconcile . The fix is to encode operator-managed annotations in NewExportProxyRoute() so they survive every reconcile cycle.
Known Issues & Fixes#
HAProxy 504 on Large Downloads (Issue #18638)#
OpenShift's HAProxy router defaults to a 30-second backend timeout. Because NewExportProxyRoute() sets no haproxy.router.openshift.io/timeout annotation, any VMExport disk download exceeding 30 seconds fails with a 504 Gateway Timeout . This affects all consumers: virtctl vmexport, Velero, Trilio, and Citrix MCS-based VDI provisioning.
Proposed fix: Add a default haproxy.router.openshift.io/timeout: 10m annotation in NewExportProxyRoute(). A follow-up could expose this as a configurable field in the KubeVirt CR .
virt-exportproxy FD Leak (Fixed — PR #18400)#
The proxy previously constructed a new http.Transport and httputil.ReverseProxy on every request, causing file descriptors to accumulate indefinitely during large downloads. PR #18400 fixed this by:
- Reusing a single shared
http.Transportwith bounded idle connection pooling (MaxIdleConns: 100,IdleConnTimeout: 30s) - Adding explicit dial and response-header timeouts on the backend, plus server-side
ReadHeaderTimeout/IdleTimeout - Setting
FlushInterval: -1to avoid proxy-side buffering of large streams - Implementing
dialBackendTLS()for eager TLS handshake verification against the export CA - Reading
MinTLSVersionandCipherSuiteslive from the KubeVirt CR store viaApplyTLSConfigurationFromKubeVirtStore()
UDN Namespace Connectivity (Fixed — PR #18606)#
In OVN-Kubernetes clusters with Primary User-Defined Networks (UDN), ClusterIP DNAT rules are only programmed on the UDN logical router — making export services unreachable from virt-exportproxy (which runs on the default network). PR #18606 resolves this by:
- Headless service (
clusterIP: None) — DNS resolves directly to pod IPs on both networks, bypassing broken DNAT k8s.ovn.org/open-default-portspod annotation — signals OVN-K to keep port 8443 reachable on the default network interface- Centralized
ExportServerPort = 8443constant — replaces scattered hardcoded443references across virtctl, backup export, and proxy code
VirtualMachineBackup: Missing Internal Endpoints (VEP #25)#
Issue #17986 tracks a gap between VirtualMachineExport and VirtualMachineBackup: backup status only exposes the external Route hostname, not internal cluster endpoints. In-cluster backup clients (e.g., Velero data movers) are therefore forced through the HAProxy router, which causes three compounding problems:
- DNS failures —
*.appswildcard hostnames may not resolve from cluster nodes - HAProxy timeouts — the 30-second default terminates long-lived
/datatransfers - Unnecessary overhead — all reads traverse the external proxy instead of going direct
The proposal (VEP #25) is to add a Links field to VirtualMachineBackupStatus mirroring the internal/external structure already present in VirtualMachineExportLinks. This is consistent with how VirtualMachineExport exposes both access paths today .
Key Source References#
| What | Where |
|---|---|
VMExport API types (VirtualMachineExport, VirtualMachineExportLinks) | staging/src/kubevirt.io/api/export/v1beta1/types.go |
| Export service & pod creation, port definitions | pkg/storage/export/export/export.go |
OpenShift Route generation (NewExportProxyRoute) | pkg/virt-operator/resource/generate/components/routes.go |
Route reconciliation (syncRoute) | pkg/virt-operator/resource/apply/routes.go |
| virt-exportproxy FD leak & TLS fix | PR #18400 |
| UDN connectivity fix (headless service, port centralization) | PR #18606 |
| HAProxy timeout issue & annotation stripping | Issue #18638 |
| VirtualMachineBackup internal endpoints (VEP #25) | Issue #17986 |