Simplifying KubeVirt's `VirtualMachine` UX with Instancetypes and Preferences

KubeVirt’s VirtualMachine API contains many advanced options for tuning a virtual machine’s resources and performance that go beyond what typical users need to be aware of. Users have until now been unable to simply define the storage/network they want assigned to their VM and then declare in broad terms what quality of resources and kind of performance they need for their VM. Instead, the user has to be keenly aware how to request specific compute resources alongside all of the performance tunings available on the VirtualMachine API and how those tunings impact their guest’s operating system in order to get a desired result.

A common pattern for IaaS is to have abstractions separating the resource sizing and performance of a workload from the user-defined values related to launching their custom application. This pattern is evident across all the major cloud providers (also known as hyperscalers) as well as open source IaaS projects like OpenStack. AWS has instance types, GCP has machine types, Azure has instance VM sizes, and OpenStack has flavors.

Let’s take AWS for example to help visualize what this abstraction enables. Launching an EC2 instance only requires a few top level arguments; the disk image, instance type, keypair, security group, and subnet:

$ aws ec2 run-instances --image-id ami-xxxxxxxx \
                        --count 1 \
                        --instance-type c4.xlarge \
                        --key-name MyKeyPair \
                        --security-group-ids sg-903004f8 \
                        --subnet-id subnet-6e7f829e

When creating the EC2 instance the user doesn’t define the amount of resources, what processor to use, how to optimize the performance of the instance, or what hardware to schedule the instance on. Instead, all of that information is wrapped up in that single --instance-type c4.xlarge CLI argument. c4 denotes a specific performance profile version, in this case from the Compute Optimized family and xlarge denotes a specific amount of compute resources provided by the instance type, in this case 4 vCPUs, 7.5 GiB of RAM, 750 Mbps EBS bandwidth, etc.

While hyperscalers can provide predefined types with performance profiles and compute resources already assigned IaaS and virtualization projects such as OpenStack and KubeVirt can only provide the raw abstractions for operators, admins, and even vendors to then create instances of these abstractions specific to each deployment.

Instancetype API

The recently renamed instancetype API and associated CRDs aim to address this by providing KubeVirt users with a set of APIs and abstractions that allow them to make fewer choices when creating a VirtualMachine while still ending up with a working, performant guest at runtime.

VirtualMachineInstancetype

---
apiVersion: instancetype.kubevirt.io/v1alpha1
kind: VirtualMachineInstancetype
metadata:
  name: example-instancetype
spec:
  cpu:
    guest: 1
  memory:
    guest: 128Mi

KubeVirt now provides two instancetype based CRDs, a cluster wide VirtualMachineClusterInstancetype and a namespaced VirtualMachineInstancetype. These CRDs encapsulate the following resource related characteristics of a VirtualMachine through a shared VirtualMachineInstancetypeSpec:

  • CPU : Required number of vCPUs presented to the guest
  • Memory : Required amount of memory presented to the guest
  • GPUs : Optional list of vGPUs to passthrough
  • HostDevices: Optional list of HostDevices to passthrough
  • IOThreadsPolicy : Optional IOThreadsPolicy to be used
  • LaunchSecurity: Optional LaunchSecurity to be used

Anything provided within an instancetype cannot be overridden within a VirtualMachine. For example, CPU and Memory are both required attributes of an instancetype. If a user makes any requests for CPU or Memory resources within their VirtualMachine, the instancetype will conflict and the request will be rejected.

VirtualMachinePreference

---
apiVersion: instancetype.kubevirt.io/v1alpha1
kind: VirtualMachinePreference
metadata:
  name: example-preference
spec:
  devices:
    preferredDiskBus: virtio
    preferredInterfaceModel: virtio

KubeVirt also provides two further preference based CRDs, again a cluster-wide VirtualMachineClusterPreference and namespaced VirtualMachinePreference. These CRDs encapsulate the preferred value of any remaining attributes of a VirtualMachine required to run a given workload, again this is through a shared VirtualMachinePreferenceSpec.

Unlike instancetypes, preferences only represent the preferred values and as such can be overridden by values in the VirtualMachine provided by the user.

VirtualMachine{Instancetype,Preference}Matcher

---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: example-vm
spec:
[..]
  instancetype:
    kind: VirtualMachineInstancetype
    name: example-instancetype
  preference:
    kind: VirtualMachinePreference
    name: example-preference
