Modern datacenters and beyond

Scenario: Provision a New VM from Scratch

Why This Scenario Matters

Provisioning a new VM is the single most frequent infrastructure operation in any enterprise. At 5,000+ VMs, the current VMware environment processes provisioning requests daily -- from dev/test environments spun up by CI pipelines to production database servers requested through ITSM tickets. The provisioning workflow touches every layer of the stack: the API entry point, the scheduler, storage allocation, network plumbing, and hypervisor boot sequence. Understanding this workflow end-to-end on each candidate platform reveals where automation is mature, where manual steps still exist, where latency hides, and where failure modes lurk.

This scenario traces a single, concrete request: "Provision a RHEL 9 VM with 4 vCPUs, 16 GiB RAM, a 100 GiB boot disk cloned from a golden image template, attached to VLAN 200 (application tier), with cloud-init injecting hostname, SSH keys, and network configuration." The same request is executed on all three platforms so the evaluation team can compare the operational experience.


End-to-End Flow: VMware vSphere (Baseline)

This is what the team does today. It is the baseline against which every replacement is measured.

Step-by-Step

VMware vSphere: VM Provisioning Flow
======================================

  Request                  vCenter                 ESXi Host              vSAN / SAN
  (Terraform/             Server                                         Storage
   vRA/Manual)
     |                       |                        |                      |
     | 1. API call           |                        |                      |
     |   (SOAP/REST)         |                        |                      |
     +---------------------> |                        |                      |
     |                       |                        |                      |
     |  2. Validate:         |                        |                      |
     |     - RBAC perms      |                        |                      |
     |     - Resource pool   |                        |                      |
     |       quota check     |                        |                      |
     |     - Template exists |                        |                      |
     |     ~200 ms           |                        |                      |
     |                       |                        |                      |
     |  3. DRS placement:    |                        |                      |
     |     - Score each host |                        |                      |
     |     - CPU/RAM fit     |                        |                      |
     |     - Affinity rules  |                        |                      |
     |     - Datastore space |                        |                      |
     |     ~500 ms           |                        |                      |
     |                       |                        |                      |
     |                       | 4. Clone template      |                      |
     |                       +----------------------->|                      |
     |                       |    - Linked clone:     |  5. Storage alloc    |
     |                       |      metadata only     |--------------------->|
     |                       |      ~2 sec            |  - vSAN: CLOM policy |
     |                       |    - Full clone:       |    evaluation        |
     |                       |      copies all blocks |  - Allocate objects  |
     |                       |      ~30-120 sec       |  - Apply FTT policy  |
     |                       |      (100 GiB)         |  ~1-3 sec (linked)   |
     |                       |                        |  ~30-120 sec (full)  |
     |                       |                        |                      |
     |                       | 6. Configure VM:       |                      |
     |                       |    - vCPU, RAM         |                      |
     |                       |    - vNIC -> port group|                      |
     |                       |      (VLAN 200)        |                      |
     |                       |    - CD-ROM (cloud-init|                      |
     |                       |      ISO)              |                      |
     |                       |    ~500 ms             |                      |
     |                       |                        |                      |
     |                       | 7. Power on            |                      |
     |                       +----------------------->|                      |
     |                       |    - VMkernel creates  |                      |
     |                       |      vCPU world threads|                      |
     |                       |    - Memory allocated  |                      |
     |                       |    - vNIC connected    |                      |
     |                       |      to vDS port       |                      |
     |                       |    ~2-3 sec            |                      |
     |                       |                        |                      |
     |                       |                    8. VM boots:               |
     |                       |                       BIOS/UEFI -> GRUB ->   |
     |                       |                       kernel -> cloud-init    |
     |                       |                       ~30-60 sec              |
     |                       |                        |                      |
     | 9. VM ready           |                        |                      |
     |<----------------------+                        |                      |
     |   Total (linked): ~40-70 sec                   |                      |
     |   Total (full): ~90-180 sec                    |                      |
Step Component Action Duration
1 vCenter API Receive REST/SOAP request, parse spec ~100 ms
2 vCenter RBAC, quota, template validation ~200 ms
3 DRS Host placement scoring ~500 ms
4 ESXi Clone template VMDK 2 sec (linked) / 30-120 sec (full)
5 vSAN/SAN Allocate storage, apply policy 1-3 sec (linked) / included in clone time (full)
6 vCenter Configure vHW, attach network ~500 ms
7 ESXi Power on, create VM world ~2-3 sec
8 Guest OS BIOS -> kernel -> cloud-init ~30-60 sec
Total ~40-70 sec (linked) / ~90-180 sec (full)

Storage Detail

Networking Detail


