Aravind Prabhakar

Systems Engineer | Networking | Security | PreSales | Cloud | Devops | AIOps

View on GitHub Linkedin Blogs Tags
30 May 2022

Using Kubevirt to run VMs on kubernetes

By Aravind

Using Kubevirt to run VMs on kubernetes clusters

Kubernetes has been the defacto standard for ochestration container and VM workloads. The APIs have been extended using Kubevirt which is a CNCF project to instantiate VM workloads. This post talks about how to spin up Juniper’s VMs, particularly vSRX on kubevirt. Kubevirt under the hood uses libvirt , so KVM based VMs can be spun up.

most of the documents talks about 0.35 and prior versions. it is important for k8s version and kubevirt versions to match the release times. I believe around 3 releases gap is fine based on my testing in my case i had the below k8s versions and none of the kubevirt would install. So had to try the newer ones. so v0.50.0 worked based on the k8s version i had.

Kubernetes installation

Follow the link here to setup kubernetes cluster. Talks about 3 servers running Ubuntu 20.04 version

root@k8s-master:~/# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:48:05Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}

root@k8s-master:~/# kubectl version
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:49:13Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.6", GitCommit:"ad3338546da947756e8a88aa6822e9c11e7eac22", GitTreeState:"clean", BuildDate:"2022-04-14T08:43:11Z", GoVersion:"go1.17.9", Compiler:"gc", Platform:"linux/amd64"}

root@k8s-master:~/# kubelet --version
Kubernetes v1.23.6

Install Kubevirt

You can change the version v0.51.0 to the newer ones accordingly posted on https://github.com/kubevirt/kubevirt/branches/active and https://kubevirt.io/user-guide/latest_release_notes/

kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.51.0/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/v0.51.0/kubevirt-cr.yaml

Verify pod status

root@k8s-master:~/vsrx-kubevirt# kubectl get pods -n kubevirt
NAME                              READY   STATUS    RESTARTS     AGE
virt-api-667c9d49c7-btcjd         1/1     Running   1 (8h ago)   9h
virt-api-667c9d49c7-wblj5         1/1     Running   1 (8h ago)   9h
virt-controller-fb8776755-26tld   1/1     Running   1 (8h ago)   9h
virt-controller-fb8776755-lm5sv   1/1     Running   1 (8h ago)   9h
virt-handler-6wjgt                1/1     Running   0            9h
virt-handler-zc8rs                1/1     Running   0            9h
virt-operator-54986cb6df-d6wms    1/1     Running   0            9h
virt-operator-54986cb6df-lrfj9    1/1     Running   0            9h

Install virtctl

Virtctl is used as a tool to upload images which can then be used to spin up teh VMs. The procedure to upload and spin will be covered in the later sections

Download the respective version of virtctl . Use the same as kubevirt. In this case v0.51.0. The latest version can be found here https://github.com/kubevirt/kubevirt/releases

export VERSION=v0.51.0
wget https://github.com/kubevirt/kubevirt/releases/download/${VERSION}/virtctl-${VERSION}-linux-amd64

add respective permissions to the executable

sudo chmod +rx <virtctl file>
cp <file> /usr/local/bin/virtctl

Install libvirt client

This needs to be installed on all the worker nodes where the VM needs to be spun up.

root@k8s-worker1:~# sudo apt install libvirt-clients

In case the VM work loads needs to be spun up on the master node, then the master node has to be tainted. In order to taint, run the below.

kubectl taint nodes --all node-role.kubernetes.io/master-

Install CDI (Containerized data importer)

Containerized data importer is needed in order to upload VM images which can be used to run the VMs.

Find versions here

https://github.com/kubevirt/containerized-data-importer/releases

replace version with values and install

