<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Daniel Smith on Medium]]></title>
        <description><![CDATA[Stories by Daniel Smith on Medium]]></description>
        <link>https://medium.com/@danielsmith_81273?source=rss-fcf240be1002------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*5nPpVToA8qz2jv9u.</url>
            <title>Stories by Daniel Smith on Medium</title>
            <link>https://medium.com/@danielsmith_81273?source=rss-fcf240be1002------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 22:12:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@danielsmith_81273/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Taking KubeVirt and K8s 1.34/1.33 for a ride]]></title>
            <link>https://medium.com/@danielsmith_81273/taking-kubevirt-and-k8s-1-34-1-33-for-a-ride-8ad3eba255bb?source=rss-fcf240be1002------2</link>
            <guid isPermaLink="false">https://medium.com/p/8ad3eba255bb</guid>
            <category><![CDATA[k8s]]></category>
            <category><![CDATA[kubevirt]]></category>
            <dc:creator><![CDATA[Daniel Smith]]></dc:creator>
            <pubDate>Tue, 07 Oct 2025 19:11:37 GMT</pubDate>
            <atom:updated>2025-10-12T03:23:38.831Z</atom:updated>
            <content:encoded><![CDATA[<p>As the summer winds down, the falls colours start to pop out and we find ourselves at the weird in-between time of sailing and snowboarding season, a grey rainy Saturday in September gave me a chance to take K8s 1.34 (and some 1.33) features for a spin, with a desire to focus on the new and fancy KubeVirt extensions that allow you to spin up VMs via K8s Custom Resource Definitions (CRDs) on your cluster much as would any other resource.</p><p>Along with examining the KubeVirt extensions, there are 2 or 3 other features that came out in 1.34 and 1.33 that I wanted to explore and understand how they are working overall, and this post will explore them in detail. In these examples im using Rancher k3s installed on top of a Ubuntu and for this post we will focus primarily on the KubeVirt.</p><p>All of the scripts, CRD examples and other materials that I outline in this post can be found in the <a href="https://github.com/dasmlab/k8s-kubevirt-134-primer">github repo here</a>.</p><ul><li><strong><em>Note! </em></strong><em>in the examples below I am using a VM to run K8s that will run VMs — make sure you enable Nesting (VHV, VH-T, etc) on your hypervisor if you are trying this at home</em>) <em>or use baremetal nodes.</em></li><li>However, if you dont have true virtualization on your setup, you can still play with KubeVirt and use Emulation Mode by Patching KubeVirt — see the details below!</li></ul><h3>What does this post Cover?</h3><ul><li>How to install K3s K8s with KubeVirt on a Ubuntu Machine in under 2 minutes and form up a cluster of 1 Control plane and 2 worker nodes.</li><li>How to deploy a simple VM (<em>good ol’ Cirros image for those that remember the QEMU days)</em> via KubeVirt on your K3s cluster.</li></ul><p>There are other features outlined within the github repo that we will examine from K8s 1.33 and 1.34 and those will be covered in subsequent posts if this one goes well!</p><h3>Getting Started — Our Environment</h3><p>For this exploration, I created three VMs using ESXi, but you can use whatever hypervisor or Cloud Provider you prefer and it should work all the same. The four you see below is my “terminal” that we will run our <strong>kubectl</strong> and <strong>virtctl </strong>commands from overall.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/418/1*WxIEft0zAnhuXtpl5ai5QQ.png" /></figure><p>The machines are all running Ubuntu 24 and have the following specs</p><ul><li><strong>Control Node:</strong> 4 vCPUs, 6GB RAM, 200GB Disk, Single Nic</li><li><strong>Worker Node(s):</strong> 4CPUs, 3GB RAM, 200GB Disk, Single Nic</li></ul><p>The machines are all operating on a single, flat vSwitch with a <strong>192.168.1.0/24</strong> subnet and router and DNS at <strong>192.168.1.1. </strong>and our little setup looks like this. Its assumed for this you have internet connectivity from the machines that you stand up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DkXQlfcSokvJeDWq" /><figcaption>A Simple 3 node K8s Cluster and basic Network Layout</figcaption></figure><h3>Setup SSH Keys Across the Machines</h3><p>Once you have installed Ubuntu on your Machines, you should login to your Control node, generate a SSH key and copy that to the worker node /home/&lt;your user&gt;/.ssh/authorized_keys file so that password-less SSH between control plane and worker plane works. This is for the <strong>install.sh</strong> script that we will use for the setup.</p><h4><strong>Step 1</strong></h4><ul><li>Login to Control Node and Create an SSH key with <strong>ssh-keygen</strong> command. I used the defaults (press enter through), but you can configure as you like.</li></ul><pre>ssh-keygen -t rsa</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SBHt8DeA_oiZMqp3DeImYg.png" /></figure><h4>Step 2</h4><ul><li>Install the id_rsa.pub key created above on the worker nodes by using the SCP command (<em>note: we can do this since we know there is no other keys installed on the workers, if you are doing this on existing node, be careful as this would wipe out things on those node)</em></li></ul><pre>scp ~/.ssh/id_rsa.pub &lt;your user&gt;@worker-XX:/home/&lt;your user&gt;/.ssh/authorized_keys</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*20wgtpBvh7OG6IDJiUtANw.png" /></figure><p>Repeat for both workers and test it that you can SSH to the workers from the control plane without a password as shown below. <em>You will get prompted for key exchange on the first try.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uVD_a3PCfwTXfo0JDaZcPA.png" /></figure><p><em>again, make sure you test both nodes and its working, if ssh isnt working from control planes to workers, the install script will fail as its built on SSH keys being setup.</em></p><h3>Install Rancher (k3s) K8s Cluster with KubeVirt</h3><p>I have provided a simple<strong> install.sh</strong> script that support installing a Single Node or Multi Node K3s Cluster that can be found in the <a href="https://github.com/dasmlab/k8s-kubevirt-134-primer/blob/main/setup/installer/install.sh">github repo here (install.sh)</a> and is used to install the k8s cluster components on the machines.</p><p>The <strong>install.sh</strong> script uses a simple file called <strong>nodes.txt</strong> (or whatever you want to call it) that lists the nodes that will form the cluster.</p><p>The following Arguments are supported in the script:</p><ul><li><strong>mode: </strong>Cluster or Single — <em>Set to cluster if more than one node setup is desired</em></li><li><strong>node-name: </strong>Set the K8s Node Name (if blank, defaults to hostname)</li><li><strong>nodes-file:</strong> Path to the nodes list that defines the nodes in the cluster</li><li><strong>control-host: </strong>The IP address of the Control Plane Host <em>(192.168.1.23 from our diagram above)</em></li></ul><p><strong><em>Install.sh Syntax Example</em></strong></p><pre><br>sudo ./install.sh --mode cluster --node-name cp-00 --nodes-file nodes.txt --control-host &lt;control-ip&gt;</pre><p>Execute the following steps to setup your cluster</p><h4>Step 1</h4><ul><li>Login to your control plane machine and clone the git repo with</li></ul><pre>git clone https://github.com/dasmlab/k8s-kubevirt-134-primer.git</pre><p>Example:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cXjHTdBvoXoqftqcFgQTrw.png" /></figure><h4>Step 2</h4><ul><li>Change to the <strong>k8s-kubevirt-134-primer/setup/installer </strong>directory and examine the files there. You will see the <strong>install.sh</strong>, the <strong>README.md</strong>, and a <strong>nodelist.sample.txt</strong> file there (<em>as shown below)</em></li><li>Copy <strong>nodelist.sample txt to nodeslist.txt</strong> and edit it to reflect our cluster layout.</li></ul><pre>cd k8s-kubevirt-134-primer/setup/installer<br>cp nodelist.sample.txt nodelist.txt</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m7bGQKh2l2PSTEfzNzGJPA.png" /></figure><ul><li>Edit your nodelist.txt to match the hostname’s of your machines. The example below uses the same hostnames for SSH, that is <strong>worker-00</strong> and <strong>worker-01</strong></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KBv5dSPKX6T_QVG4O-E4Rw.png" /></figure><h4>Step 3</h4><ul><li>Run the <strong>./install.sh </strong>script to install the k8s cluster. This should be done from the control plane node.</li></ul><pre>sudo ./install.sh \<br>--mode cluster \<br>--node-name control-00 \<br>--nodes-file nodelist.txt \<br>--control-host 192.168.1.23</pre><p>Example Output (start and end shown)</p><ul><li>Starting of script on control plane node:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9YrEXuNDgKAV1JTP6aj8zg.png" /></figure><ul><li>Ending of script example:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hLC-P3wP8fI_aiaf_MMQKQ.png" /></figure><h4>Step 4</h4><ul><li>Use kubectl and check your cluster is up and nodes are present and ready.</li></ul><pre>kubectl get pods -A<br>kubectl get nodes</pre><p>Example Output of our 3 node cluster (1 Control, 2 Workers)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0P9rl4MyzzhuHG58bi34WQ.png" /></figure><p>Great! Our cluster is up and has KubeVirt installed and ready to go.</p><h4>Step 5 — OPTIONAL / EMULATION MODE</h4><p>If you dont have access to KVM or True Virtulaization, you can run the following command to patch your cluster and use emulation mode.</p><p>Run the following command to patch your cluster</p><pre>    kubectl patch kubevirt kubevirt -n kubevirt -p &#39;{&quot;spec&quot;:{&quot;configuration&quot;:{&quot;developerConfiguration&quot;:{&quot;useEmulation&quot;:true}}}}&#39; --type=merge</pre><h3>Examine the KubeVirt CRD, Pods and Services.</h3><p>With the installation of kubeVirt Extension into our cluster, we can see that the following CRDs are delivered within kubevirt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zf7KH9x9_49zYkU19nht9Q.png" /></figure><p>We can also see the following KubeVirt Operators (pods/replica sets) that are running to act on any of the CRDs that we submit to the cluster. The layout is straightforward with a API Pod for requests, a controller, handler and the coordinating operator pods as shown below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c2fqpxpBiy7VuUmhQl0NGQ.png" /></figure><p>We can see the following services are setup by default as well, including the support for a prometheus scraping point (<em>we will explore what metrics are exposed by KubeVirt later in this post).</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Iyfa74p9806gPG9YSBp0Ug.png" /></figure><h3>Deploy a Virtual Machine via K8s</h3><p>We will use the following three Resource Definitions. A Namespace, a Datavolume — <em>this is new! its like a CSI for VMs </em>and our Virtual Machine itself.</p><p>Here are the three manifests we will use (also available here)</p><p>The <strong>Namespace </strong>is called <strong>kubevirt-lab</strong></p><pre>apiVersion: v1<br>kind: Namespace<br>metadata:<br>  name: kubevirt-lab<br>  labels:<br>    app.kubernetes.io/name: kubevirt-lab<br>    app.kubernetes.io/part-of: kubevirt-exploration</pre><p>The <strong>DataVolume </strong>is called <strong>cirros-demo-dv</strong> and has a <strong>2Gi</strong> size. Notice that this is where we provide the <strong>image </strong>URL via http (<em>there are other sources that are supported in the spec as well).</em></p><p>I have left out “ volumeMode” from the DataVolume definition below, so that it will use the default from the StorageClass on the cluster. Options include “Block” and “Filesystem” depending on your backing Storage setup.</p><pre>kind: DataVolume<br>metadata:<br>  name: cirros-demo-dv<br>  namespace: kubevirt-lab<br>spec:<br>  source:<br>    http:<br>      url: https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img<br>  pvc:<br>    accessModes:<br>      - ReadWriteOnce<br>    resources:<br>      requests:<br>        storage: 2Gi<br></pre><p>The VirtualMachine is called <strong>cirros-vm</strong> and has <strong>1 CPU Core and 256MB of RAM</strong>. We can see we attach our <strong>DataVolume </strong>from above via the <strong>containerdisk</strong> disk that provides the mapping to the volume. <em>Very much like doing a CSI or other mount to a K8s pod!</em></p><p>You can see in this manifest, that we are defining Two (2) Disk Devices. The containerdisk, that will be provided via our DataVolume mount, and then a CloudInit disk, where we have a simple inline configuration. <em>This will avoid the 20 iteration time out that the Cirros image has looking for CloudInit Metadata! :)</em></p><pre><br>apiVersion: kubevirt.io/v1<br>kind: VirtualMachine<br>metadata:<br>  name: cirros-vm<br>  namespace: kubevirt-lab<br>  labels:<br>    app: cirros-demo<br>spec:<br>  running: false<br>  template:<br>    metadata:<br>      labels:<br>        kubevirt.io/domain: cirros-vm<br>    spec:<br>      domain:<br>        cpu:<br>          cores: 1<br>        resources:<br>          requests:<br>            memory: 256Mi<br>        devices:<br>          disks:<br>            - name: cloudinitdisk<br>              disk:<br>                bus: virtio<br>              bootOrder: 2<br>            - name: containerdisk<br>              disk:<br>                bus: virtio<br>              bootOrder: 1<br>      volumes:<br>        - name: containerdisk<br>          dataVolume:<br>            name: cirros-demo-dv<br>        - name: cloudinitdisk<br>          cloudInitNoCloud:<br>            userData: |<br>              #cloud-config<br>              datasource_list: [ None ]<br>              ssh_pwauth: False</pre><h4>Step 1</h4><p>Apply the manifests to the cluster and observe the virtual machine is deployed (<em>of course, you can do this in a single file and/or use ‘-k’ to process all the files as you prefer).</em></p><pre>kubectl apply -f namespace.yaml<br>kubectl apply -f data-volume-demo.yaml<br>kubectl apply -f virtualmachine-cirrors.yaml</pre><h4>Step 2</h4><p>Check that your Virtual Machine and DataVolume are deployed. You should see your VM is in a Stopped State. Use “<strong>kubectl get all -n kubevirt-lab</strong>” to see the output as shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1K27Jfs9AYsfnz_7psmaHw.png" /></figure><p>We can see that the VM is stopped and doing a <strong>“kubectl describe vm cirros-vm -n kubevirt-lab”</strong> shows us the message <strong>“VMI not started” </strong>in the Status section of the output as shown below</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*omBPCJX1niMToXXYHdnJjQ.png" /></figure><h4>Step 3</h4><p>Use the <strong>virtctl</strong> command to start your Virtual Machine. Note: either copy <strong>/etc/rancher/k3s/k3s.yaml </strong>to ~/.kube/config or set KUBECONFIG for this command to work</p><pre>cp /etc/rancher/k3s/k3s.yaml ~/.kube/config<br>virtctl start cirros-vm -n kubevirt-lab</pre><p>You should see the following output from your command. You can then check the status of the objects (DataVolume and VirtualMachine) in the namespace and see the changes that have occurred</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IeacgiE6GEjWZkseJJG7IA.png" /><figcaption>Start VM with Virtctl once defined in K8s CR</figcaption></figure><p>You can see the DataVolume <strong>PROGRESS</strong> indicator as it creates the DataVolume with the Cirros Image. Behind the scenes you can also see that a PersistentVolumeClaim is in use, so very much like your standard K8s interfaces.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7CKTBZVdQxb9aF5GlpGWYw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HEKTt6yj8vKC4yc74ItnjA.png" /></figure><p>After a time your Virtual Machine will be provisioned and you can see that things are up and running.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WVdOhIB5HM9ofQQRcNKpYg.png" /></figure><p><strong>Step 4</strong></p><p>Connect to your Virtual Machine using virtctl and check that its running.</p><pre>virtctl console cirros-vm -n kubevirt-lab</pre><p>And there you have it, a Virtual Machine running on your K8s Worker that was created using a Declarative Method. You can login with <strong>cirros/gocubsgo</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NDxAmAVcQo-UXHgK1VKpdw.png" /></figure><p><strong>Step 5</strong></p><p>You can see that the Virtual Machine as a IP address from within our Cluster IP space (10.42.1.0/24), we can use <strong>virtctl</strong> to connect to that IP over ssh from the Control Plane Node (or you can port forward through as well).</p><pre>virtctl ssh cirros-vm -n kubevirt-lab -l cirros</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yn8GglMQ7C8JURdo1AXwpA.png" /></figure><p>Or you can go direct (if you are on a K8s Node) with ssh</p><pre># ssh cirros@10.42.1.74</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EqheVgo8Xifgm1Rt7mRD6A.png" /></figure><h3>What have we learned?</h3><p>We have setup a simple 3-node K8s cluster with KubeVirt on it and then using the KubeVirt CRDs, we declared a Virtual Machine and a backing DataStore that contained our QCOW Cirrors image and validated that we were able to get a basic virtual machine to start and its networking stack to come up.</p><p>With those simple steps, you can now port virtually (tee hee) any VM into your K8s environment — of course, you should ensure you test in appropriate environments before moving production workloads! :)</p><h3>Where to go next?</h3><p>Of course, this is just the starting point, things like adding LoadBalancers to sets of VMs, how to do rolling updates and wiring up your Flux/ArgoCD/GitActions pipelines and start pushing out your VMs.</p><p>I hope you enjoyed this article and the examples, i think that you will find that the integration that KubeVirt provides makes deploying Virtual Machines via K8s pretty much like deploying anything else in K8s, in a declarative manner!</p><p>Till next time!</p><p>Daniel</p><p>Check out this and other stuff at <a href="https://dasmlab.org/">https://dasmlab.org</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ad3eba255bb" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>