End-to-End Flow: OpenShift Virtualization Engine (OVE)

Request Entry Point

On OVE, a VM provisioning request can arrive through three paths:

  1. GitOps (ArgoCD): A VirtualMachine YAML manifest is committed to a Git repository. ArgoCD detects the change and applies the manifest to the cluster. This is the recommended production workflow for auditability and drift detection.
  2. Terraform/OpenTofu: The kubevirt Terraform provider creates the VirtualMachine CR via the Kubernetes API. Used for integration with existing IaC pipelines.
  3. OpenShift Console: The web UI provides a guided form for VM creation with template selection. Used for ad-hoc requests and troubleshooting.

All three paths converge on the same point: a VirtualMachine custom resource is created in the Kubernetes API server.

Step-by-Step

OVE: VM Provisioning Flow
===========================

  Request              kube-apiserver          virt-controller        kube-scheduler
  (GitOps/             + etcd                  (KubeVirt)
   Terraform/
   Console)
     |                       |                        |                      |
     | 1. Create VM CR       |                        |                      |
     |   kubectl apply -f    |                        |                      |
     +---------------------> |                        |                      |
     |                       |                        |                      |
     |  2. Admission:        |                        |                      |
     |     - virt-api webhook|                        |                      |
     |       validates spec  |                        |                      |
     |     - RBAC check      |                        |                      |
     |     - ResourceQuota   |                        |                      |
     |     - LimitRange      |                        |                      |
     |     ~300 ms           |                        |                      |
     |                       |                        |                      |
     |                       | 3. virt-controller     |                      |
     |                       |    watches VM CR       |                      |
     |                       +----------------------->|                      |
     |                       |    Creates:            |                      |
     |                       |    - DataVolume (CDI)  |                      |
     |                       |    - VMI CR            |                      |
     |                       |    - virt-launcher Pod |                      |
     |                       |    ~500 ms             |                      |
     |                       |                        |                      |
     |                       |                        | 4. kube-scheduler    |
     |                       |                        +--------------------->|
     |                       |                        |  Scores nodes:       |
     |                       |                        |  - CPU/RAM requests  |
     |                       |                        |  - nodeSelector      |
     |                       |                        |  - affinity rules    |
     |                       |                        |  - topology spread   |
     |                       |                        |  - device plugins    |
     |                       |                        |    (kvm, vhost-net)  |
     |                       |                        |  ~200 ms             |
     |                       |                        |                      |


  CDI Controller          CSI Driver            Ceph/ODF               OVN-Kubernetes
  (storage import)        (rbd.csi.ceph.com)    Cluster                CNI
     |                       |                        |                      |
     | 5. DataVolume         |                        |                      |
     |    processing:        |                        |                      |
     |    - Creates PVC      |                        |                      |
     |    - Spawns importer  | 6. CSI CreateVolume    |                      |
     |      pod or cloner    +----------------------->|                      |
     |      pod              |    - rbd create        | 7. Ceph RBD:         |
     |    ~1 sec             |    ~500 ms             |    - PG allocation   |
     |                       |                        |    - Replicate       |
     |  If clone from        |                        |    (3x or EC)        |
     |  golden image PVC:    |                        |    ~1-2 sec          |
     |  - CSI clone (fast)   |                        |                      |
     |  - Ceph rbd clone     |                        |                      |
     |    (COW, instant)     |                        |                      |
     |  ~2-3 sec total       |                        |                      |
     |                       |                        |                      |
     |  If import from URL:  |                        |                      |
     |  - CDI importer pod   |                        |                      |
     |    downloads QCOW2    |                        |                      |
     |  - Converts to raw    |                        |                      |
     |  - Writes to PVC      |                        |                      |
     |  ~60-300 sec          |                        |                      |
     |  (depends on image    |                        |                      |
     |   size and network)   |                        |                      |
     |                       |                        |                      |


  kubelet (target node)   virt-handler           virt-launcher Pod      OVN / Multus
                          (DaemonSet)            (per-VM)
     |                       |                        |                      |
     | 8. kubelet starts     |                        |                      |
     |    virt-launcher pod  |                        |                      |
     |    - CRI-O pulls      |                        |                      |
     |      container image  |                        |                      |
     |      (if not cached)  |                        |                      |
     |    - Creates pod      |                        |                      |
     |      sandbox          |                        |                      |
     |    ~2-5 sec           |                        |                      |
     |                       |                        |                      |
     |                       |                        |  9. CNI execution:   |
     |                       |                        +--------------------->|
     |                       |                        |  - OVN-K8s: creates  |
     |                       |                        |    logical port on   |
     |                       |                        |    node's logical    |
     |                       |                        |    switch            |
     |                       |                        |  - Assigns Pod IP    |
     |                       |                        |    from Pod CIDR     |
     |                       |                        |  - Programs OVS      |
     |                       |                        |    flows             |
     |                       |                        |  ~500 ms             |
     |                       |                        |                      |
     |                       |                        |  10. Multus (VLAN):  |
     |                       |                        |  - Reads NAD for     |
     |                       |                        |    VLAN 200          |
     |                       |                        |  - cnv-bridge plugin |
     |                       |                        |    creates bridge +  |
     |                       |                        |    VLAN subinterface |
     |                       |                        |  - Attaches to pod   |
     |                       |                        |    network namespace |
     |                       |                        |  ~300 ms             |
     |                       |                        |                      |
     |                       | 11. virt-handler       |                      |
     |                       |     detects new VMI    |                      |
     |                       +----------------------->|                      |
     |                       |     Configures:        |                      |
     |                       |     - libvirt domain   |                      |
     |                       |       XML              |                      |
     |                       |     - Disk attachments |                      |
     |                       |       (RBD block       |                      |
     |                       |        device)         |                      |
     |                       |     - NIC passthrough  |                      |
     |                       |       to QEMU          |                      |
     |                       |     ~1 sec             |                      |
     |                       |                        |                      |
     |                       |                  12. virt-launcher:           |
     |                       |                      - Starts libvirtd       |
     |                       |                      - libvirtd starts QEMU  |
     |                       |                      - QEMU boots VM:        |
     |                       |                        UEFI -> GRUB ->       |
     |                       |                        kernel -> cloud-init  |
     |                       |                      ~30-60 sec              |
     |                       |                        |                      |
     | 13. VM ready          |                        |                      |
     |   VMI status:         |                        |                      |
     |   phase: Running      |                        |                      |
     |   Total: ~40-75 sec (clone) / ~120-360 sec (import)                   |