VERSION=$(curl -s https://github.com/kubevirt/containerized-data-importer/releases/latest | grep -o "v[0-9]\.[0-9]*\.[0-9]*")
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-operator.yaml
kubectl create -f https://github.com/kubevirt/containerized-data-importer/releases/download/$VERSION/cdi-cr.yaml

Verify the pods

root@k8s-master:~/vsrx-kubevirt# kubectl get pods -n cdi -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE          NOMINATED NODE   READINESS GATES
cdi-apiserver-76ffb454c9-4mjsr     1/1     Running   0          9h    10.244.1.9    k8s-worker2   <none>           <none>
cdi-deployment-668465f546-4xc87    1/1     Running   0          9h    10.244.2.7    k8s-worker1   <none>           <none>
cdi-operator-64d6dc7f6f-2997n      1/1     Running   0          9h    10.244.1.8    k8s-worker2   <none>           <none>
cdi-uploadproxy-664c777f8f-cb2fn   1/1     Running   0          9h    10.244.1.10   k8s-worker2   <none>           <none>

Expose node port

Expose node port for the CDI by creating a node port service so that teh CDI proxy is reachable from outside of the cluster

save the file as cdi-service.yaml

root@k8s-master:~/vsrx-kubevirt# more CDI/cdi-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: cdi-uploadproxy-nodeport
  namespace: cdi
  labels:
    cdi.kubevirt.io: "cdi-uploadproxy"
spec:
  type: NodePort
  ports:
    - port: 443
      targetPort: 443
      nodePort: 31001
      protocol: TCP
  selector:
    cdi.kubevirt.io: cdi-uploadproxy

Apply the file

kubectl apply -f cdi-service.yaml

Verify service status

root@k8s-master:~/vsrx-kubevirt# kubectl get svc -n cdi
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
cdi-api                    ClusterIP   10.108.255.248   <none>        443/TCP         9h
cdi-prometheus-metrics     ClusterIP   10.98.76.169     <none>        8080/TCP        9h
cdi-uploadproxy            ClusterIP   10.96.92.83      <none>        443/TCP         9h
cdi-uploadproxy-nodeport   NodePort    10.104.121.237   <none>        443:31001/TCP   8h

Upload the images to CDI

In order the upload the images to CDI, there are few things that are required, depending on how the image needs to be spun up. This talks specifically about persistent disks. This may not be needed if a containerDisk is used.

when the datavolume is created, it automatically creates an upload pod and which triggers a PVC (persistent volume claim) which ultimately gets bound to a PV (persistent volume). it creates a scratch paritition (PV) would also be needed. Once the upload is complete this will be released.

Create Storage class

root@k8s-master:~/vsrx-kubevirt/CDI# more storage_profile.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  name: hostpath-provisioner
provisioner: kubernetes.io/hostpath-provisioner
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

Apply

kubectl apply -f storage_profile.yaml

Verify the storage class

root@k8s-master:~/vsrx-kubevirt# kubectl get storageclass
NAME                             PROVISIONER                          RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
hostpath-provisioner (default)   kubernetes.io/hostpath-provisioner   Delete          WaitForFirstConsumer   false                  156m

Create persistent volume

Create 2, one for scratch and another for boot disk. The hostPath is a path on the local server. The storageClassName is important because that is the tying in factor between the DV, PV and PVC. so ensure the correct names are used. Otherwise, the pvc, will reamin in pending stage and will never get bound.

root@k8s-master:~/vsrx-kubevirt/CDI# more pv1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
spec:
  storageClassName: hostpath-provisioner
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 25Gi
  hostPath:
    path: /root/vsrx-kubevirt/pvdata2

root@k8s-master:~/vsrx-kubevirt/CDI# more pv2.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0002
spec:
  storageClassName: hostpath-provisioner
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 25Gi
  hostPath:
    path: /root/vsrx-kubevirt/pvdata2

Apply the file

kubectl apply -f pv1.yaml
kubectl apply -f pv2.yaml

Verify the PV status

root@k8s-master:~/vsrx-kubevirt/CDI# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                       STORAGECLASS           REASON   AGE
pv0001   25Gi       RWO            Retain           Released   default/vsrx-boot-scratch   hostpath-provisioner            12m
pv0002   25Gi       RWO            Retain           Bound      default/vsrx-boot           hostpath-provisioner            12m

Create DataVolume (DV)

root@k8s-master:~/vsrx-kubevirt/CDI# more dv.yaml
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
  name: vsrx-boot
spec:
  source:
      upload: {}
  pvc:
    accessModes:
      - ReadWriteOnce
    resources:
      requests:
        storage: 25Gi

Apply the file

kubectl apply -f dv.yaml

Verify the DV status

root@k8s-master:~/vsrx-kubevirt/CDI# kubectl get dv
NAME        PHASE       PROGRESS   RESTARTS   AGE
vsrx-boot   Succeeded   N/A                   3m26s

Once the DV is applied, a pod is kicked off for image upload and will be in waiting, until the PVCs are BOUND. once BOUND, the pod will transition to a running state. Once running, upload the image.

Verify the PVC status

root@k8s-master:~/vsrx-kubevirt/CDI# kubectl get pvc
NAME                STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS           AGE
vsrx-boot           Bound    pv0002   25Gi       RWO            hostpath-provisioner   12s
vsrx-boot-scratch   Bound    pv0001   25Gi       RWO            hostpath-provisioner   12s

Upload the image using virtctl

use the dv which was created. The Uploadproxy IP is the pod IP and 8443 is the port exposed. Ideally the nodePort exposed should be used which is outside the cluster. Since the connection did not succeed, local clusterIP has been used. The image vsrx3_21.4R2_kubevirt.qcow2 is locally present on the master node.

root@k8s-master:~/vsrx-kubevirt# virtctl image-upload dv vsrx-boot --size=25Gi --image-path=vsrx3_21.4R2_kubevirt.qcow2 --uploadproxy-url https://10.244.1.10:8443  --insecure --storage-class hostpath-provisioner

Once kicked off, below will be the output if no errors are noticed. Errors include ceritificates, PVC unbound and waiting for pod to go into running.

Using existing PVC default/vsrx-boot
Uploading data to https://10.244.1.10:8443

 746.81 MiB / 746.81 MiB [=================================================================================================] 100.00% 6s

Uploading data completed successfully, waiting for processing to complete, you can hit ctrl-c without interrupting the progress
Processing completed successfully
Uploading vsrx3_21.4R2_kubevirt.qcow2 completed successfully

At this time, the upload to the registry is completed. the scratch PVC would be released at this time.

root@k8s-master:~/vsrx-kubevirt/CDI# kubectl get pvc
NAME        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS           AGE
vsrx-boot   Bound    pv0002   25Gi       RWO            hostpath-provisioner   3m23s

Use multus for multiple interfaces for VMs

Multus is needed incase multiple interfaces is needed for the VM. The interfaces can come from multiple CNIs, or bridge, macvlan or SRIOV. Below is the process to use multus and connect to linux bridges

Install Multus

Install multus using the below. These are obtained directly from the multus quick start guide.

git clone https://github.com/k8snetworkplumbingwg/multus-cni.git && cd multus-cni
cat ./deployments/multus-daemonset-thick-plugin.yml | kubectl apply -f -

Verify the status

root@k8s-master:~# kubectl get pods -A | grep multus
kube-system   kube-multus-ds-dmttm                 1/1     Running   0            155m
kube-system   kube-multus-ds-dpxrw                 1/1     Running   0            155m
kube-system   kube-multus-ds-qtnmk                 1/1     Running   0            155m

Create Network Attachment Definition

root@k8s-master:~/vsrx-kubevirt/NAD# more nad-fxp.yaml
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: fxp
spec:
  config: '{
    "cniVersion": "0.3.0",
    "name": "fxp",
    "type": "bridge",
    "bridge": "br0",
    "isGateway": true,
    "ipam": {
     "type": "host-local",
     "subnet": "192.168.5.0/24"
    }
}'

