Skip to content

Export API

It can be desirable to export a Virtual Machine and its related disks out of a cluster so you can import that Virtual Machine into another system or cluster. The Virtual Machine disks are the most prominent things you will want to export. The export API makes it possible to declaratively export Virtual Machine disks. It is also possible to export individual PVCs and their contents, for instance when you have created a memory dump from a VM or are using virtio-fs to have a Virtual Machine populate a PVC.

In order not to overload the kubernetes API server the data is transferred through a dedicated export proxy server. The proxy server can then be exposed to the outside world through a service associated with an Ingress/Route or NodePort. As an alternative, the port-forward flag can be used with the virtctl integration to bypass the need of an Ingress/Route.

Export Feature Gate

VMExport support must be enabled in the feature gates to be available. The feature gates field in the KubeVirt CR must be expanded by adding the VMExport to it.

Export token

In order to securely export a Virtual Machine Disk, you must create a token that is used to authorize users accessing the export endpoint. This token must be in the same namespace as the Virtual Machine. The contents of the secret can be passed as a token header or parameter to the export URL. The name of the header or argument is x-kubevirt-export-token with a value that matches the content of the secret. The secret can be named any valid secret in the namespace. We recommend you generate an alpha numeric token of at least 12 characters. The data key should be token. For example:

apiVersion: v1
kind: Secret
metadata:
  name: example-token
stringData:
  token: 1234567890ab

Export Virtual Machine volumes

After you have created the token you can now create a VMExport CR that identifies the Virtual Machine you want to export. You can create a VMExport that looks like this:

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
spec:
  tokenSecretRef: example-token
  source:
    apiGroup: "kubevirt.io"
    kind: VirtualMachine
    name: example-vm

The following volumes present in the VM will be exported:

  • PersistentVolumeClaims
  • DataVolumes
  • MemoryDump

All other volume types are not exported. To avoid the export of inconsistent data, a Virtual Machine can only be exported while it is powered off. Any active VM exports will be terminated if the Virtual Machine is started. To export data from a running Virtual Machine you must first create a Virtual Machine Snapshot (see below).

If the VM contains multiple volumes that can be exported, each volume will get its own URL links. If the VM contains no volumes that can be exported, the VMExport will go into a Skipped phase, and no export server is started.

Export Virtual Machine Snapshot volumes

You can create a VMExport CR that identifies the Virtual Machine Snapshot you want to export. You can create a VMExport that looks like this:

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
spec:
  tokenSecretRef: example-token
  source:
    apiGroup: "snapshot.kubevirt.io"
    kind: VirtualMachineSnapshot
    name: example-vmsnapshot

When you create a VMExport based on a Virtual Machine Snapshot, the controller will attempt to create PVCs from the volume snapshots contained in Virtual Machine Snapshot. Once all the PVCs are ready, the export server will start and you can begin the export. If the Virtual Machine Snapshot contains multiple volumes that can be exported, each volume will get its own URL links. If the Virtual Machine snapshot contains no volumes that can be exported, the VMExport will go into a skipped phase, and no export server is started.

Export Persistent Volume Claim

You can create a VMExport CR that identifies the Persistent Volume Claim (PVC) you want to export. You can create a VMExport that looks like this:

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
spec:
  tokenSecretRef: example-token
  source:
    apiGroup: ""
    kind: PersistentVolumeClaim
    name: example-pvc

In this example the PVC name is example-pvc. Note the PVC doesn't need to contain a Virtual Machine Disk, it can contain any content, but the main use case is exporting Virtual Machine Disks. After you post this yaml to the cluster, a new export server is created in the same namespace as the PVC. If the source PVC is in use by another pod (such as the virt-launcher pod) then the export will remain pending until the PVC is no longer in use. If the exporter server is active and another pod starts using the PVC, the exporter server will be terminated until the PVC is not in use anymore.

The VirtualMachineExport CR will contain a status with internal and external links to the export service. The internal links are only valid inside the cluster, and the external links are valid for external access through an Ingress or Route. The cert field will contain the CA that signed the certificate of the export server for internal links, or the CA that signed the Route or Ingress.

KubeVirt content-type

The following is an example of exporting a PVC that contains a KubeVirt disk image. The controller determines if the PVC contains a kubevirt disk by checking if there is a special annotation on the PVC, or if there is a DataVolume ownerReference on the PVC, or if the PVC has a volumeMode of block.

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
  namespace: example
spec:
  source:
    apiGroup: ""
    kind: PersistentVolumeClaim
    name: example-pvc
  tokenSecretRef: example-token
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:10:09Z"
    reason: podReady
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:09:02Z"
    reason: pvcBound
    status: "True"
    type: PVCReady
  links:
    external:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: raw
          url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example-disk/disk.img
        - format: gzip
          url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example-disk/disk.img.gz
        name: example-disk
    internal:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: raw
          url: https://virt-export-example-export.example.svc/volumes/example-disk/disk.img
        - format: gzip
          url: https://virt-export-example-export.example.svc/volumes/example-disk/disk.img.gz
        name: example-disk
  phase: Ready
  serviceName: virt-export-example-export