Timing Summary (OVE)

Step Component Action Duration
1 kube-apiserver Receive and persist VM CR ~100 ms
2 virt-api webhook Admission validation, RBAC, quota ~300 ms
3 virt-controller Create DataVolume, VMI, virt-launcher Pod ~500 ms
4 kube-scheduler Score nodes, bind Pod ~200 ms
5 CDI controller Create PVC, spawn importer/cloner ~1 sec
6-7 CSI + Ceph Create RBD volume, replicate ~1-2 sec (clone) / ~60-300 sec (import)
8 kubelet + CRI-O Pull image, create pod sandbox ~2-5 sec
9 OVN-Kubernetes Create logical port, assign Pod IP, program OVS ~500 ms
10 Multus Attach VLAN 200 secondary interface ~300 ms
11 virt-handler Generate libvirt domain XML, configure disks/NICs ~1 sec
12 virt-launcher Start libvirtd -> QEMU -> guest boot -> cloud-init ~30-60 sec
Total Clone from golden image PVC ~40-75 sec
Total Import from HTTP URL ~120-360 sec

Storage Detail (OVE)

The golden-image workflow is the key to fast provisioning at scale:

Golden Image Workflow (OVE)
============================

One-time setup:
  1. CDI imports base QCOW2 into a PVC:
     DataVolume "rhel9-golden" -> CDI importer pod downloads from HTTP
     -> converts QCOW2 to raw -> writes to Ceph RBD volume
     -> PVC "rhel9-golden" is ready (takes 5-10 min for a 10 GiB image)

  2. Create a VolumeSnapshot of the golden PVC:
     VolumeSnapshot "rhel9-golden-snap" -> CSI calls rbd snap create
     -> instant metadata operation

Per-VM provisioning:
  3. DataVolume with dataSource referencing the snapshot:
     apiVersion: cdi.kubevirt.io/v1beta1
     kind: DataVolume
     spec:
       source:
         snapshot:
           name: rhel9-golden-snap
           namespace: golden-images
       pvc:
         accessModes: [ReadWriteMany]
         resources:
           requests:
             storage: 100Gi
         storageClassName: ocs-storagecluster-ceph-rbd

  4. CSI driver calls rbd clone:
     rbd clone pool/rhel9-golden@snap pool/vm-new-boot-disk
     -> COW clone: only metadata, zero data copied
     -> VM gets a thin-provisioned 100 GiB volume that reads from parent
     -> Only new writes consume physical space
     -> Takes ~2-3 seconds total (PVC creation + CSI clone + Ceph PG mapping)