Apply the file

kubectl apply -f nad-fxp.yaml

Similarly create multiple NADs so that it can be added to the VM manifest file.

Finally Create VM and Start

Finally create the VM using all the above parameters and start. Ensure you use the same data volume name is being used as that was created

root@k8s-master:~/vsrx-kubevirt# more vsrx-kubevirt_cdi.yaml
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: vsrx
spec:
  running: True
  template:
    metadata:
      labels:
        kubevirt.io/vm: vsrx
    spec:
      domain:
        ioThreadsPolicy: auto
        cpu:
          sockets: 1
          cores: 4
          threads: 1
        resources:
          requests:
            memory: 8Gi
            cpu: "4"
          limits:
            cpu: "4"
            memory: 8Gi
        devices:
          useVirtioTransitional: true
          disks:
            - name: boot
              disk:
                bus: virtio
          interfaces:
            - name: default
              masquerade: {}
            - name: fxp             <<< 2nd interface 
              bridge: {}
            - name: left            <<< 3rd interface 
              bridge: {}
      volumes:
        - name: boot
          dataVolume:
            name: vsrx-boot         <<< name should match 
      networks:
        - name: default
          pod: {}
        - name: fxp                 <<< multus based interface 
          multus:
            networkName: fxp
        - name: left
          multus:
            networkName: left