Archive content-type

Archive content-type is automatically selected if we are unable to determine the PVC contains a KubeVirt disk. The archive will contain all the files that are in the PVC.

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
  namespace: example
spec:
  source:
    apiGroup: ""
    kind: PersistentVolumeClaim
    name: example-pvc
  tokenSecretRef: example-token
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:10:09Z"
    reason: podReady
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:09:02Z"
    reason: pvcBound
    status: "True"
    type: PVCReady
  links:
    external:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: dir
          url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example/dir
        - format: tar.gz
          url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example/disk.tar.gz
        name: example-disk
    internal:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: dir
          url: https://virt-export-example-export.example.svc/volumes/example/dir
        - format: tar.gz
          url: https://virt-export-example-export.example.svc/volumes/example/disk.tar.gz
        name: example-disk
  phase: Ready
  serviceName: virt-export-example-export

Manifests

The VirtualMachine manifests can be retrieved by accessing the manifests in the VirtualMachineExport status. The all type will return the VirtualMachine manifest, any DataVolumes, and a configMap that contains the public CA certificate of the Ingress/Route of the external URL, or the CA of the export server of the internal URL. The auth-header-secret will be a secret that contains a Containerized Data Importer (CDI) compatible header. This header contains a text version of the export token.

Both internal and external links will contain a manifests field. If there are no external links, then there will not be any external manifests either. The virtualMachine manifests field is only available if the source is a VirtualMachine or VirtualMachineSnapshot. Exporting a PersistentVolumeClaim will not generate a Virtual Machine manifest.

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
  namespace: example
spec:
  source:
    apiGroup: ""
    kind: PersistentVolumeClaim
    name: example-pvc
  tokenSecretRef: example-token
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:10:09Z"
    reason: podReady
    status: "True"
    type: Ready
  - lastProbeTime: null
    lastTransitionTime: "2022-06-21T14:09:02Z"
    reason: pvcBound
    status: "True"
    type: PVCReady
  links:
    external:
      ...
      manifests:
      - type: all
        url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/external/manifests/all
      - type: auth-header-secret
        url: https://vmexport-proxy.test.net/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/external/manifests/secret
    internal:
      ...
      manifests:
      - type: all
        url: https://virt-export-export-pvc.default.svc/internal/manifests/all
      - type: auth-header-secret
        url: https://virt-export-export-pvc.default.svc/internal/manifests/secret
  phase: Ready
  serviceName: virt-export-example-export

Format types

There are 4 format types that are possible:

  • Raw. The unaltered raw KubeVirt disk image.
  • Gzip. The raw KubeVirt disk image but gzipped to help with transferring efficiency.
  • Dir. A directory listing, allowing you to find the files contained in the PVC.
  • Tar.gz The contents of the PVC tarred and gzipped in a single file.

Raw and Gzip will be selected if the PVC is determined to be a KubeVirt disk. KubeVirt disks contain a single disk.img file (or are a block device). Dir will return a list of the files in the PVC, to download a specific file you can replace /dir in the URL with the path and file name. For instance if the PVC contains the file /example/data.txt you can replace /dir with /example/data.txt to download just data.txt file. Or you can use the tar.gz URL to get all the contents of the PVC in a tar file.

The export server certificate is valid for 7 days after which it is rotated by deleting the export server pod and associated secret and generating a new one. If for whatever reason the export server pod dies, the associated secret is also automatically deleted and a new pod and secret are generated. The VirtualMachineExport object status will be automatically updated to reflect the new certificate.

The external link certificates are associated with the Ingress/Route that points to the service created by the KubeVirt operator. The CA that signed the Ingress/Route will part of the certificates.

TTL (Time to live) for an Export

For various reasons (security being one), users should be able to specify a TTL for the VMExport objects that limits the lifetime of an export. This is done via the ttlDuration field which accepts a k8s duration, which defaults to 2 hours when not specified.

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
    name: example-export
spec:
    source:
        apiGroup: "kubevirt.io"
        kind: VirtualMachine
        name: example-vm
    tokenSecretRef: example-token
    ttlDuration: 1h

virtctl integration: vmexport

The virtctl vmexport command allows users to interact with the export API in an easy-to-use way.

vmexport uses two mandatory arguments:

  • The vmexport functions (create|delete|download).
  • The VirtualMachineExport name.

These three functions are:

Create

# Creates a VMExport object according to the specified flag.

# The flag should either be:

# --pvc, to specify the name of the pvc to export.
# --snapshot, to specify the name of the VM snapshot to export.
# --vm, to specify the name of the Virtual Machine to export.