Storage layout after cloning 10 VMs:
  +----------------------------------------------------------+
  | Ceph RBD Pool                                            |
  |                                                          |
  |  rhel9-golden (parent image, 10 GiB actual data)         |
  |    +-- @snap (snapshot, metadata only)                   |
  |         |                                                |
  |         +-- vm-001-boot (clone, COW, ~0 GiB initially)   |
  |         +-- vm-002-boot (clone, COW, ~0 GiB initially)   |
  |         +-- vm-003-boot (clone, COW, ~0 GiB initially)   |
  |         ...                                              |
  |         +-- vm-010-boot (clone, COW, ~0 GiB initially)   |
  |                                                          |
  |  Physical space used: ~10 GiB (shared parent)            |
  |  Logical space provisioned: 1,000 GiB (10 x 100 GiB)    |
  +----------------------------------------------------------+

This is fundamentally more space-efficient than a VMware full clone and comparable to VMware linked clones, with one important advantage: Ceph RBD clones are independent images that can be snapshotted, resized, and managed independently, while VMware linked clones retain a dependency chain to the parent that complicates lifecycle management.

Networking Detail (OVE)

The VM gets two network interfaces:

VM Networking on OVE
=====================

  NetworkAttachmentDefinition for VLAN 200:
  apiVersion: k8s.cni.cncf.io/v1
  kind: NetworkAttachmentDefinition
  metadata:
    name: app-vlan200
    namespace: production
  spec:
    config: |
      {
        "cniVersion": "0.3.1",
        "name": "app-vlan200",
        "type": "cnv-bridge",
        "bridge": "br-vlan200",
        "vlan": 200,
        "ipam": {}
      }

  VirtualMachine spec (network section):
  spec:
    template:
      spec:
        networks:
          - name: default
            pod: {}                        # Primary: OVN-K8s Pod network
          - name: app-net
            multus:
              networkName: app-vlan200     # Secondary: VLAN 200 via Multus

  Result on the node:
  +-------------------------------------------------------------------+
  | Worker Node                                                       |
  |                                                                   |
  |  Physical NIC (bond0) -- trunk: VLAN 100, 200, 300, 400          |
  |       |                                                           |
  |       +-- br-ex (OVS bridge for overlay)                          |
  |       |     |                                                     |
  |       |     +-- GENEVE tunnel to other nodes                      |
  |       |     +-- ovn-k8s-mp0 (management port)                     |
  |       |                                                           |
  |       +-- br-vlan200 (Linux bridge for VLAN 200)                  |
  |             |                                                     |
  |             +-- bond0.200 (VLAN subinterface)                     |
  |             +-- veth-vm-001 (connected to virt-launcher pod)      |
  |                                                                   |
  |  virt-launcher Pod (vm-001)                                       |
  |  +-------------------------------------------------------------+  |
  |  |  eth0 (OVN Pod network) -- IP: 10.128.2.15/23               |  |
  |  |  net1 (Multus VLAN 200) -- passed to QEMU as virtio NIC     |  |
  |  |                                                              |  |
  |  |  QEMU Process:                                               |  |
  |  |    VM sees:                                                  |  |
  |  |      eth0: 10.128.2.15 (pod network, used for K8s services)  |  |
  |  |      eth1: 10.200.1.50 (VLAN 200, cloud-init static config)  |  |
  |  +-------------------------------------------------------------+  |
  +-------------------------------------------------------------------+

Step-by-step network plumbing during provisioning:

  1. Pod sandbox created: CRI-O creates a new network namespace for the virt-launcher pod.
  2. OVN-Kubernetes CNI ADD: Creates a veth pair. One end goes into the pod namespace as eth0, the other connects to the OVS integration bridge (br-int). OVN allocates a logical switch port and assigns a Pod IP from the node's subnet. OVS flow rules are programmed to forward traffic for this port through the GENEVE overlay.
  3. Multus CNI ADD: Reads the NetworkAttachmentDefinition for app-vlan200. Invokes the cnv-bridge plugin, which creates a veth pair connecting the pod namespace to the br-vlan200 Linux bridge. The bridge is pre-configured with bond0.200 as an uplink, providing direct Layer 2 access to VLAN 200 on the physical network.
  4. virt-handler passes NICs to QEMU: Both interfaces (eth0 from OVN, net1 from Multus) are presented to the VM as virtio-net devices. The VM sees two NICs: one for the cluster network, one for the application VLAN.
  5. cloud-init configures guest networking: The seed data (injected via a ConfigMap-backed disk or the NoCloud datasource) configures eth1 inside the guest with the static IP 10.200.1.50/24, gateway, and DNS for VLAN 200.

End-to-End Flow: Azure Local

Request Entry Point