Ensure you use useVirtioTransitional: true . Else the vSRX will end up in boot loop.

Apply the manifest file

kubectl apply -f vsrx-kubevirt_cdi.yaml

Verify the status.

Once applied, there will be a kubevirt pod which will go into running state. This pod can be used to run virsh commands.

root@k8s-master:~/vsrx-kubevirt# kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
virt-launcher-vsrx-ggf8d   1/1     Running   0          109m

check vm status

root@k8s-master:~/vsrx-kubevirt# kubectl get vm
NAME   AGE    STATUS    READY
vsrx   111m   Running   True

Login to VM

Login to VM and verify if all network interfaces have been attached.

root@k8s-master:~/vsrx-kubevirt# virtctl console vsrx
Successfully connected to vsrx console. The escape sequence is ^]

root> show chassis fpc
                     Temp  CPU Utilization (%)   CPU Utilization (%)  Memory    Utilization (%)
Slot State            (C)  Total  Interrupt      1min   5min   15min  DRAM (MB) Heap     Buffer
  0  Online          -------------------- CPU less FPC --------------------

root> show interfaces terse
Interface               Admin Link Proto    Local                 Remote
ge-0/0/0                up    up
gr-0/0/0                up    up
ip-0/0/0                up    up
lsq-0/0/0               up    up
lt-0/0/0                up    up
mt-0/0/0                up    up
sp-0/0/0                up    up
sp-0/0/0.0              up    up   inet
                                   inet6
sp-0/0/0.16383          up    up   inet
ge-0/0/1                up    up
dsc                     up    up
fti0                    up    up
fxp0                    up    up
fxp0.0                  up    up
gre                     up    up
ipip                    up    up
irb                     up    up
lo0                     up    up
lo0.16384               up    up   inet     127.0.0.1           --> 0/0
lo0.16385               up    up   inet     10.0.0.1            --> 0/0
                                            10.0.0.16           --> 0/0
                                            128.0.0.1           --> 0/0
                                            128.0.0.4           --> 0/0
                                            128.0.1.16          --> 0/0
lo0.32768               up    up
lsi                     up    up
mtun                    up    up
pimd                    up    up
pime                    up    up
pp0                     up    up
ppd0                    up    up
ppe0                    up    up
st0                     up    up
tap                     up    up
vlan                    up    down

Run VIRSH commands

If one needs to run virsh commands as on KVM systems for debugging, then the pod can be used.

kubectl exec -it virt-launcher-vsrx-ggf8d bash

bash-4.4# virsh list
 Id   Name           State
------------------------------
 1    default_vsrx   running

API reference

The below link contains the API reference

Using emulation

Sometimes we may not have baremetal servers but a bunch of VMs. In such cases where nested virtualization is required, emulation should be turned on.

Perform the below in order to achieve the same.

Edit kubevirt

kubectl edit kubevirt kubevirt -n kubevirt

Add the below params

spec:
  configuration:
    developerConfiguration:
      useEmulation: true

save the file using esc + :wq. Now you can spin up VMs on the cluster.

[ linux  kubernetes  ] tags: linux - kubernetes