$ virtctl vmexport create name [flags]

Delete

# Deletes the specified VMExport object.

$ virtctl vmexport delete name

Download

# Downloads a volume from the defined VMExport object.

# The main available flags are:

# --output, mandatory flag to specify the output file.
# --volume, optional flag to specify the name of the downloadable volume.
# --vm|--snapshot|--pvc, if specified, are used to create the VMExport object assuming it doesn't exist. The name of the object to export has to be specified.
# --format, optional flag to specify wether to download the file in compressed (default) or raw format.
# --port-forward, optional flag to easily download the volume without the need of an ingress or route. Also, the local port can be optionally specified with the --local-port flag.

$ virtctl vmexport download name [flags]

By default, the volume will be downloaded in compressed format. Users can specify the desired format (gzip or raw) by using the format flag, as shown below:

# Downloads a volume from the defined VMExport object and, if necessary, decompresses it.
$ virtctl vmexport download name --format=raw [flags]

TTL (Time to live)

TTL can also be added when creating a VMExport via virtctl

$ virtctl vmexport create name --ttl=1h

For more information about usage and examples:

$ virtctl vmexport --help

Export a VM volume.

Usage:
  virtctl vmexport [flags]

Examples:
  # Create a VirtualMachineExport to export a volume from a virtual machine:
    virtctl vmexport create vm1-export --vm=vm1

    # Create a VirtualMachineExport to export a volume from a virtual machine snapshot
    virtctl vmexport create snap1-export --snapshot=snap1

    # Create a VirtualMachineExport to export a volume from a PVC
    virtctl vmexport create pvc1-export --pvc=pvc1

    # Delete a VirtualMachineExport resource
    virtctl vmexport delete snap1-export

    # Download a volume from an already existing VirtualMachineExport (--volume is optional when only one volume is available)
    virtctl vmexport download vm1-export --volume=volume1 --output=disk.img.gz

    # Create a VirtualMachineExport and download the requested volume from it
    virtctl vmexport download vm1-export --vm=vm1 --volume=volume1 --output=disk.img.gz

Flags:
  -h, --help              help for vmexport
      --insecure          When used with the 'download' option, specifies that the http request should be insecure.
      --keep-vme          When used with the 'download' option, specifies that the vmexport object should not be deleted after the download finishes.
      --output string     Specifies the output path of the volume to be downloaded.
      --pvc string        Sets PersistentVolumeClaim as vmexport kind and specifies the PVC name.
      --snapshot string   Sets VirtualMachineSnapshot as vmexport kind and specifies the snapshot name.
      --vm string         Sets VirtualMachine as vmexport kind and specifies the vm name.
      --volume string     Specifies the volume to be downloaded.

Use "virtctl options" for a list of global command-line options (applies to all commands).

Use cases

Clone VM from one cluster to another cluster

If you want to transfer KubeVirt disk images from a source cluster to another target cluster, you can use the VMExport in the source to expose the disks and use Containerized Data Importer (CDI) in the target cluster to import the image into the target cluster. Let's assume we have an Ingress or Route in the source cluster that exposes the export proxy with the following example domain virt-exportproxy-example.example.com and we have a Virtual Machine in the source cluster with one disk, which looks like this:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  labels:
    kubevirt.io/vm: vm-example-datavolume
  name: example-vm
spec:
  dataVolumeTemplates:
  - metadata:
      creationTimestamp: null
      name: example-dv
    spec:
      storage:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 20Gi
        storageClassName: local
      source:
        registry:
          url: docker://quay.io/containerdisks/centos-stream:9
  runStrategy: Halted
  template:
    metadata:
      labels:
        kubevirt.io/vm: vm-example-datavolume
    spec:
      domain:
        devices:
          disks:
          - disk:
              bus: virtio
            name: datavolumedisk1
        resources:
          requests:
            memory: 2Gi
      terminationGracePeriodSeconds: 0
      volumes:
      - dataVolume:
          name: example-dv
        name: datavolumedisk1

This is a VM that has a DataVolume (DV) example-dv that is populated from a container disk and we want to export that disk to the target cluster. To export this VM we have to create a token that we can use in the target cluster to get access to the export, or we can let the export controller generate one for us. For example

apiVersion: v1
kind: Secret
metadata:
  name: example-token
stringData:
  token: 1234567890ab
The value of the token is 1234567890ab hardly a secure token, but it is an example. We can now create a VMExport that looks like this:

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
spec:
  tokenSecretRef: example-token #optional, if omitted the export controller will generate a token
  source:
    apiGroup: "kubevirt.io"
    kind: VirtualMachine
    name: example-vm
If the VM is not running the status of the VMExport object will get updated once the export-server pod is running to look something like this:

apiVersion: export.kubevirt.io/v1beta1
kind: VirtualMachineExport
metadata:
  name: example-export
  namespace: example