On Azure Local, VM provisioning integrates with the Azure control plane:

  1. Azure Portal / Azure CLI: The operator creates an Azure Arc VM resource. The request goes through Azure Resource Manager (ARM).
  2. Terraform (azurerm/azapi provider): ARM API call via Terraform to create an Arc VM.
  3. Windows Admin Center: On-premises GUI for cluster management.

All paths converge on the Azure Arc Resource Bridge, which translates ARM requests into local Hyper-V operations.

Step-by-Step

Azure Local: VM Provisioning Flow
====================================

  Request              Azure Resource          Arc Resource           Hyper-V Host
  (Portal/CLI/         Manager (ARM)           Bridge (on-prem       (Failover
   Terraform)          (Azure cloud)           appliance VM)          Cluster node)
     |                       |                        |                      |
     | 1. ARM API call       |                        |                      |
     |   PUT /providers/     |                        |                      |
     |   Microsoft.AzureStack|                        |                      |
     |   HCI/virtualMachines |                        |                      |
     +---------------------> |                        |                      |
     |                       |                        |                      |
     |  2. ARM validates:    |                        |                      |
     |     - Azure RBAC      |                        |                      |
     |     - Subscription    |                        |                      |
     |       quota           |                        |                      |
     |     - Template/image  |                        |                      |
     |       gallery ref     |                        |                      |
     |     ~500-1000 ms      |                        |                      |
     |     (cloud roundtrip) |                        |                      |
     |                       |                        |                      |
     |                       | 3. ARM dispatches      |                      |
     |                       |    to Arc Resource     |                      |
     |                       |    Bridge via           |                      |
     |                       |    Arc connected        |                      |
     |                       |    cluster agent        |                      |
     |                       +----------------------->|                      |
     |                       |    ~1-2 sec             |                      |
     |                       |    (depends on          |                      |
     |                       |     connectivity)       |                      |
     |                       |                        |                      |
     |                       |                        | 4. Arc Bridge        |
     |                       |                        |    translates ARM    |
     |                       |                        |    spec to local     |
     |                       |                        |    Hyper-V config:   |
     |                       |                        |    - Select cluster  |
     |                       |                        |      node (best fit) |
     |                       |                        |    - Create VHDX     |
     |                       |                        |      from gallery    |
     |                       |                        |      image           |
     |                       |                        +--------------------->|
     |                       |                        |    ~1 sec            |
     |                       |                        |                      |
     |                       |                        |                  5. Storage:
     |                       |                        |                     - S2D CSV
     |                       |                        |                       allocation
     |                       |                        |                     - VHDX copy
     |                       |                        |                       from gallery
     |                       |                        |                       image
     |                       |                        |                     - ReFS block
     |                       |                        |                       cloning
     |                       |                        |                       (if same CSV)
     |                       |                        |                     ~5-30 sec
     |                       |                        |                     (block clone)
     |                       |                        |                     ~60-180 sec
     |                       |                        |                     (full copy)
     |                       |                        |                      |
     |                       |                        |                  6. Networking:
     |                       |                        |                     - VMSwitch
     |                       |                        |                       port
     |                       |                        |                       created
     |                       |                        |                     - VLAN 200
     |                       |                        |                       tag set
     |                       |                        |                     - SDN Network
     |                       |                        |                       Controller
     |                       |                        |                       programs
     |                       |                        |                       VFP rules
     |                       |                        |                     ~1-2 sec
     |                       |                        |                      |
     |                       |                        |                  7. VM created:
     |                       |                        |                     - Hyper-V
     |                       |                        |                       creates VM
     |                       |                        |                     - Assigns
     |                       |                        |                       vCPUs, RAM
     |                       |                        |                     - Attaches
     |                       |                        |                       VHDX
     |                       |                        |                     - Powers on
     |                       |                        |                     ~2-3 sec
     |                       |                        |                      |
     |                       |                        |                  8. Guest boot:
     |                       |                        |                     UEFI -> BCD
     |                       |                        |                     -> kernel ->
     |                       |                        |                     cloud-init/
     |                       |                        |                     Sysprep
     |                       |                        |                     ~30-90 sec
     |                       |                        |                      |
     | 9. ARM resource       |                        |                      |
     |    provisioning       |                        |                      |
     |    state:             |                        |                      |
     |    "Succeeded"        |                        |                      |
     |<----------------------+                        |                      |
     |   Total: ~45-130 sec (block clone)                                    |
     |          ~120-300 sec (full copy)                                      |

Timing Summary (Azure Local)

