Running a Migration from vSphere to OpenShift Virtualization on Everpure FlashArray

Red Hat

Audience
Public
Technology Integrations
Linux
Source Type
Documentation

This section provides the complete step-by-step execution framework for running a Forklift-based migration from VMware vSphere to OpenShift Virtualization using Everpure XCopy Offload.

Prerequisites and Cluster Preparation

Tool Verification

# Verify oc and kubectl are installed and authenticated
oc login --server=https://<openshift-api>:6443
oc whoami
# Expected: system:admin or your cluster-admin user
# Verify Forklift operator is installed
oc get pods -n openshift-mtv | grep forklift
# Expected: forklift-controller, forklift-api, forklift-ui pods in Running state
# Verify Portworx StorageCluster is healthy
oc get storagecluster -n portworx
# Expected: Status = Online
# Verify StorageClass exists
oc get sc | grep px-pure
# Expected: px-pure-iscsi-sc (or nvmetcp / fc variant) listed
  1. Enable XCopy Offload.
    XCopy Offload must be explicitly enabled on the Forklift controller. This is a one-time configuration per cluster. Without this step, all migrations fall back to the standard VDDK path regardless of the StorageMap configuration.
    oc patch forkliftcontrollers.forklift.konveyor.io forklift-controller \
    --type merge \
    -p '{"spec": {"feature_copy_offload": "true"}}' \
    -n openshift-mtv
    # Verify the patch was applied
    oc get forkliftcontroller forklift-controller -n openshift-mtv \
    -o jsonpath='{.spec.feature_copy_offload}'
    # Expected: true
          
  2. Enable SCSI Persistent Reservation (if required).

    Required only for VMs that use SCSI Persistent Reservation — for example, Windows Server Failover Clustering. Skip this step for standard VM workloads.

    oc patch hco kubevirt-hyperconverged \
    -n openshift-cnv --type json \
    -p '[{"op": "replace", "path": "/spec/featureGates/persistentReservation", "value": true}]'
    # Verify
    oc get hco kubevirt-hyperconverged -n openshift-cnv -o yaml | grep persistentReservation
    # Expected: persistentReservation: true
  3. Set Environment Variables.

    Set these variables in the shell session before running any subsequent commands. They are referenced throughout this section.

    export FORKLIFT_NAMESPACE="forklift-migration"
    export PROVIDER_NAME="vcenter-source"
    export SOURCE_VCENTER_USER="administrator@vsphere.local"
    export SOURCE_VCENTER_PASSWORD="<vcenter-password>"
    export SOURCE_VCENTER_URL="https://<vcenter-ip-or-fqdn>"
  4. Configure Image Registry (VDDK / Ephemeral Registry).
    Note: This step is required only when running migrations without XCopy Offload (the standard VDDK path).
    1. Configure Ephemeral Image Registry (VDDK Path Only):
      # Enable the internal OpenShift image registry with ephemeral storage
      oc patch configs.imageregistry.operator.openshift.io/cluster \
      --patch '{"spec":{"managementState":"Managed","storage":{"emptyDir":{}},"defaultRoute":true}}' \
      --type=merge
      # Wait for the registry deployment to become available
      oc wait --for=condition=Available --timeout=5m deployment/image-registry -n openshift-image-registry
      # Retrieve the registry route hostname
      REGISTRY_ROUTE=$(oc get route default-route -n openshift-image-registry -o jsonpath='{.spec.host}')
      echo "Registry route: ${REGISTRY_ROUTE}"
    2. Build and Push VDDK Image (VDDK Path Only):
      export VDDK_IMAGE_NAME="vddk"
      export VDDK_IMAGE_TAG="8.0.3"
      FULL_IMAGE_PATH="${REGISTRY_ROUTE}/${FORKLIFT_NAMESPACE}/${VDDK_IMAGE_NAME}:${VDDK_IMAGE_TAG}"
      # Create a service account with image push permissions
      SERVICE_ACCOUNT="forklift-builder"
      oc create sa "$SERVICE_ACCOUNT" -n "$FORKLIFT_NAMESPACE" --dry-run=client -o yaml | oc apply -f -
      oc policy add-role-to-user system:image-builder -z "$SERVICE_ACCOUNT" -n "$FORKLIFT_NAMESPACE"
      TOKEN=$(oc create token "$SERVICE_ACCOUNT" -n "$FORKLIFT_NAMESPACE")
      # Log in to the internal registry
      podman login --tls-verify=false -u "$SERVICE_ACCOUNT" -p "$TOKEN" "$REGISTRY_ROUTE"
    3. Download VDDK tarball from developer.broadcom.com, then build and push:
      cat > Dockerfile <<EOF
      FROM registry.access.redhat.com/ubi8/ubi-minimal
      USER 1001
      COPY vmware-vix-disklib-distrib /vmware-vix-disklib-distrib
      RUN mkdir -p /opt
      ENTRYPOINT ["cp", "-r", "/vmware-vix-disklib-distrib", "/opt"]
      EOF
      tar -xzf ./*.tar.gz
      podman build . -t "$FULL_IMAGE_PATH"
      podman push --tls-verify=false "$FULL_IMAGE_PATH"
  5. Configure the Source Provider.

    The Provider CR defines the connection to the vCenter source environment. Forklift uses this connection to discover VM inventory, retrieve disk metadata, and obtain the FlashArray volume identifiers needed for XCopy Offload operations.

    1. Create the vCenter Credentials Secret:
      PROVIDER_SECRET_NAME="${PROVIDER_NAME}-creds"
      oc create secret generic "$PROVIDER_SECRET_NAME" \
      --from-literal=user="$SOURCE_VCENTER_USER" \
      --from-literal=password="$SOURCE_VCENTER_PASSWORD" \
      --from-literal=insecureSkipVerify=true \
      -n "$FORKLIFT_NAMESPACE" --dry-run=client -o yaml | oc apply -f -
      # Verify
      oc get secret "$PROVIDER_SECRET_NAME" -n "$FORKLIFT_NAMESPACE"
    2. Create the Provider CR: For XCopy Offload, the vddkInitImage field is omitted. Including it forces VDDK-based migration regardless of the offloadPlugin configuration in the StorageMap:
      cat <<EOF | oc apply -f -
      apiVersion: forklift.konveyor.io/v1beta1
      kind: Provider
      metadata:
      name: ${PROVIDER_NAME}
      namespace: ${FORKLIFT_NAMESPACE}
      spec:
      type: vsphere
      url: ${SOURCE_VCENTER_URL}
      secret:
      name: ${PROVIDER_NAME}-creds
      namespace: ${FORKLIFT_NAMESPACE}
      # vddkInitImage is intentionally omitted for XCopy Offload migrations
      EOF
      # Monitor provider readiness
      oc get provider ${PROVIDER_NAME} -n ${FORKLIFT_NAMESPACE} -w
      # Expected: STATUS = Ready
  6. Configure the Network Mapping between VMware and OpenShift.

    The NetworkMap CR maps VMware port groups to OpenShift network destinations. Every port group that migrated VMs are connected to must be mapped. Unmapped networks cause the migration plan to fail validation.

    Destination Type When to Use Configuration
    pod Standard VM traffic — maps to the OpenShift cluster SDN

    Name: pod

    Type: pod

    No additional config required

    multus VMs requiring dedicated network interfaces (storage, backup, replication)

    Name: <multus-network-attachment-name>

    Type: multus

    NetworkAttachmentDefinition must exist.

    1. Retrieve VMware Network IDs:
      # List available networks from the provider inventory
      oc get -n openshift-mtv networks.forklift.konveyor.io \
      --selector="migration.openshift.io/provider-uid=${PROVIDER_UID}" \
      -o custom-columns='ID:.spec.id,NAME:.spec.name'
    2. Create the NetworkMap:
      cat <<EOF | oc apply -f -
      apiVersion: forklift.konveyor.io/v1beta1
      kind: NetworkMap
      metadata:
      name: default-network-map
      namespace: ${FORKLIFT_NAMESPACE}
      spec:
      map:
      - destination:
      name: pod
      type: pod # Maps to OpenShift pod network (default)
      source:
      id: <vm-network-id>
      name: <vm-network-name>
      # Add additional entries for each unique port group used by migrated VMs
      # - destination:
      # name: multus-network
      # type: multus # Use multus for VMs requiring dedicated networks
      # source:
      # id: <second-network-id>
      # name: <second-network-name>
      provider:
      source:
      name: ${PROVIDER_NAME}
      namespace: ${FORKLIFT_NAMESPACE}
      destination:
      name: host
      namespace: openshift-mtv
      EOF
  7. Configure the Storage Mapping between the Datastore and the StorageClass.

    The StorageMap CR maps VMware datastores to Kubernetes StorageClasses. When XCopy Offload is enabled, the StorageMap also contains the FlashArray credential reference and storage vendor identifier that Forklift uses to issue XCOPY commands.

    1. Create FlashArray Credentials Secret for XCopy:
      # This secret authorises Forklift to issue XCOPY commands to FlashArray
      oc create secret generic pure-storage-creds \
      --from-literal=apiToken=<flasharray-api-token> \
      --from-literal=managementEndpoint=<flasharray-mgmt-ip-or-fqdn> \
      -n ${FORKLIFT_NAMESPACE} --dry-run=client -o yaml | oc apply -f -
    2. Retrieve VMware Datastore IDs:
      # List datastores from provider inventory
      oc get -n openshift-mtv datastores.forklift.konveyor.io \
      --selector="migration.openshift.io/provider-uid=${PROVIDER_UID}" \
      -o custom-columns='ID:.spec.id,NAME:.spec.name,TYPE:.spec.type'
    3. Create the StorageMap with XCopy Offload:
      cat <<EOF | oc apply -f -
      apiVersion: forklift.konveyor.io/v1beta1
      kind: StorageMap
      metadata:
      name: pure-storage-map
      namespace: ${FORKLIFT_NAMESPACE}
      spec:
      map:
      - destination:
      accessMode: ReadWriteOnce
      storageClass: px-pure-iscsi-sc # Target Portworx + FlashArray StorageClass
      offloadPlugin:
      vsphereXcopyConfig:
      secretRef: pure-storage-creds # FlashArray API credentials
      storageVendorProduct: pureFlashArray
      source:
      id: <source-datastore-id>
      name: <source-datastore-name>
      # Add additional entries for each source datastore
      # Multiple datastores can map to the same or different StorageClasses
      provider:
      source:
      name: ${PROVIDER_NAME}
      namespace: ${FORKLIFT_NAMESPACE}
      destination:
      name: host
      namespace: openshift-mtv
      EOF
  8. Define the Migration Plan:

    The Plan CR selects which VMs to migrate and binds together the Provider, NetworkMap, and StorageMap. A single Plan can contain one VM or hundreds. Plans are reusable — the same Plan can be executed multiple times to retry failed VMs.

    Standard VM Migration Plan

    cat <<EOF | oc apply -f -
    apiVersion: forklift.konveyor.io/v1beta1
    kind: Plan
    metadata:
    name: migration-plan-wave1
    namespace: ${FORKLIFT_NAMESPACE}
    spec:
    # Use generateName for PVC names to avoid conflicts across migration runs
    pvcNameTemplateUseGenerateName: true
    targetNamespace: ${FORKLIFT_NAMESPACE}
    warm: false
    provider:
    source:
    name: ${PROVIDER_NAME}
    namespace: ${FORKLIFT_NAMESPACE}
    destination:
    name: host
    namespace: openshift-mtv
    map:
    network:
    name: default-network-map
    namespace: ${FORKLIFT_NAMESPACE}
    storage:
    name: pure-storage-map
    namespace: ${FORKLIFT_NAMESPACE}
    vms:
    - id: vm-1001
    name: app-server-01
    - id: vm-1002
    name: app-server-02
    - id: vm-1003
    name: db-server-01
    EOF

    Shared Disk Migration Plan Variant

    # For VMs with shared RDM volumes — Stage 1 only
    # migrateSharedDisks: true migrates shared volumes alongside the first VM
    spec:

    migrateSharedDisks: true # Stage 1: first VM + shared disks

    vms:
    - id: vm-cluster-lead
    name: cluster-node-01
    # For Stage 2 — remaining VMs without re-migrating shared disks
    spec:
    migrateSharedDisks: false # Stage 2: remaining VMs only
    vms:
    - id: vm-cluster-02
    name: cluster-node-02
    # ... remaining cluster nodes

    Warm Migration Variant

    Warm migration pre-copies VM disk data while the VM is still running in VMware, then performs a final sync and cutover. This minimises downtime for VMs that cannot tolerate an extended outage. With XCopy Offload, the initial copy and the final sync are both performed as FlashArray internal operations — the warm migration benefit is a near-zero final sync window.

    # Enable warm migration in the Plan spec
    spec:
    warm: true # Enables pre-copy phase while source VM is running
  9. Start the Migration.
    cat <<EOF | oc apply -f -
    apiVersion: forklift.konveyor.io/v1beta1
    kind: Migration
    metadata:
    name: migration-wave1-run1
    namespace: ${FORKLIFT_NAMESPACE}
    spec:
    plan:
    name: migration-plan-wave1
    namespace: ${FORKLIFT_NAMESPACE}
    EOF
  10. Monitor the Migration Progress:
    # Check overall migration status
    oc get migration migration-wave1-run1 -n ${FORKLIFT_NAMESPACE}
    # Watch for Succeeded condition
    oc get migration migration-wave1-run1 -n ${FORKLIFT_NAMESPACE} \
    -o jsonpath='{.status.conditions[?(@.type=="Succeeded")].status}'
    # Detailed status including per-VM progress
    oc describe migration migration-wave1-run1 -n ${FORKLIFT_NAMESPACE}
    # Watch migrated VM pods appear in the target namespace
    oc get pods -n ${FORKLIFT_NAMESPACE} -w
    # Verify migrated VMs
    oc get
  11. Validate Post-Migration Results:
    # Confirm VM is Running
    oc get vm <vm-name> -n ${FORKLIFT_NAMESPACE}
    # Expected: STATUS = Running
    # Confirm PVC is Bound
    oc get pvc -n ${FORKLIFT_NAMESPACE}
    # Expected: STATUS = Bound for all migrated VM disks
    # Confirm VM is accessible (requires virtctl or console access)
    virtctl console <vm-name> -n ${FORKLIFT_NAMESPACE}
    # For failed VMs — create a new Migration referencing the same Plan
    # with only the failed VM IDs in the vms list

Handling Partial Failures:

Forklift migrations are non-destructive with respect to the source environment. Source VMs remain powered on and unmodified throughout the migration. If a subset of VMs in a batch fail:

  • The successfully migrated VMs continue running on OpenShift.
  • The failed VMs remain running on VMware, with no data loss or service disruption.
  • Create a new Migration CR referencing the same plan, with the vms list updated to include only the failed VM IDs.
  • Apply the new Migration CR to retry only the failed VMs.