A Kubernetes MutatingAdmissionWebhook that removes problematic default gateway configurations from MTV (Migration Toolkit for Virtualization) migration pods to enable vCenter connectivity while preserving transfer network access.
Forklift commit dd73d5a adds native support for forklift.konveyor.io/route: "none" to achieve the same result as this webhook:
metadata:
annotations:
forklift.konveyor.io/route: "none"MTV's transfer network feature allows separating VM migration traffic from normal cluster traffic or leveraging high-speed layer 2 networks for disk transfers. In some deployments, the transfer network is a non-routable layer 2 network with direct ESXi host connectivity but no gateway, while vCenter remains accessible only via the cluster's default routing/egress.
When a NetworkAttachmentDefinition includes a gateway (via forklift.konveyor.io/route annotation or an ipam.routes entry for 0.0.0.0/0/::/0), Forklift's setTransferNetwork() uses the modern k8s.v1.cni.cncf.io/networks annotation with a default-route field. Without a gateway, it falls back to the legacy v1.multus-cni.io/default-network annotation.
This creates a conflict for OVN-Kubernetes localnet topologies in non-routable transfer network scenarios:
- Legacy annotation path (no gateway): OVN localnet cannot attach the network
- Modern annotation path (with gateway): Network attaches successfully, but the default route breaks vCenter connectivity
Both CDI importer pods (app=containerized-data-importer) and virt-v2v pods (forklift.app=virt-v2v) authenticate to vCenter and exchange metadata via the cluster's default network before transferring disks. Setting the default route to an IP address on the transfer network prevents connectivity to vCenter (no route to host).
This webhook intercepts migration pod creation and removes the default-route field from the k8s.v1.cni.cncf.io/networks annotation while preserving the network attachment itself.
The webhook targets both migration pod types with separate MutatingWebhookConfiguration entries:
- virt-v2v webhook: Pods with
forklift.app=virt-v2vlabel - CDI importer webhook: Pods with
app=containerized-data-importerlabel
Both parse the k8s.v1.cni.cncf.io/networks annotation, remove default-route fields, and return the modified pod specification.
Example transformation:
Before:
[{"name":"mtv-transfer","namespace":"default","default-route":["192.168.0.1"]}]After:
[{"name":"mtv-transfer","namespace":"default"}]This allows:
- Pods retain the transfer network as a secondary interface
- Default routing remains on the cluster's default network (for vCenter access)
- Direct layer 2 connectivity to ESXi hosts on the transfer network
When to use this webhook:
Use when all of the following are true:
- OVN-Kubernetes with localnet topology transfer network
- vCenter not reachable via the transfer network (special case: high-speed non-routable network directly connected to ESXi hosts)
- MTV transfer network feature enabled
Skip if vCenter is reachable via the transfer network gateway, you're not using MTV transfer networks, or your CNI accepts the legacy annotation.
- OpenShift 4.x cluster with MTV installed
ocCLI configured and authenticated
The deployment uses a prebuilt image published to ghcr.io/grandeit/gateway-yeeter:latest via GitHub Actions on every push to main.
oc apply -k deploy/Verify deployment:
oc get pods -n openshift-mtv -l app=gateway-yeeter
oc logs -n openshift-mtv -l app=gateway-yeeter -fThe deployment includes:
- 2 replicas for redundancy
- Pod anti-affinity ensuring pods run on different nodes
- PodDisruptionBudget maintaining at least 1 pod during voluntary disruptions
- Automatic TLS certificate management via OpenShift's service-ca-operator
Check the webhook logs for "YEETING" messages when migration pods are created:
oc logs -n openshift-mtv -l app=gateway-yeeter -fVerify the webhook modified the pod's network annotation:
oc get pod <migration-pod> -n <namespace> -o jsonpath='{.metadata.annotations.k8s\.v1\.cni\.cncf\.io/networks}' | jqThe default-route field should be absent from the network configuration.
oc delete -k deploy/Forklift's guessTransferNetworkDefaultRoute() function discovers the gateway in priority order:
- Checks the
forklift.konveyor.io/routeannotation on the NetworkAttachmentDefinition - Parses the NAD's
spec.configJSON and searches theipam.routesarray for a default route (0.0.0.0/0or::/0), extracting the gateway IP
If a gateway is found, setTransferNetwork() creates a NetworkSelectionElement with GatewayRequest set to the discovered IP and marshals it to the k8s.v1.cni.cncf.io/networks annotation. If no gateway is found, it falls back to setting v1.multus-cni.io/default-network with the NAD's namespaced name.
The code includes an interesting comment: // FIXME: the codepath using the multus annotation should be phased out.
The transfer network workflow creates a fundamental conflict:
| Gateway Discovered | Annotation Used | OVN Localnet Behavior | vCenter Reachability |
|---|---|---|---|
| Yes | k8s.v1.cni.cncf.io/networks with default-route |
Network attaches | Broken - default route overrides cluster routing |
| No | v1.multus-cni.io/default-network |
Fails - annotation not supported | N/A - network never attaches |
The webhook solves this by keeping the modern annotation format but removing the default-route field, allowing OVN to attach the network without disrupting pod routing.
MTV chooses between CDI importer pods and virt-v2v pods based on Plan.ShouldUseV2vForTransfer(). For vSphere sources, virt-v2v is used only when all of these conditions are true:
- Not a warm migration (warm needs CDI snapshot delta handling)
- Destination is local (
destination.IsHost()- can't monitor progress on remote clusters) MigrateSharedDisks: true(virt-v2v transfers all disks; CDI needed for selective disk migration)SkipGuestConversion: false(raw copy mode requires CDI)Type != MigrationOnlyConversion(conversion-only expects CDI-populated disks)
For OVA sources, ShouldUseV2vForTransfer() short-circuits to true.