Step Component Action Duration
1 ARM API Receive and validate request ~200 ms
2 ARM Azure RBAC, subscription quota, image validation ~500-1000 ms
3 Arc agent Relay to on-prem Arc Resource Bridge ~1-2 sec
4 Arc Resource Bridge Translate ARM spec, select target node ~1 sec
5 S2D / ReFS Allocate CSV, create VHDX (block clone or full copy) ~5-30 sec (clone) / ~60-180 sec (copy)
6 SDN Network Controller Create VMSwitch port, program VFP/VLAN rules ~1-2 sec
7 Hyper-V Create VM, assign resources, power on ~2-3 sec
8 Guest OS UEFI -> boot -> cloud-init/Sysprep ~30-90 sec
Total Block clone from gallery image ~45-130 sec
Total Full copy from gallery image ~120-300 sec

Storage Detail (Azure Local)

Azure Local Storage: VHDX on S2D CSV
======================================

  Gallery image workflow:
  1. Admin uploads VHDX to Azure Arc image gallery
     -> Stored on a Cluster Shared Volume (CSV) path:
        C:\ClusterStorage\Volume01\Gallery\rhel9-golden.vhdx

  2. Per-VM provisioning:
     Option A -- ReFS Block Cloning (same CSV):
       - ReFS copies metadata pointers, not data blocks
       - New VHDX references same physical extents
       - Writes trigger COW at the ReFS extent level (64 KiB)
       - Duration: ~5-30 seconds (metadata + differencing disk setup)

     Option B -- Full copy (different CSV or non-ReFS):
       - Block-by-block copy of VHDX
       - 100 GiB at ~500 MB/s = ~200 seconds
       - Duration: ~60-180 seconds

  S2D replication:
  +------------------------------------------------------+
  | Cluster Shared Volume (CSV) on S2D                   |
  |                                                      |
  | Volume01 (3-way mirror, ReFS)                        |
  |   |                                                  |
  |   +-- Gallery/rhel9-golden.vhdx (10 GiB actual)     |
  |   +-- VMs/vm-001/boot.vhdx (differencing from       |
  |   |                          golden, initially ~0)   |
  |   +-- VMs/vm-002/boot.vhdx                          |
  |   +-- VMs/vm-003/boot.vhdx                          |
  |                                                      |
  | S2D ensures 3 copies across 3 nodes (mirror)         |
  | Write path: VM -> ReFS -> CSV -> S2D Storage Bus     |
  |   -> write to local SSD/NVMe + replicate to 2 peers |
  |   -> acknowledge when majority (2/3) written         |
  +------------------------------------------------------+

Networking Detail (Azure Local)

Azure Local uses the Hyper-V Virtual Switch (VMSwitch) combined with Microsoft SDN Network Controller (optional) or simple VLAN tagging:

  1. VMSwitch: Each Hyper-V host has one or more virtual switches bound to physical NICs (typically SET -- Switch Embedded Teaming, which combines NIC teaming with the virtual switch).
  2. VLAN tagging: The VM's virtual NIC is configured with VLAN 200. The VMSwitch applies the VLAN tag on egress and strips it on ingress.
  3. SDN Network Controller (if deployed): The Network Controller programs Virtual Filtering Platform (VFP) rules on each VMSwitch to enforce ACLs, QoS, and overlay networking (VXLAN). This is the equivalent of NSX DFW.
  4. IP assignment: cloud-init (Linux) or Sysprep/unattend.xml (Windows) configures the guest NIC. Azure Local also supports Azure-style DHCP via the Arc VM networking extension.

Azure Dependency: Internet Connectivity

A critical architectural difference: Azure Local requires connectivity to Azure for the ARM control plane. Steps 1-3 involve a roundtrip to Azure cloud services. If the internet connection to Azure is down, new VM provisioning through the ARM path is blocked. Existing VMs continue to run (the data plane is fully on-premises), but management operations are degraded. Failover Cluster Manager and PowerShell can provision VMs locally without Azure, but this bypasses the Arc management layer and loses the unified Azure portal experience.

For a Tier-1 financial institution, this dependency on external cloud services for management operations must be evaluated against operational resilience requirements. FINMA expects that critical infrastructure can be operated independently during connectivity outages.


Three-Platform Comparison

Provisioning Timing Comparison

Provisioning Duration Comparison (Clone from Template)
========================================================

               VMware            OVE               Azure Local
               (linked clone)    (RBD clone)       (ReFS block clone)
  API/admit    |====| 0.7s       |=====| 0.8s      |=========| 1.5-3s
  Schedule     |===| 0.5s        |==| 0.2s          (included in Arc
                                                     Bridge step)
  Storage      |==| 2s           |===| 2-3s         |=======| 5-30s
  Network      |=| 0.1s         |=====| 0.8s       |==| 1-2s
  VM create    |===| 2-3s        |=====| 2-6s       |===| 2-3s
  Guest boot   |===========|     |===========|      |===========|
               30-60s            30-60s              30-90s
               ___________       ___________         ___________
  TOTAL        ~40-70s           ~40-75s             ~45-130s