[..]

The previous instancetype and preference CRDs are matched to a given VirtualMachine through the use of a matcher. Each matcher consists of the following:

VirtualMachineInstancePreset Deprecation

The new instancetype API and CRDs conflict somewhat with the existing VirtualMachineInstancePreset CRD. The approach taken by the CRD has also been removed in core k8s so, as advertised on the mailing list, I have started the process of deprecating VirtualMachineInstancePreset in favor of the Instancetype CRDs listed above.

Examples

The following example is taken from the KubeVirt User Guide:

$ cat << EOF | kubectl apply -f - 
---
apiVersion: instancetype.kubevirt.io/v1alpha1
kind: VirtualMachineInstancetype
metadata:
  name: cmedium
spec:
  cpu:
    guest: 1
  memory:
    guest: 1Gi
---
apiVersion: instancetype.kubevirt.io/v1alpha1
kind: VirtualMachinePreference
metadata:
  name: fedora
spec:
  devices:
    preferredDiskBus: virtio
    preferredInterfaceModel: virtio
    preferredRng: {}
  features:
    preferredAcpi: {}
    preferredSmm: {}
  firmware:
    preferredUseEfi: true
    preferredUseSecureBoot: true    
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  creationTimestamp: null
  name: fedora
spec:
  instancetype:
    name: cmedium
    kind: virtualMachineInstancetype
  preference:
    name: fedora
    kind: virtualMachinePreference
  runStrategy: Always
  template:
    metadata:
      creationTimestamp: null
    spec:
      domain:
        devices: {}
      volumes:
      - containerDisk:
          image: quay.io/containerdisks/fedora:latest
        name: containerdisk
      - cloudInitNoCloud:
          userData: |-
            #cloud-config
            users:
              - name: admin
                sudo: ALL=(ALL) NOPASSWD:ALL
                ssh_authorized_keys:
                  - ssh-rsa AAAA...
        name: cloudinit
EOF

We can compare the original VirtualMachine spec with that of the running VirtualMachineInstance to confirm our instancetype and preferences have been applied using the following diff command:

$ diff --color -u <( kubectl get vms/fedora -o json | jq .spec.template.spec) <( kubectl get vmis/fedora -o json | jq .spec)
[..]
 {
   "domain": {
-    "devices": {},
+    "cpu": {
+      "cores": 1,
+      "model": "host-model",
+      "sockets": 1,
+      "threads": 1
+    },
+    "devices": {
+      "disks": [
+        {
+          "disk": {
+            "bus": "virtio"
+          },
+          "name": "containerdisk"
+        },
+        {
+          "disk": {
+            "bus": "virtio"
+          },
+          "name": "cloudinit"
+        }
+      ],
+      "interfaces": [
+        {
+          "bridge": {},
+          "model": "virtio",
+          "name": "default"
+        }
+      ],
+      "rng": {}
+    },
+    "features": {
+      "acpi": {
+        "enabled": true
+      },
+      "smm": {
+        "enabled": true
+      }
+    },
+    "firmware": {
+      "bootloader": {
+        "efi": {
+          "secureBoot": true
+        }
+      },
+      "uuid": "98f07cdd-96da-5880-b6c7-1a5700b73dc4"
+    },
     "machine": {
       "type": "q35"
     },
-    "resources": {}
+    "memory": {
+      "guest": "1Gi"
+    },
+    "resources": {
+      "requests": {
+        "memory": "1Gi"
+      }
+    }
   },
+  "networks": [
+    {
+      "name": "default",
+      "pod": {}
+    }
+  ],
   "volumes": [
     {
       "containerDisk": {
-        "image": "quay.io/containerdisks/fedora:latest"
+        "image": "quay.io/containerdisks/fedora:latest",
+        "imagePullPolicy": "Always"
       },
       "name": "containerdisk"
     },

Future work

There’s still plenty of work required before the API and CRDs can move from their current alpha version to beta. We have a specific kubevirt/kubevirt issue tracking our progress to beta. As set out there and in the KubeVirt community API Graduation Phase Expecations, part of this work is to seek feedback from the wider community so please do feel free to chime in there with any and all feedback on the API and CRDs.

You can also track our work on this API through the area/instancetype tag or my personal blog where I will be posting regular updates and demos for instancetypes.