spec:
  tokenSecretRef: example-token
  source:
    apiGroup: "kubevirt.io"
    kind: VirtualMachine
    name: example-vm
status:
  conditions:
  - lastProbeTime: null
    reason: podReady
    status: "True"
    type: Ready
  - lastProbeTime: null
    reason: pvcBound
    status: "True"
    type: PVCReady
  links:
    external:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: raw
          url: https://virt-exportproxy-example.example.com/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example-dv/disk.img
        - format: gzip
          url: https://virt-exportproxy-example.example.com/api/export.kubevirt.io/v1beta1/namespaces/example/virtualmachineexports/example-export/volumes/example-dv/disk.img.gz
        name: example-disk
    internal:
      cert: |-
        -----BEGIN CERTIFICATE-----
        ...
        -----END CERTIFICATE-----
      volumes:
      - formats:
        - format: raw
          url: https://virt-export-example-export.example.svc/volumes/example-dv/disk.img
        - format: gzip
          url: https://virt-export-example-export.example.svc/volumes/example-dv/disk.img.gz
        name: example-disk
  phase: Ready
  serviceName: virt-export-example-export
Note in this example we are in the example namespace in the source cluster, which is why the internal links domain ends with .example.svc. The external links are what will be visible to outside of the source cluster, so we can use that for when we import into the target cluster.

Now we are ready to import this disk into the target cluster. In order for CDI to import, we will need to provide appropriate yaml that contains the following: - CA cert (as config map) - The token needed to access the disk images in a CDI compatible format - The VM yaml - DataVolume yaml (optional if not part of the VM definition)

virtctl provides an additional argument to the download command called --manifest that will retrieve the appropriate information from the export server, and either save it to a file with the --output argument or write to standard out. By default this output will not contain the header secret as it contains the token in plaintext. To get the header secret you specify the --include-secret argument. The default output format is yaml but it is possible to get json output as well.

Assuming there is a running VirtualMachineExport called example-export and the same namespace exists in the target cluster. The name of the kubeconfig of the target cluster is named kubeconfig-target, to clone the vm into the target cluster run the following commands:

$ virtctl vmexport download example-export --manifest --include-secret --output=import.yaml
$ kubectl apply -f import.yaml --kubeconfig=kubeconfig-target

The first command generates the yaml and writes it to import.yaml. The second command applies the generated yaml to the target cluster. It is possible to combine the two commands writing to standard out with the first command, and piping it into the second command. Use this option if the export token should not be written to a file anywhere. This will create the VM in the target cluster, and provides CDI in the target cluster with everything required to import the disk images.

After the import completes you should be able to start the VM in the target cluster.

Download a VM volume locally using virtctl vmexport

Several steps from the previous section can be simplified considerably by using the vmexport command.

Again, let's assume we have an Ingress or Route in our cluster that exposes the export proxy, and that we have a Virtual Machine in the cluster with one disk like this:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  labels:
    kubevirt.io/vm: vm-example-datavolume
  name: example-vm
spec:
  dataVolumeTemplates:
  - metadata:
      creationTimestamp: null
      name: example-dv
    spec:
      storage:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 20Gi
        storageClassName: local
      source:
        registry:
          url: docker://quay.io/containerdisks/centos-stream:9
  runStrategy: Halted
  template:
    metadata:
      labels:
        kubevirt.io/vm: vm-example-datavolume
    spec:
      domain:
        devices:
          disks:
          - disk:
              bus: virtio
            name: datavolumedisk1
        resources:
          requests:
            memory: 2Gi
      terminationGracePeriodSeconds: 0
      volumes:
      - dataVolume:
          name: example-dv
        name: datavolumedisk1

Once we meet these requirements, the process of downloading the volume locally can be accomplished by different means:

Performing each step separately

We can download the volume by performing every single step in a different command. We start by creating the export object:

# We use an arbitrary name for the VMExport object, but specify our VM name in the flag.

$ virtctl vmexport create vmexportname --vm=example-vm

Then, we download the volume in the specified output:

# Since our virtual machine only has one volume, there's no need to specify the volume name with the --volume flag.

# After the download, the VMExport object is deleted by default, so we are using the optional --keep-vme flag to delete it manually.

$ virtctl vmexport download vmexportname --output=/tmp/disk.img --keep-vme

Lastly, we delete the VMExport object:

$ virtctl vmexport delete vmexportname
Performing one single step

All the previous steps can be simplified in one, single command:

# Since we are using a create flag (--vm) with download, the command creates the object assuming the VMExport doesn't exist.

# Also, since we are not using --keep-vme, the VMExport object is deleted after the download.

$ virtctl vmexport download vmexportname --vm=example-vm --output=/tmp/disk.img

After the download finishes, we can find our disk in /tmp/disk.img.