Feature Comparison

Capability VMware vSphere OVE (KubeVirt) Azure Local
API model SOAP/REST (vCenter) Kubernetes API (kubectl) ARM REST API (Azure)
GitOps native No (requires wrapper) Yes (ArgoCD, Flux) Partial (ARM templates)
Clone mechanism Linked clone / full clone Ceph RBD clone (COW) ReFS block clone / VHDX differencing
Clone speed (100 GiB) ~2 sec (linked) ~2-3 sec (RBD clone) ~5-30 sec (ReFS clone)
Template management vCenter template library DataVolume + VolumeSnapshot Azure Arc image gallery
Scheduler DRS (proprietary) kube-scheduler (pluggable) Failover Cluster + Arc Bridge
Network model vDS + port group + VLAN OVN (overlay) + Multus (VLAN) VMSwitch + VLAN + optional SDN
Multi-NIC Multiple vNICs on port groups Multus NetworkAttachmentDefinition Multiple vNICs on VMSwitch
cloud-init support Via GOSC / OVF properties Native (NoCloud datasource) Via Sysprep (Windows) / cloud-init (Linux)
RBAC granularity vCenter roles + permissions Kubernetes RBAC (namespace-scoped) Azure RBAC (subscription/resource group)
Quota enforcement Resource pools (soft/hard) ResourceQuota + LimitRange Azure subscription quotas
Offline provisioning Full (vCenter on-prem) Full (cluster is self-contained) Degraded (ARM path requires Azure)

Storage Efficiency Comparison

Provisioning 100 VMs from a 10 GiB Golden Image (100 GiB logical per VM)
==========================================================================

  Logical provisioned:  100 VMs x 100 GiB = 10,000 GiB (all platforms)

  Physical consumed at provisioning time:

  VMware (linked clone):
    Parent VMDK:  10 GiB (shared)
    Delta disks:  ~0 GiB (initially)
    Overhead:     ~10 GiB per FTT=1 mirror (vSAN)
    Total:        ~20 GiB physical
    Ratio:        500:1 thin provisioning ratio

  OVE (Ceph RBD clone):
    Parent image: 10 GiB (shared, 3x replicated = 30 GiB raw)
    Clone COW:    ~0 GiB (initially)
    Total:        ~30 GiB raw physical (3x replication)
    Ratio:        ~333:1 thin provisioning ratio
    Note:         Replication factor higher than VMware FTT=1
                  but raw efficiency is comparable

  Azure Local (ReFS block clone):
    Gallery VHDX: 10 GiB (shared, 3-way mirror = 30 GiB raw)
    Diff disks:   ~0 GiB (initially)
    Total:        ~30 GiB raw physical (3-way mirror)
    Ratio:        ~333:1 thin provisioning ratio

Failure Modes During Provisioning

Failure VMware OVE Azure Local
Template not found vCenter returns error, no VM created CDI DataVolume enters ImportNotFound phase ARM returns 404 for gallery image
Insufficient storage CLOM rejects placement, error in vCenter task PVC stays Pending, CSI driver reports capacity error in events S2D reports insufficient CSV space
No suitable host DRS returns "no compatible host", VM not created Pod stays Pending, scheduler reports Insufficient cpu/memory in events Failover Cluster cannot find node with capacity
Network config error Port group missing: VM created but vNIC disconnected NAD missing: Multus CNI fails, pod enters CrashLoopBackOff or ContainerCreating with event error VMSwitch not configured for VLAN: NIC connected but no Layer 2 path
cloud-init failure VM boots but unconfigured. Requires console access to debug. Same -- VM boots but unconfigured. Debug via virtctl console or virtctl ssh. Same -- VM boots but unconfigured. Debug via Azure Serial Console or RDP.
Timeout / partial failure vCenter task shows partial progress. Can retry or clean up manually. Kubernetes resources left in intermediate state. kubectl delete vm cleans up (virt-controller handles cascading deletion of VMI, Pod, PVC if ownerReferences are set). ARM deployment shows failed. Arc Bridge may have partially created resources on-prem. Manual cleanup via PowerShell may be needed.

Automation at Scale: Provisioning 100 VMs

For the migration factory (5,000+ VMs in waves), provisioning speed and parallelism matter. Here is a realistic estimate for provisioning 100 VMs simultaneously:

