If you are looking for a way to deploy Virtual Machines, alongside your container workloads, then KubeVirt may be the answer. In this blog post we will explore the details on how to create a Virtual Machine using KubeVirt.
Platform9 Managed Kubernetes has a new UI for KubeVirt. The UI has been reworked and has added a Wizard that will guide you through creation, UI enhancements that show more details around Virtual Machines and Virtual Machine Instances, and additional options as well as overviews.
KubeVirt helps users run Virtual Machines alongside their container based workloads. In this guide we will walk through the creation of a VM using the PMK UI, however you could also create a VM using kubectl.
Before we can walk through this guide we need to make sure a PMK environment has been setup with KubeVirt. After KubeVirt has been installed we need to install storage to back our Virtual Machine. In this demo I installed Ceph using Rook. Below are guides on how to do each, however if you get stuck we also have documentation that can be referenced: KubeVirt.
Starting out we have a new view for Virtual Machines and Virtual Machine Instances. There are a few differences between a VM and a VMI, but I usually think of it as a running Virtual Machine (VMI) vs a template of a Virtual Machine (VM). If you create a VMI then the instance will start running as soon as it has been setup, while a Virtual Machine will wait for you to start/stop/restart unless you have specified that it should always be running.
In this view we will end up selecting “New Virtual Machine” which is where we will actually define the Virtual Machine as a VM/VMI.
Here is a view of the VMI section, which is where we can view running Virtual Machines. Later in this demo we will show a running VMI.
The wizard has a few sections and different options. We are going to walk through creating a VM with the Wizard, however if you wanted you could create a VM with YAML which will allow you to paste in your VM/VMI YAML configuration. If you already have working VMs and just want to create them in an environment managed by PMK, then you should use the YAML option.
In this section we name the VM. A VM name can only use specific characters.
A lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, ‘-‘ or ‘.’, and must start and end with an alphanumeric character (e.g. ‘example.com’, regex used for validation is ‘[a-z0-9]([-a-z0-9][a-z0-9])?(\.[a-z0-9]([-a-z0-9][a-z0-9])?)*’
If you have already created the backing storage for the VM then you will want to use the same namespace where the volume lives. In our example we are doing everything in the default namespace.
Select the cluster and then the namespace where you want to run your VM. Select the VM type and Run Strategy. In the demo we are deploying to the KubeVirt cluster, in the default namespace, with the type Virtual Machine, and the run strategy Manual. With the manual run strategy we can control when the Virtual Machine starts/stops/restarts.
Storage can get complicated as there are a few different options. We will go over each, however the type used will depend on your use case.
With a cloud image we can pull qcow2/raw images, similar to what you would use with OpenStack, and copy them into a volume. The volume will then back the VM pod. For this example we will select access mode ReadWriteOnce, set the size to 15GB, and the storage class to ceph-block. Ceph-Block will be setup by default if you are using Rook Ceph. If you are using a different type of storage backend then you’ll want to verify what access mode and storage class to use.
In the demo I am using a cirros image for simplicity. The cirros image will not require additional configuration with cloud-init to allow us to login. Other cloud images may require SSH keys for authentication, or specific options that enable password based authentication over SSH. SSH access can also be configured by modifying the YAML instead of using Cloud-Init.
accessCredentials: - sshPublicKey: source: secret: secretName: my-pub-key
An image from a Repository is a container that has a cloud image expanded into it. This can be useful if you want to store the image in a public or private repository. We set the same details for Access Mode, Size, and Storage Class.
Example of Cirros in the KubeVirt DockerHub repository
Uploading an image can cut down on the time it takes to create a Virtual Machine, and also gives us a way to clone or attach an existing disk to our VM. In the examples for Clone and Attach Existing we are using a previously created data volume using the commands provided in the UI.
It is possible that you will run into an error message related to uploadproxy URL not found:
PVC default/demo-vm-dv not found DataVolume default/demo-vm-dv created Waiting for PVC demo-vm-dv upload pod to be ready... Pod now ready uploadproxy URL not found
To resolve the issue you will want to find the endpoint with the following commands:
$ kubectl get service cdi-uploadproxy -n cdi NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE cdi-uploadproxy ClusterIP 10.21.14.48 <none> 443/TCP 19m
kubectl virt image-upload --pvc-name=demo-vm-pv --pvc-size=10Gi --image-path=cirros-0.5.1-x86_64-disk.img --uploadproxy-url=https://10.21.14.48 --insecure
kubectl virt image-upload dv demo-vm-dv --size=10Gi --image-path=cirros-0.5.1-x86_64-disk.img --uploadproxy-url=https://10.21.14.48 --insecure
virtctl image-upload --pvc-name=demo-vm-pv --pvc-size=10Gi --image-path=cirros-0.5.1-x86_64-disk.img --uploadproxy-url=https://10.21.14.48 --insecure
virtctl image-upload dv demo-vm-dv --size=10Gi --image-path=cirros-0.5.1-x86_64-disk.img --uploadproxy-url=https://10.21.14.48 --insecure
If you don’t have access to the ClusterIP, update the service to type LoadBalancer or NodePort.
With clone existing we can take the image that was uploaded to a volume and clone it. This option reduces the time it takes to download the image and expand it into a volume, which means faster VM creation. The time it takes to create the volume, download the image, and expand it into the volume will depend on your hardware and network speed. Since we are cloning the volume instead of using it, we can use the base volume to create additional VMs in the future.
The alternative to cloning would be to use the volume we created. This will attach the volume, which can be useful if we only want to create a single Virtual Machine based off of the image we uploaded previously to a volume.
In this section we can specify VCPU and RAM resource requests. A preset can also be selected if one has been configured beforehand.
In the network section we can select between Masquerade (default) and Bridge. This section is more useful if you have configured networking, or have special networking needs.
Cloud-Init allows us to configure the Virtual Machine on launch. In this section we can specify user configurations, SSH keys, applications to install, and many other options. For our example, since we SSH using password based authentication, we have left the cloud-init section blank. In the prerequisites section, earlier in the blog, we have linked to a KubeVirt setup that provides YAML configurations using cloud-init in case you need to see examples.
Cloud-Init Docs – https://cloudinit.readthedocs.io/en/latest/
The YAML resource shows us what is going to be created based on the selections we have made with the Wizard. In this section you can edit the YAML if you have additional requirements such as labeling.
Finally we have made it to the end of the Wizard. In this section there is a brief overview for review. Once the information has been reviewed we can select Create, and a VM will be created for us. Since we specified a Run Strategy of Manual we will need to start the VM after it has been created.
Virtual Machine Start
Now that the VM has been created it will be viewable in the Virtual Machines section. Select the VM and then select Start. This will start the VM which will create a new resource for us, the Virtual Machine Instance. The VMI will be viewable in the VMI section.
Virtual Machine Instances
Now that we have started the VM we can view it in the VM Instances section. This will give us an overview of the VM such as the status, the node where it is running, and other labels that were added. In this section we can also pause and unpause the VMI.
Now that we have a Virtual Machine running we may want to access it. We can use the virt/virtctl CLI to expose the VM via a ClusterIP.
There are options to use a LoadBalancer, NodePort, or ClusterIP. ClusterIP is the default. To use any of the others we can specify –type TYPE.
kubectl virt expose vmi demo-vm --name demo-svc --port 22 --target-port 22
Once we have exposed the VM we can find the ClusterIP that was created with:
$ kubectl get service demo-svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demo-svc ClusterIP 10.21.156.14 <none> 22/TCP 110s
Now we have an IP address that can be used to SSH to the instance. The default user for cirros is cirros and the default password is gocubsgo.
$ ssh firstname.lastname@example.org email@example.com's password: $ uname Linux $ uptime 20:13:20 up 23:22, 1 users, load average: 0.00, 0.00, 0.00
If a VM has been launched, but isn’t starting, we may want to check the volumes to verify that everything has been created and that the image has expanded into the volume.
We are using storage to back our VM which means that we need to verify that the storage is setup and working. Verify that the PV and PVC are both created and not in a blocking state (failed) and that they are bound.
$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-f8064569-246e-4ef9-8ea4-78b17cb63821 10Gi RWO Delete Bound default/demo-test-disk-1 ceph-block 2m21s
$ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE demo-test-disk-1 Bound pvc-f8064569-246e-4ef9-8ea4-78b17cb63821 10Gi RWO ceph-block 2m38s
If there are issues with the disks then you will want to run
kubectl describe pv NAME to see the errors associated with it. Due to the amount of errors you could be seeing we will gloss over this section and defer to the storage providers documentation for the storage you are using.
Based on the examples above, the storage is setup and is working correctly. Let’s go ahead and check on the importer pod that expands the image specified into the volume we have created. We can run
kubectl get pods to see the importer pod running, then run
kubectl logs -f NAMEto see the logs/status of the disk creation and image conversion.
$ kubectl get pods NAME READY STATUS RESTARTS AGE importer-demo-test-disk-1 1/1 Running 0 25s
$ kubectl logs -f importer-demo-test-disk-1 I1201 20:16:19.774363 1 importer.go:52] Starting importer I1201 20:16:19.825284 1 importer.go:135] begin import process I1201 20:16:21.055268 1 data-processor.go:323] Calculating available size I1201 20:16:21.055353 1 data-processor.go:335] Checking out file system volume size. I1201 20:16:21.055387 1 data-processor.go:343] Request image size not empty. I1201 20:16:21.055437 1 data-processor.go:348] Target size 10447220736. I1201 20:16:21.056862 1 util.go:39] deleting file: /data/lost+found I1201 20:16:21.223388 1 nbdkit.go:269] Waiting for nbdkit PID. I1201 20:16:22.223901 1 nbdkit.go:290] nbdkit ready. I1201 20:16:22.223967 1 data-processor.go:231] New phase: Convert I1201 20:16:22.223992 1 data-processor.go:237] Validating image I1201 20:16:24.731156 1 qemu.go:250] 0.00 I1201 20:16:26.730825 1 qemu.go:250] 1.25 ... I1201 20:17:29.001141 1 qemu.go:250] 99.69 I1201 20:17:30.727739 1 data-processor.go:231] New phase: Resize W1201 20:17:30.774162 1 data-processor.go:310] Available space less than requested size, resizing image to available space 9872623104. I1201 20:17:30.774296 1 data-processor.go:316] Expanding image size to: 9872623104 I1201 20:17:30.830524 1 data-processor.go:237] Validating image I1201 20:17:30.841875 1 data-processor.go:231] New phase: Complete I1201 20:17:30.842315 1 importer.go:217] Import Complete
After the disk has been created we can start the VM.
The new Wizard makes creating Virtual Machines easier by taking some of the guesswork out of creating a VM YAML by hand. We hope the new UI and Wizard workflow will make KubeVirt more accessible to everyone. If you have any feedback or questions – please join our Slack Community.
- Using MetalLB to add the LoadBalancer Service to Kubernetes Environments - February 28, 2022
- How to Set Up Knative Serving on Kubernetes - February 2, 2022
- How to Create a Virtual Machine Using KubeVirt – A Detailed Walkthrough - December 1, 2021