Batch Provisioning: 100 VMs (clone from template, 4 vCPU / 16 GiB each)
==========================================================================

  VMware (vCenter):
    - vCenter processes API calls serially per datacenter (task queue)
    - Clone tasks run in parallel on different hosts (limited by
      concurrent provisioning operations setting, default 8 per host)
    - 10 hosts x 8 parallel clones = 80 concurrent operations
    - Linked clone: ~2 sec per clone + 30-60 sec boot = ~70 sec total
    - But: vCenter task queue can bottleneck at 100+ simultaneous tasks
    - Realistic total: ~2-3 minutes for all 100 VMs to reach running state

  OVE (KubeVirt):
    - kube-apiserver handles 100 VM CRs concurrently (etcd write throughput
      is the bottleneck, typically >1000 writes/sec)
    - CDI processes DataVolumes in parallel (configurable concurrency)
    - Ceph RBD clones are independent and parallelizable
    - kube-scheduler binds 100 pods in <5 seconds
    - Guest boot is fully parallel across nodes
    - Realistic total: ~1-2 minutes for all 100 VMs to reach Running phase
    - Bottleneck: CDI importer concurrency if importing (not cloning)

  Azure Local:
    - ARM API has throttling limits (per-subscription, per-resource-provider)
    - Arc Resource Bridge processes requests serially or with limited
      concurrency (implementation-dependent)
    - S2D block clones can run in parallel across different CSV owners
    - Realistic total: ~3-5 minutes for all 100 VMs to reach running state
    - Bottleneck: ARM API throttling and Arc Bridge concurrency
    - Workaround: Use PowerShell directly on-cluster to bypass ARM
      (loses Azure portal visibility)

Key Takeaways

  1. Clone speed is comparable across platforms. All three support COW/thin cloning from golden images. The guest OS boot time (30-90 seconds) dominates total provisioning time, making the infrastructure overhead (2-30 seconds) a secondary concern for individual VMs.

  2. OVE has the best automation story. Native Kubernetes API means GitOps (ArgoCD), Terraform, and CI/CD pipelines work without adapters. Every provisioning operation is a declarative YAML manifest versioned in Git. VMware requires vCenter-specific tooling; Azure Local requires ARM integration.

  3. Azure Local has a cloud dependency for management. The ARM roundtrip adds latency and introduces a dependency on internet connectivity. This is architecturally different from VMware and OVE, both of which operate fully on-premises.

  4. Storage efficiency is a function of replication factor, not cloning mechanism. All three platforms achieve near-zero initial space consumption for clones. The real space difference comes from the replication factor: vSAN FTT=1 (2x), Ceph replica=3 (3x), S2D 3-way mirror (3x). At 5,000 VMs, this difference is significant for raw capacity planning.

  5. Networking complexity is highest on OVE. The OVN overlay + Multus secondary network model is more components than VMware's vDS + port group or Azure Local's VMSwitch + VLAN. The VMware team will need to learn OVN logical switch/router concepts, Multus NAD configuration, and OVS flow debugging. The networking training budget should reflect this.

  6. Failure recovery is most declarative on OVE. Failed provisioning on OVE leaves Kubernetes resources in known states with clear events explaining the failure. Deleting and re-creating is straightforward. VMware task failures may require vCenter task investigation. Azure Local failures may leave split state between ARM and on-prem.


Discussion Guide

These questions should be raised with engineering teams and vendors during the PoC:

  1. For OVE: What is the CDI DataVolume concurrency limit in the current configuration? Can it handle 50+ simultaneous clone operations without backpressure? What is the Ceph cluster's IOPs ceiling for parallel RBD clone creation?

  2. For Azure Local: What is the ARM API throttling limit for VM creation? If we need to provision 100 VMs in a wave, will we hit the subscription-level rate limit? Can we use local PowerShell provisioning during the migration factory and reconcile with Arc afterwards?

  3. For all platforms: What is the end-to-end provisioning time for a Windows Server VM with Sysprep (not just Linux with cloud-init)? Windows Sysprep specialization typically adds 5-10 minutes to the boot sequence. Is this acceptable for the migration factory's cadence?

  4. For all platforms: How does the golden image lifecycle work? When the RHEL 9 base image is updated (security patches), how is the golden image refreshed, and how are existing clones affected? On OVE, updating the parent image does NOT retroactively update existing clones (they are independent after cloning). A new golden image requires new clones for new VMs.

  5. For the networking team: In the OVE model, VMs on VLAN 200 via Multus communicate directly on the physical VLAN (no overlay). VMs on the OVN Pod network communicate via GENEVE overlay. What is the impact on existing firewall rules, monitoring tools, and network segmentation policies that assume all VM traffic is on physical VLANs?