<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Apple Silicon on Qstars IT Blog - Cloud, Kubernetes &amp; Software Engineering</title>
        <link>https://blog.qstars.nl/tags/apple-silicon/</link>
        <description>Recent content in Apple Silicon on Qstars IT Blog - Cloud, Kubernetes &amp; Software Engineering</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Fri, 11 Jul 2025 09:38:00 +0200</lastBuildDate><atom:link href="https://blog.qstars.nl/tags/apple-silicon/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Local Kubernetes on Mac: a Multi-Node Cluster with UTM</title>
        <link>https://blog.qstars.nl/posts/macos-local-kubernetes-cluster-with-utm/</link>
        <pubDate>Fri, 11 Jul 2025 09:38:00 +0200</pubDate>
        
        <guid>https://blog.qstars.nl/posts/macos-local-kubernetes-cluster-with-utm/</guid>
        <description>&lt;img src="https://blog.qstars.nl/posts/macos-local-kubernetes-cluster-with-utm/macos-k8s.jpg" alt="Featured image of post Local Kubernetes on Mac: a Multi-Node Cluster with UTM" /&gt;&lt;blockquote&gt;
&lt;p&gt;Today we&amp;rsquo;re going to configure a Kubernetes cluster using virtual machines on our MacOS host. We are going to use UTM which utilises QEMU, a type-2 hypervisor to actually virtualise the new machines. I will be using a 2024 Macbook Pro with the Apple Silicon M4 chip, this guide can also be followed on older M-chips or Intel CPU&amp;rsquo;s.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;virtual-machines-vs-dockerized-kubernetes&#34;&gt;Virtual Machines vs Dockerized Kubernetes
&lt;/h2&gt;&lt;p&gt;There is an abundance of easy to use tools out there to quickly spin up a local Kubernetes cluster. You may have heard of &lt;a class=&#34;link&#34; href=&#34;https://minikube.sigs.k8s.io/docs/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Minikube&lt;/a&gt;, &lt;a class=&#34;link&#34; href=&#34;https://kind.sigs.k8s.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;KinD&lt;/a&gt; (Kubernetes-in-Docker) or &lt;a class=&#34;link&#34; href=&#34;https://k3s.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;K3S&lt;/a&gt;. Running Kubernetes inside actual VMs simulates a more realistic production environment. Each VM has their own kernel and networking stack, this doesn&amp;rsquo;t just offer a higher level of protection to your host machine it also allows us tighter controls over the OS &amp;amp; Kernel.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to be installing the Kubernetes (&lt;code&gt;v1.32&lt;/code&gt;*) cluster using &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/docs/reference/setup-tools/kubeadm/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;kubeadm&lt;/a&gt;, meaning that all of our control plane components are going to be running as &lt;a class=&#34;link&#34; href=&#34;https://kubernetes.io/docs/concepts/workloads/pods/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Pods&lt;/a&gt; on the cluster (in the &lt;code&gt;kube-system&lt;/code&gt; namespace, more about that later).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*At the time of writing, &lt;code&gt;v1.32&lt;/code&gt; is the latest minor release. Other minor versions can be installed following this guide, you just need to swap out the version numbers where specified. &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/releases/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Learn more&lt;/a&gt; about Kubernetes versioning.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites
&lt;/h2&gt;&lt;p&gt;Don&amp;rsquo;t know your CPU architecture? Run &lt;code&gt;uname -m&lt;/code&gt; in a terminal of your choice.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Got an Apple Silicon M-chip (&lt;code&gt;arm64&lt;/code&gt;)? Use &lt;code&gt;aarch64&lt;/code&gt; where there is no specific &lt;code&gt;arm64&lt;/code&gt; download available.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;A MacOS machine (Intel or Apple Silicon)&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://mac.getutm.app/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;UTM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://ubuntu.com/download/server&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Ubuntu Server&lt;/a&gt; &amp;amp; &lt;a class=&#34;link&#34; href=&#34;https://alpinelinux.org/downloads/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Alpine Linux Standard&lt;/a&gt; ISO files matching your cpu architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation
&lt;/h2&gt;&lt;p&gt;This section will cover the installation of three virtual machine types. The &lt;strong&gt;Router&lt;/strong&gt; will provide internet &amp;amp; inter-node connectivity to our Kubernetes cluster, using a router like this will allow us to connect to any internet device without the inter-node network being disrupted (i.e by changing IP addresses or firewall rules).&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;control plane&lt;/strong&gt; and &lt;strong&gt;worker&lt;/strong&gt; node(s) are standard Kubernetes components, in this guide we&amp;rsquo;ll install one worker and one dedicated control plane node. Repeat the installation steps to add more &lt;strong&gt;control plane&lt;/strong&gt; or &lt;strong&gt;worker&lt;/strong&gt; nodes to your cluster for &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;high-avalability&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;❗The amount of RAM (memory) is expressed in &lt;a class=&#34;link&#34; href=&#34;https://nl.wikipedia.org/wiki/Mebibyte&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Mebibytes&lt;/a&gt;. The amount of processor cores refer to &lt;strong&gt;&lt;em&gt;virtual&lt;/em&gt;&lt;/strong&gt; CPU cores assigned to the VM. On Apple Silicon, these are distributed across &lt;a class=&#34;link&#34; href=&#34;https://eclecticlight.co/2024/02/19/apple-silicon-1-cores-clusters-and-performance&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;efficiency and performance cores&lt;/a&gt;, while on Intel Macs, they may correspond to physical or logical &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Hyper-threading&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;hyperthreaded&lt;/a&gt; cores.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;router&#34;&gt;Router
&lt;/h3&gt;&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/th&gt;
          &lt;th&gt;Value&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Operating system&lt;/td&gt;
          &lt;td&gt;Alpine Standard 3.21.3+&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Memory&lt;/td&gt;
          &lt;td&gt;512 Mebibyte&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Processor cores&lt;/td&gt;
          &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Disk size&lt;/td&gt;
          &lt;td&gt;10 Gigabytes&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Let&amp;rsquo;s get this party started by setting up our Router VM. Press the + icon in UTM and select &lt;strong&gt;Virtualize&lt;/strong&gt;. On the next screen, select &lt;strong&gt;Linux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/7Il6VE4.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Getting started&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;On the Linux virtualisation page we are going to leave &lt;code&gt;Apple Virtualization&lt;/code&gt; &amp;amp; &lt;code&gt;Boot from kernal image&lt;/code&gt; off. Press &lt;code&gt;Browse...&lt;/code&gt; and select the Alpine ISO we have just downloaded.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/fIUCIsh.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Linux virtualization engine&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Fill in the memory, CPU core requirements &amp;amp; storage as per the values above. Leave OpenGL acceleration turned off. We are going to skip the &lt;strong&gt;Shared Directory&lt;/strong&gt; page.&lt;/p&gt;
&lt;p&gt;Now you should be greeted with the &lt;strong&gt;Summary&lt;/strong&gt; page, here you can review what we&amp;rsquo;ve just configured before spinning up the vm. Give it a name of your liking and press &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/0xpEuBn.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Summary Page&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Before we can continue installing Alpine on the machine we have to add a &lt;strong&gt;Serial&lt;/strong&gt; (terminal) output to the vm. Right click on the machine in the lefthand overview and press &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Head over to the &lt;strong&gt;Devices&lt;/strong&gt; tab, we are going to remove the primary Display that&amp;rsquo;s configured for the machine and instead add a &lt;strong&gt;Serial&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/6HcLux6.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Router devices configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re ready to install Alpine Linux! Hit &lt;strong&gt;Save&lt;/strong&gt; &amp;amp; press the play button to spin up our virtual machine. After waiting for some seconds you&amp;rsquo;ll be greeted with a login screen, login with the &lt;code&gt;root&lt;/code&gt; username and no password.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/M7SVK4U.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Alpine Linux login screen&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;We can begin the installation process by running the &lt;code&gt;setup-alpine&lt;/code&gt; command in the terminal. We&amp;rsquo;ll be prompted by the setup wizard to configure a hostname, use a name that you&amp;rsquo;ll be able to identify the machine with, I choose &lt;code&gt;router&lt;/code&gt;. For the &lt;strong&gt;Interface&lt;/strong&gt; (networking) configuration, you can simply press enter. We want to configure the default interface to be assigned an IP address from our internet modem using &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;DHCP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/SDF7mct.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Alpine initial installation configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Next, enter a memorable password for the &lt;code&gt;root&lt;/code&gt; user. This is the most privileged user on the machine and it can perform any action, make it secure! After filling in your password you&amp;rsquo;ll be asked what timezone you&amp;rsquo;re from. From this part onwards, you can safely use the default values for sections &lt;strong&gt;Proxy&lt;/strong&gt;, &lt;strong&gt;Network Time Protocol&lt;/strong&gt;, &lt;strong&gt;APK Mirror&lt;/strong&gt; and &lt;strong&gt;User&lt;/strong&gt;. This will leave us to configure the system disk.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/C6jFkZX.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Alpine Linux disk configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Wait for the installer to finish and then exit out of the vm. We are going to go back into the settings for the vm by right clicking on it in our overview and pressing &lt;strong&gt;Edit&lt;/strong&gt; (if the vm isn&amp;rsquo;t stopped yet, press &lt;strong&gt;Stop&lt;/strong&gt; first to make sure it&amp;rsquo;s turned off!). Now, under the &lt;strong&gt;Drives&lt;/strong&gt; section, select the &lt;strong&gt;USB Drive&lt;/strong&gt; and delete it, this will make sure the installation image is no longer mounted when starting the machine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/sDEtVi9.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;USB Drive configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Make sure to save your changes! You&amp;rsquo;ve now installed Alpine Linux on a virtual machine. Turn on the vm and login with your previously configured password for the &lt;code&gt;root&lt;/code&gt; user. We&amp;rsquo;re going to update our virtual machine, but before we can do that we have to make sure we have the right &lt;a class=&#34;link&#34; href=&#34;https://wiki.alpinelinux.org/wiki/Repositories&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;repositories&lt;/a&gt; set.&lt;/p&gt;
&lt;p&gt;In the terminal, run the following command: &lt;code&gt;cat /etc/apk/repositories&lt;/code&gt;
&lt;img src=&#34;https://i.imgur.com/hiD1Mma.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Repositories list&#34;
	
	
&gt;
It&amp;rsquo;s important to set the official Alpine &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;community&lt;/code&gt; repositories as seen above. If you do &lt;strong&gt;not&lt;/strong&gt; see these two repositories, you can add them with the command below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; -e &lt;span class=&#34;s1&#34;&gt;&amp;#39;http://dl-cdn.alpinelinux.org/alpine/v3.21/main\nhttp://dl-cdn.alpinelinux.org/alpine/v3.21/community&amp;#39;&lt;/span&gt; &amp;gt; /etc/apk/repositories
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, we&amp;rsquo;re ready to update our system. Run the &lt;code&gt;apk update &amp;amp;&amp;amp; apk upgrade -y&lt;/code&gt; command and wait for it to complete. We&amp;rsquo;re going to have to completely turn off the vm one last time to edit the network configurations. Our goal is to have our own isolated inter-node Kubernetes network that also has a gateway to the internet.&lt;/p&gt;
&lt;p&gt;To achieve this, we&amp;rsquo;ll have to configure two network devices for our Router. A Kubernetes &lt;strong&gt;Host-only&lt;/strong&gt; network device and one that&amp;rsquo;s set to the &lt;strong&gt;Bridge&lt;/strong&gt; mode. This will allow our Kubernetes nodes to direct their internet requests via the router. Let&amp;rsquo;s dig into the machine configurations first.&lt;/p&gt;
&lt;p&gt;Under &lt;strong&gt;Devices&lt;/strong&gt; you&amp;rsquo;ll see a &lt;strong&gt;Network&lt;/strong&gt; device. Select it and simply change the &lt;strong&gt;Network Mode&lt;/strong&gt; from &lt;em&gt;Shared Network&lt;/em&gt; to &lt;em&gt;Bridged (Advanced)&lt;/em&gt;. Select &lt;em&gt;en0&lt;/em&gt; as the &lt;strong&gt;Bridged Interface&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/WGvysOR.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Bridged network device configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Add a new Network device and set it&amp;rsquo;s &lt;strong&gt;Network Mode&lt;/strong&gt; to &lt;strong&gt;Host-Only&lt;/strong&gt;. Save your configurations and spin up the machine. After you&amp;rsquo;ve authenticated in the vm, run the &lt;code&gt;ip a&lt;/code&gt; command to list the network devices.
&lt;img src=&#34;https://i.imgur.com/DimX52U.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Network device output&#34;
	
	
&gt;
We can ignore the &lt;code&gt;lo&lt;/code&gt; device, it&amp;rsquo;s irrelevant to us. The &lt;code&gt;eth0&lt;/code&gt; network device already has ip address &lt;code&gt;192.168.0.171&lt;/code&gt; assigned, this is an ip address in my local home network. It&amp;rsquo;s time for us to install some required networking related packages to turn this machine into a router.&lt;/p&gt;
&lt;p&gt;Run the following command to start installing and configuring the necessary packages, and wait for the machine to reboot.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;apk add iptables &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/etc/init.d/iptables save &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rc-update add iptables &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;net.ipv4.ip_forward = 1&amp;#39;&lt;/span&gt; &amp;gt; /etc/sysctl.d/router.conf &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re almost done with the router now. All we need to do is configure the host-only device and give it a static IP in our network. I&amp;rsquo;m going to go with the &lt;code&gt;192.168.100.0/24&lt;/code&gt; subnet, where &lt;code&gt;192.168.100.1&lt;/code&gt; will be our router, and we will increment per Kubernetes node.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s check in on our current network interfaces configuration. Run the &lt;code&gt;cat /etc/network/interfaces&lt;/code&gt; command.
&lt;img src=&#34;https://i.imgur.com/Y8RfQIq.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Network interfaces&#34;
	
	
&gt;
As you can see, two of our network devices have default configurations to request IP addresses. The &lt;code&gt;lo&lt;/code&gt;opback interface lets the system communicate with itself using IP address &lt;code&gt;127.0.0.1&lt;/code&gt;, it allows the system to talk to itself without needing a physical network device.&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Bridged mode&lt;/strong&gt;, the &lt;code&gt;eth0&lt;/code&gt; interface acts like a physical device directly connected to your home network. This means it gets an IP address from your home router’s &lt;strong&gt;DHCP server&lt;/strong&gt;, just like any other device on your network (e.g., your laptop or phone).&lt;/p&gt;
&lt;p&gt;As you can see, there is no entry for the &lt;code&gt;eth1&lt;/code&gt; interface yet. This is the also the reason why we didn&amp;rsquo;t see an IP address assigned to the &lt;code&gt;eth1&lt;/code&gt; interface previously. Let&amp;rsquo;s set it up!&lt;/p&gt;
&lt;p&gt;Run the following command in your terminal to set default configurations for &lt;code&gt;eth1&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;auto eth1\niface eth1 inet static\n\taddress 192.168.100.1\n\tnetmask 255.255.255.0\n&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/network/interfaces
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Verify that our &lt;code&gt;eth1&lt;/code&gt; entry is now added to &lt;code&gt;/etc/network/interfaces&lt;/code&gt;
&lt;img src=&#34;https://i.imgur.com/mPCTHEm.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Interfaces file with eth1 setup&#34;
	
	
&gt;
Apply the configuration by running &lt;code&gt;rc-service networking restart&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/wfJNF8h.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;rc-service network restart&#34;
	
	
&gt;
Voilà! You now have an Alpine Linux router virtual machine. Everything has been set up as it should, to verify our &lt;code&gt;eth1&lt;/code&gt; interface now has a static ip address of &lt;code&gt;192.168.100.1&lt;/code&gt;&lt;em&gt;/24&lt;/em&gt;, run the &lt;code&gt;ip a&lt;/code&gt; command one last time.
&lt;img src=&#34;https://i.imgur.com/RLJpcbc.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ip address overview&#34;
	
	
&gt;
To quickly recap: We&amp;rsquo;ve just installed Alpine Linux on a virtual machine, configured the box to allow the forwarding of network packets from our &lt;strong&gt;Host-Only&lt;/strong&gt; network to our &lt;strong&gt;Bridged&lt;/strong&gt; network and ultimately the internet.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ Make sure you always have the router running alongside your Kubernetes control plane nodes. The machines that make up your Kubernetes cluster live inside the &lt;strong&gt;Host-Only&lt;/strong&gt; network on your Mac, allowing them to talk to eachother but not your home router for internet access. If you notice an issue with internet connectivity, restarting the router is a great troubleshooting starting point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s time for us to move on to the first Kubernetes node.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🛜 Switching Wi-Fi networks (e.g Office to Home)? Either run &lt;code&gt;rc-service networking restart&lt;/code&gt; or reboot the router to make sure it&amp;rsquo;s assigned a new IP and connected to the new router.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;control-plane&#34;&gt;Control plane
&lt;/h3&gt;&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/th&gt;
          &lt;th&gt;Value&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Operating system&lt;/td&gt;
          &lt;td&gt;Ubuntu Server 24.04+&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Memory&lt;/td&gt;
          &lt;td&gt;2048 Mebibyte&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Processor cores&lt;/td&gt;
          &lt;td&gt;2&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Disk size&lt;/td&gt;
          &lt;td&gt;30 Gigabytes&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In Kubernetes, a node refers to a machine running a Kubernetes distribution. You have control plane and worker nodes, we&amp;rsquo;re going to start off with the control plane as it&amp;rsquo;s the brains of the operation and requires the most installation efforts of the two.&lt;/p&gt;
&lt;p&gt;Start by repeating the three first steps to setting up a new virtual machine. Give it a name, select the operating system installation image (be sure to select Ubuntu!) and set the CPU, memory and disk specifications as outlined above.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/CqymX29.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu control plane configuration overview&#34;
	
	
&gt;
Toggle the &lt;strong&gt;Open VM Settings&lt;/strong&gt; checkbox and hit &lt;strong&gt;Save&lt;/strong&gt;. In the machine settings, swap out the &lt;strong&gt;Display&lt;/strong&gt; for a &lt;strong&gt;Serial&lt;/strong&gt; again, and set the &lt;strong&gt;Network&lt;/strong&gt; device to &lt;strong&gt;Host-Only&lt;/strong&gt; mode. Save your settings and spin up the control plane.&lt;/p&gt;
&lt;p&gt;At first you will be greeted with some fairly standard installation options, continue the installation in &lt;strong&gt;rich mode&lt;/strong&gt; before selecting your language and geographical location.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/Sxq0EIn.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu installer&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;When asked what base you&amp;rsquo;d like to use, keep the default &lt;strong&gt;Ubuntu Server&lt;/strong&gt; base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/EgEqyIX.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu base installation selection&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;The next screen is important, it&amp;rsquo;s the network configuration for our control plane. Select the network device, it should only be one as we configured the default device to sit in the &lt;strong&gt;Host-Only&lt;/strong&gt; network. In the new dropdown box select &lt;strong&gt;Edit IPv4&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/kPy1RyA.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu network configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Set the &lt;strong&gt;IPv4 Method&lt;/strong&gt; to &lt;strong&gt;Manual&lt;/strong&gt; and copy the values you see below. This will assign the control plane IP address &lt;code&gt;192.168.100.2&lt;/code&gt; and use our router &lt;code&gt;192.168.100.1&lt;/code&gt; as it&amp;rsquo;s &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Gateway_address&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;gateway address&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/YYfbVXZ.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu ipv4 configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Before hitting &lt;strong&gt;Save&lt;/strong&gt;, in the &lt;code&gt;Nameservers&lt;/code&gt; tab, fill in &lt;code&gt;1.1.1.1&lt;/code&gt;. Now you can save the network configuration! For the next three steps up until &lt;strong&gt;Profile configuration&lt;/strong&gt; you can safely use the default values.&lt;/p&gt;
&lt;p&gt;In the profile configuration, set the machine&amp;rsquo;s name to &lt;strong&gt;controlplane&lt;/strong&gt;. Fill in your own username for the user account along with a secure password.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/IZH5bG7.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu profile configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;The next pages can safely be skipped using the default configurations. Wait for the system installer to complete before &lt;strong&gt;fully turning off the machine&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/vptXlnm.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu system installer progress&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Once the vm is fully turned off, edit it and remove the &lt;strong&gt;USB Drive&lt;/strong&gt; from it&amp;rsquo;s attached &lt;strong&gt;Devices&lt;/strong&gt; again. Afterwards turn on the control plane again and login using your previously chosen username and password.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to need access to the entire system while installing Kubeadm. To get privileged access to the machine, switch to the &lt;code&gt;root&lt;/code&gt; user by running &lt;code&gt;sudo su -&lt;/code&gt; and typing in your password.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s test our internet connectivity by saying hello to google.com using a &lt;code&gt;ping&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/Y0ZcHVf.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Google ping test&#34;
	
	
&gt;
Beautiful! Our control plane machine was able to find &lt;code&gt;google.com&lt;/code&gt; on the internet when the machine itself doesn&amp;rsquo;t have an internet connection. The control plane uses the router to access the internet. To test this, fully shutdown the router and run the same &lt;code&gt;ping&lt;/code&gt; command again.
&lt;img src=&#34;https://i.imgur.com/DYWMW2F.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ping command without internet&#34;
	
	
&gt;
As you can see, the machine cannot even resolve an ip address for &lt;code&gt;google.com&lt;/code&gt;, this is because the nameserver we have configured is Cloudflare&amp;rsquo;s &lt;code&gt;1.1.1.1&lt;/code&gt;, in order for us to resolve a new domain name we need an internet connection in order to do so.&lt;/p&gt;
&lt;p&gt;With the network sorted we can pivot our attention to setting up the node for Kubernetes. Run the following command to set up the required system settings &amp;amp; kernel modules.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo swapoff -a &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;/\/swap.img/ s/^/#/&amp;#39;&lt;/span&gt; /etc/fstab &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl mask swap.img.swap &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sysctl -w net.ipv4.ip_forward&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;net.ipv4.ip_forward=1&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee -a /etc/sysctl.conf &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sysctl --system &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo modprobe overlay &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo modprobe br_netfilter
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve enabled &lt;strong&gt;IPv4 Forwarding&lt;/strong&gt;, the &lt;strong&gt;overlay&lt;/strong&gt; &amp;amp; &lt;strong&gt;br_netfilter&lt;/strong&gt; kernel modules and disabled memory &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Memory_paging&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;swapping&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not all though, we need to install a &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/docs/concepts/architecture/cri/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Container Runtime Interface&lt;/a&gt; before we can bootstrap a Kubernetes cluster on the machine. We&amp;rsquo;ll use &lt;a class=&#34;link&#34; href=&#34;https://containerd.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;containerd&lt;/a&gt;, you can install containerd from source or using a package repository. We&amp;rsquo;re going to use the latter.&lt;/p&gt;
&lt;p&gt;The containerd team does not publish their updates to package repositories directly, this is done by the Docker team. Docker uses containerd internally and they also publish the apt packages. This means that we&amp;rsquo;re going to have to add the Docker apt repository as a source for our machine to find packages from.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install -y apt-transport-https ca-certificates curl gpg &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo install -m &lt;span class=&#34;m&#34;&gt;0755&lt;/span&gt; -d /etc/apt/keyrings &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod a+r /etc/apt/keyrings/docker.asc &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;deb [arch=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;dpkg --print-architecture&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;. /etc/os-release &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;UBUNTU_CODENAME&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;:-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; stable&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That was a long command! It pulled Docker&amp;rsquo;s GPG key that&amp;rsquo;ll be used to check if packages we download &lt;em&gt;really&lt;/em&gt; come from Docker, afterwards we update the available package list with an &lt;code&gt;apt-get update&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now the &lt;code&gt;containerd.io&lt;/code&gt; package should be available for us to be installed, run &lt;code&gt;apt install containerd.io&lt;/code&gt; to install it!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/qlHh5IX.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Containerd apt installation&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Verify that the installation went successful by running.&lt;code&gt;systemctl status containerd&lt;/code&gt;, you should see the service is in an &lt;strong&gt;active (running)&lt;/strong&gt; state.
&lt;img src=&#34;https://i.imgur.com/cBZGmGS.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Containerd system service status&#34;
	
	
&gt;
&lt;em&gt;Do you see our previously installed kernel driver being executed? &lt;code&gt;ExecStartPre=/sbin/modprobe&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Run the following commands to generate and tweak the default containerd configuration.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;containerd config default &amp;gt; /etc/containerd/config.toml &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/SystemdCgroup = false/SystemdCgroup = true/&amp;#39;&lt;/span&gt; /etc/containerd/config.toml &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl restart containerd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;With our CRI installed we&amp;rsquo;ve finally completed the necessary prerequisites to transform this vm into a Kubernetes control plane, so let&amp;rsquo;s do that! Just like with the Docker repository previously, we have to download the official Kubernetes apt repository GPG key to verify we are installing the legitimate packages. Run the following command to do so;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo gpg --dearmor -o /etc/apt/keyrings/Kubernetes-apt-keyring.gpg &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;deb [signed-by=/etc/apt/keyrings/Kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee /etc/apt/sources.list.d/Kubernetes.list &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve now updated apt to also look for packages inside the Kubernetes repository. To check what packages and their versions are available to us, run &lt;code&gt;apt-cache madison &amp;lt;package&amp;gt;&lt;/code&gt;. Where &lt;code&gt;&amp;lt;package&amp;gt;&lt;/code&gt; can either be &lt;code&gt;kubeadm&lt;/code&gt;, &lt;code&gt;kubelet&lt;/code&gt; or &lt;code&gt;kubectl&lt;/code&gt; (we&amp;rsquo;re going to be installing all three!).
&lt;img src=&#34;https://i.imgur.com/mrojCco.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;All available kubeadm versions&#34;
	
	
&gt;
&lt;img src=&#34;https://i.imgur.com/kuJHjg2.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;All available kubelet versions&#34;
	
	
&gt;
These packages follow the &lt;a class=&#34;link&#34; href=&#34;https://semver.org/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Semantic Versioning&lt;/a&gt; standard. We have three packages with major release &lt;code&gt;1&lt;/code&gt;, minor release &lt;code&gt;32&lt;/code&gt; with varying patch versions &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt;. We aren&amp;rsquo;t too worried about the specific patches, let&amp;rsquo;s just go with the latest &lt;code&gt;1.32.2-1.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Run the &lt;code&gt;sudo apt install kubelet kubeadm kubectl -y&lt;/code&gt; command to install the required Kubernetes binaries. Afterwards, pin their versions to make sure you don&amp;rsquo;t accidentally upgrade any of them using &lt;code&gt;sudo apt-mark hold kubelet kubeadm kubectl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(Optional) Let&amp;rsquo;s quickly verify the versions of the installed components directly.
&lt;img src=&#34;https://i.imgur.com/wRrZdVc.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubeadm version command&#34;
	
	
&gt;&lt;img src=&#34;https://i.imgur.com/JQGSlLJ.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubelet version command&#34;
	
	
&gt;
&lt;img src=&#34;https://i.imgur.com/DM0kjr5.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubectl version command&#34;
	
	
&gt;
Hm&amp;hellip; the &lt;strong&gt;kubectl&lt;/strong&gt; command shows us two versions and a connection error. Thankfully we see that the &lt;strong&gt;Client Version&lt;/strong&gt; is set to our target version. The client version refers to the version of &lt;strong&gt;kubectl&lt;/strong&gt; we have installed, which as per our apt command earlier is &lt;code&gt;1.32.2&lt;/code&gt;. &lt;a class=&#34;link&#34; href=&#34;https://kustomize.io/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Kustomize&lt;/a&gt; is a Kubernetes configuration manager which allows you to update manifest files &lt;strong&gt;&lt;em&gt;without&lt;/em&gt;&lt;/strong&gt; a templating language like &lt;a class=&#34;link&#34; href=&#34;https://helm.sh/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Helm&lt;/a&gt;&amp;rsquo;s chart templates. This isn&amp;rsquo;t relevant to our installation however, so we can safely ignore the Kustomize version.&lt;/p&gt;
&lt;p&gt;That leaves us with a connection refused. &lt;strong&gt;Kubectl&lt;/strong&gt; automatically tries to connect to the &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;kube-apiserver&lt;/a&gt; on &lt;code&gt;localhost:8080&lt;/code&gt; if no alternative destination is configured. In our case this fails because we haven&amp;rsquo;t got the &lt;a class=&#34;link&#34; href=&#34;https://Kubernetes.io/docs/concepts/overview/components/#control-plane-components&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;control plane components&lt;/a&gt; running yet.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s finally time for us to bootstrap a Kubernetes cluster using kubeadm. Let me introduce you to two very important initialization flags. The &lt;code&gt;--service-cidr&lt;/code&gt; flag lets you set a subnet that Kubernetes will use for all of its services. The &lt;code&gt;--pod-network-cidr&lt;/code&gt; lets you specify a subnet that will be used to assign IP addresses to pods.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s important that both of these subnets are not being used on your network! Thankfully, our Kubernetes cluster lives inside our Mac&amp;rsquo;s &lt;strong&gt;Host-Only&lt;/strong&gt; network and does not automatically have access to a wider network, limiting the chance for a collision, however it&amp;rsquo;s still a good idea to decide on a dedicated subnet. I am going to use the default service cidr, and I&amp;rsquo;m going to specify &lt;code&gt;192.168.0.0/16&lt;/code&gt; as the pod network cidr.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🛜 Be careful and don&amp;rsquo;t randomly assign a subnet to your cluster! It&amp;rsquo;s best to use a private subnet as your pod cidr, as per &lt;a class=&#34;link&#34; href=&#34;https://en.wikipedia.org/wiki/Private_network&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;RFC 1918&lt;/a&gt; &lt;code&gt;192.168.0.0/16&lt;/code&gt; is a private subnet. &lt;code&gt;10.96.0.0/12&lt;/code&gt; (default service cidr) is &lt;em&gt;technically&lt;/em&gt; not private, but practically it is so we&amp;rsquo;re fine with keeping the default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That leaves us to run the &lt;code&gt;kubeadm init --pod-network-cidr=192.168.0.0/16&lt;/code&gt; command to get our cluster started.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Having issues getting &lt;code&gt;kubeadm init&lt;/code&gt; to work? Check if &lt;strong&gt;cri&lt;/strong&gt; isn&amp;rsquo;t a disabled plugin in containerd, run &lt;code&gt;cat /etc/containerd/config.toml | grep disabled_plugins&lt;/code&gt;. If you see &lt;code&gt;disabled_plugins = [&amp;quot;cri&amp;quot;]&lt;/code&gt;, scroll down to the &lt;strong&gt;Troubleshooting &amp;gt; Containerd CRI Disabled Plugin&lt;/strong&gt; section of this post before retrying the init command and continuing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If all is well, your terminal output should look something like this;
&lt;img src=&#34;https://i.imgur.com/4uarqT5.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubeadm init command stdout&#34;
	
	
&gt;
That&amp;rsquo;s a lot of information, let&amp;rsquo;s laser in on what we actually need.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/RZ6Zpn8.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubeadm init thankyou output&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Our main area of concern here is the long &lt;code&gt;kubeadm join&lt;/code&gt; command at the very bottom, save this in a note. As you can see, it points to our control plane machine &lt;code&gt;192.168.100.2&lt;/code&gt; at port &lt;code&gt;6443&lt;/code&gt;. We haven&amp;rsquo;t explicitly opened a port, or a ran an application that binds to that port ourselves&amp;hellip; Run the &lt;code&gt;export KUBECONFIG=/etc/kubernetes/admin.conf&lt;/code&gt; command followed by &lt;code&gt;kubectl get pods -A&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/ku6CkP0.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes system pods&#34;
	
	
&gt;
We just ran the command to list all pods in every namespace. As we&amp;rsquo;ve just bootstrapped this cluster using kubeadm, our control plane components are ran as pods inside the &lt;code&gt;kube-system&lt;/code&gt; namespace. As you can see, the &lt;code&gt;coredns&lt;/code&gt; pods are in a &lt;strong&gt;Pending&lt;/strong&gt; state. Let&amp;rsquo;s have a look at how our control plane node is doing.
&lt;img src=&#34;https://i.imgur.com/WwR1sMk.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes node list&#34;
	
	
&gt;
Our control plane node has a status of &lt;strong&gt;Not Ready&lt;/strong&gt;, this is because CoreDNS is not running. Our CoreDNS pods will stay in this pending state until we have installed a &lt;a class=&#34;link&#34; href=&#34;https://github.com/containernetworking/cni&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Container Networking Interface&lt;/a&gt;. There&amp;rsquo;s a couple of options to choose from such as; &lt;a class=&#34;link&#34; href=&#34;https://github.com/flannel-io/flannel&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Flannel&lt;/a&gt;, &lt;a class=&#34;link&#34; href=&#34;https://github.com/projectcalico/calico&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Calico&lt;/a&gt; and &lt;a class=&#34;link&#34; href=&#34;https://github.com/cilium/cilium&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cilium&lt;/a&gt;. Pick whichever one you prefer, I&amp;rsquo;m going to pick Cilium.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ The Flannel CNI is only a layer 3 networking solution for Kubernetes. Many Kubernetes features such as network policies are not supported and do not take effect when using Flannel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Though we can install Cilium using Kubernetes manifest files, they have made it easy for us to get everything set-up with their own installer. Let&amp;rsquo;s install the Cilium CLI.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Preparing for the CKS exam? Cilium is the CNI used on the exam, by installing Cilium in your local cluster you can fully utilise it&amp;rsquo;s features such as the &lt;a class=&#34;link&#34; href=&#34;https://cilium.io/blog/2020/11/10/ebpf-future-of-networking/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;eBPF&lt;/a&gt; based network stack, &lt;a class=&#34;link&#34; href=&#34;https://docs.cilium.io/en/latest/security/policy/index.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Cilium Network Policies&lt;/a&gt; (layer 3, 4 &amp;amp; 7) and &lt;a class=&#34;link&#34; href=&#34;https://docs.cilium.io/en/latest/network/servicemesh/mutual-authentication/mutual-authentication/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Mutual Authentication&lt;/a&gt; (mTLS). Keep your eyes on the &lt;a class=&#34;link&#34; href=&#34;https://github.com/cncf/curriculum/tree/master&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;exam curriculum&lt;/a&gt; to stay up to date.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CILIUM_CLI_VERSION&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;amd64
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;uname -m&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;aarch64&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;arm64&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;CILIUM_CLI_VERSION&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;/cilium-linux-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;.tar.gz&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;,.sha256sum&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sha256sum --check cilium-linux-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;.tar.gz.sha256sum
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo tar xzvfC cilium-linux-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;.tar.gz /usr/local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rm cilium-linux-&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;CLI_ARCH&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;.tar.gz&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;,.sha256sum&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We now have access to the command &lt;code&gt;cilium&lt;/code&gt;. We can install Cilium onto our cluster using the &lt;code&gt;cilium install&lt;/code&gt; command. I am going to install it with the SPIRE server enabled which is required for mTLS.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cilium install &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;--set authentication.mutual.spire.enabled&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;--set authentication.mutual.spire.install.enabled&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/SP7yDFH.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Cilium install command&#34;
	
	
&gt;
So far so good, let&amp;rsquo;s check on our Cilium installation with the &lt;code&gt;cilium status&lt;/code&gt; command.
&lt;img src=&#34;https://i.imgur.com/QBQ5oc6.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Cilium status&#34;
	
	
&gt;
Everything works! We can verify this one more time by running &lt;code&gt;kubectl get pods -A&lt;/code&gt; again.
&lt;img src=&#34;https://i.imgur.com/NmRLB7r.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes pod list with cilium&#34;
	
	
&gt;
Look at that! Cilium is installed and is running it&amp;rsquo;s pods, our CoreDNS pods are now also in a &lt;strong&gt;Running&lt;/strong&gt; state. Let&amp;rsquo;s check on our node again to see if it&amp;rsquo;s changed it&amp;rsquo;s status using &lt;code&gt;kubectl get nodes&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/haxLc3w.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Control plane ready status&#34;
	
	
&gt;
Perfect! Our cluster is up and running! Remember that &lt;code&gt;kubectl version&lt;/code&gt; command that was giving us the connection error from before?
&lt;img src=&#34;https://i.imgur.com/mC3sRmP.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubectl version command working as intended&#34;
	
	
&gt;
No more connection issues, we now have a working kube-apiserver to handle the request. However, in it&amp;rsquo;s current state our cluster isn&amp;rsquo;t going to run any of our applications just yet.
&lt;img src=&#34;https://i.imgur.com/2itQIQ3.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubectl create nginx pod&#34;
	
	
&gt;
&lt;img src=&#34;https://i.imgur.com/TKwPtdX.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubectl nginx pod pending&#34;
	
	
&gt;
Our pod is stuck in a &lt;strong&gt;Pending&lt;/strong&gt; state. This is because our control plane machine has a &lt;a class=&#34;link&#34; href=&#34;https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;taint&lt;/a&gt; on it that prevents us to accidentally schedule a workload on the control plane. It&amp;rsquo;s a best practice to keep your control plane isolated from any of your other applications, both for cluster security and to ensure your control plane has enough resources to function. Control planes come with the &lt;code&gt;node-role.kubernetes.io/control-plane&lt;/code&gt; taint which has a &lt;code&gt;NoSchedule&lt;/code&gt; effect.
&lt;img src=&#34;https://i.imgur.com/vtfQ9Dq.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Node taint&#34;
	
	
&gt;
If we really wanted to, we can remove this taint and all of our applications are able to be scheduled freely on the control plane. You can also choose to control what applications can run on the control plane by giving it a toleration.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to follow the best practice and leave the control plane as is, it&amp;rsquo;s time to set up a worker machine.&lt;/p&gt;
&lt;h3 id=&#34;workers&#34;&gt;Worker(s)
&lt;/h3&gt;&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;&lt;strong&gt;Resource&lt;/strong&gt;&lt;/th&gt;
          &lt;th&gt;Value&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Operating system&lt;/td&gt;
          &lt;td&gt;Ubuntu Server 24.04+&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Memory&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;1024&lt;/strong&gt; - 4096 Mebibyte&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Processor cores&lt;/td&gt;
          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt; - 4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Disk size&lt;/td&gt;
          &lt;td&gt;30+ Gigabytes&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Our worker nodes are going to be the ones actually running our applications. Usually your worker nodes are bigger and more powerful than your control plane node, as in my case I am not going to be hosting any large applications on my cluster I have equipped my worker node with the minimal needed system resources. If you need a more powerful test environment, consider allocating more system resources to your workers.&lt;/p&gt;
&lt;p&gt;In UTM, create a new virtual machine with the same Ubuntu base image as the control plane node. Remove the &lt;strong&gt;Display&lt;/strong&gt; from the &lt;strong&gt;Devices&lt;/strong&gt; section and don&amp;rsquo;t forget to change the &lt;strong&gt;Network&lt;/strong&gt; to &lt;strong&gt;Host-Only&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/r6cWndM.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Worker machine configuration overview&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Spin up the machine and follow the same installation steps as the control plane. Don&amp;rsquo;t forget to change the assigned IP &lt;strong&gt;Address&lt;/strong&gt; in the network tab to &lt;code&gt;192.168.100.3&lt;/code&gt;. Set the same Cloudflare &lt;code&gt;1.1.1.1&lt;/code&gt; nameserver.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/Q3UmSOm.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Network configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;You can safely use the defaults for all the installation steps, just fill in the device &lt;strong&gt;Profile&lt;/strong&gt; section with your username and password again.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.imgur.com/BtWtRJ3.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Ubuntu profile configuration&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;After completing the installation wizard, fully turn off the VM and remove the &lt;strong&gt;USB Drive&lt;/strong&gt; from it&amp;rsquo;s &lt;strong&gt;Devices&lt;/strong&gt;, remember? Start up the machine again and switch to the root user &lt;code&gt;sudo su -&lt;/code&gt;. Update the system packages &lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -y&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/ZA8q1Nh.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Apt update command&#34;
	
	
&gt;
To get our worker to join the cluster, we need to perform the same tweaks and installations as the control plane node. Copy and paste the following command to enable kernel modules, turn off swap, allow for ip forwarding and install the Kubernetes distribution binaries &lt;em&gt;(I&amp;rsquo;ve chained all of our previous commands into one!)&lt;/em&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo swapoff -a &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;/ swap / s/^/#/&amp;#39;&lt;/span&gt; /etc/fstab &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sysctl -w net.ipv4.ip_forward&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;net.ipv4.ip_forward=1&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee -a /etc/sysctl.conf &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sysctl --system &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo modprobe overlay &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo modprobe br_netfilter &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install -y apt-transport-https ca-certificates curl gpg &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo install -m &lt;span class=&#34;m&#34;&gt;0755&lt;/span&gt; -d /etc/apt/keyrings &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo chmod a+r /etc/apt/keyrings/docker.asc &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;deb [arch=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;dpkg --print-architecture&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;. /etc/os-release &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;UBUNTU_CODENAME&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;:-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; stable&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo gpg --dearmor -o /etc/apt/keyrings/Kubernetes-apt-keyring.gpg &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;deb [signed-by=/etc/apt/keyrings/Kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sudo tee /etc/apt/sources.list.d/Kubernetes.list &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt install containerd.io kubeadm kubelet kubectl -y &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;containerd config default &amp;gt; /etc/containerd/config.toml &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/SystemdCgroup = false/SystemdCgroup = true/&amp;#39;&lt;/span&gt; /etc/containerd/config.toml &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl restart containerd kubelet
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Remember that &lt;code&gt;kubeadm join&lt;/code&gt; command you saved from before? We&amp;rsquo;ll run it on our worker node to join the cluster (you can omit the &lt;code&gt;--v=5&lt;/code&gt; flag).
&lt;img src=&#34;https://i.imgur.com/iWsHIP6.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubeadm join output&#34;
	
	
&gt;
Success! Our worker node has joined the cluster according to kubeadm, on our control plane we can run &lt;code&gt;kubectl get nodes&lt;/code&gt; to have a look at the worker status.
&lt;img src=&#34;https://i.imgur.com/4d5PyZE.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubectl node list&#34;
	
	
&gt;
We can see our worker node has joined the cluster and is ready! By default, when joining a cluster, the &lt;code&gt;kubeadm join&lt;/code&gt; command does not assign a role label to nodes. If you would like to explicitly label your node with the worker role run &lt;code&gt;kubectl label node worker node-role.kubernetes.io/worker=&lt;/code&gt;. Functionally this doesn&amp;rsquo;t make a difference for us, but it&amp;rsquo;s nice to have.
&lt;img src=&#34;https://i.imgur.com/ZD31ojS.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes node list with worker labelled&#34;
	
	
&gt;
You can substitute the role of your node with anything you like. This allows you to dedicate nodes per role in your landscape. Instead of categorising your nodes as control plane &amp;amp; worker nodes, you can separate workers based on their workloads (i.e database, backend, etc.)
&lt;img src=&#34;https://i.imgur.com/hD8vnMm.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes node list with a custom worker label&#34;
	
	
&gt;
Running &lt;code&gt;kubectl get pods -A -o custom-columns=&amp;quot;NAME:.metadata.name,NODE:.spec.nodeName,STATUS:.status.phase&lt;/code&gt; will show us all of the pods running on the system and on which node they have been scheduled.
&lt;img src=&#34;https://i.imgur.com/MU0sMKF.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubernetes pod list&#34;
	
	
&gt;
We can see that the necessary &lt;strong&gt;Cilium&lt;/strong&gt; and &lt;strong&gt;kube-proxy&lt;/strong&gt; pods are running on the new worker node. And look at that, our &lt;strong&gt;nginx&lt;/strong&gt; image is now out of it&amp;rsquo;s limbo state and is running on the worker node.&lt;/p&gt;
&lt;p&gt;🎉 Congratulations! You now have a fully functioning, host-isolated Kubernetes cluster based on virtual machines. Adding more workers or control planes for &lt;a class=&#34;link&#34; href=&#34;https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;high availability&lt;/a&gt; is as easy as just repeating the steps we&amp;rsquo;ve taken previously. Just make sure you add the &lt;code&gt;--control-plane&lt;/code&gt; flag on the &lt;code&gt;kubeadm join&lt;/code&gt; command for additional control planes.&lt;/p&gt;
&lt;h2 id=&#34;challenge-connect-your-mac-to-the-isolated-kubernetes-cluster&#34;&gt;Challenge: Connect Your Mac to the Isolated Kubernetes Cluster
&lt;/h2&gt;&lt;p&gt;Your cluster is now up and running inside its own host-only network—completely isolated from your Mac and the rest of your home network. But can you bridge the gap and manage your cluster directly from macOS?&lt;/p&gt;
&lt;p&gt;Right now, &lt;code&gt;kubectl&lt;/code&gt; only works inside the control plane VM. Your mission: configure secure connectivity so you can run &lt;code&gt;kubectl&lt;/code&gt; from your Mac, just like you would with any remote cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hints to get started:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;ll need to copy the &lt;code&gt;admin.conf&lt;/code&gt; kubeconfig file from your control plane VM to your Mac.&lt;/li&gt;
&lt;li&gt;The Kubernetes API server (&lt;code&gt;kube-apiserver&lt;/code&gt;) is only accessible from inside the host-only network. Consider using SSH port forwarding, a VPN tunnel or setting up the required internal networking routes to expose it to your Mac.&lt;/li&gt;
&lt;li&gt;Make sure your Mac can reach the control plane node&amp;rsquo;s IP (e.g., &lt;code&gt;192.168.100.2:6443&lt;/code&gt;), but don&amp;rsquo;t expose it to your entire home network for security reasons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Can you set up seamless, secure access? Give it a try and unlock full control of your cluster from your Mac!&lt;/p&gt;
&lt;h2 id=&#34;troubleshooting&#34;&gt;Troubleshooting
&lt;/h2&gt;&lt;h3 id=&#34;node-stuck-on-notready-after-reboot--persistent-swap-issues&#34;&gt;Node stuck on NotReady after reboot / Persistent swap issues
&lt;/h3&gt;&lt;p&gt;Restarted your worker node and it&amp;rsquo;s status stays stuck on &lt;strong&gt;NotReady&lt;/strong&gt;? It could be that you still have swap enabled on your machine(s). If you followed our steps before, you probably have swap disabled in the usual locations but it doesn&amp;rsquo;t hurt to check!&lt;/p&gt;
&lt;p&gt;Have a look into at the Kubelet logs with &lt;code&gt;journalctl -n 10 -u kubelet --no-pager&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/W5srfT9.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubelet logs&#34;
	
	
&gt;At the bottom we see the error &lt;strong&gt;running with swap on is not supported, please disable swap or set &amp;ndash;fail-swap-on flag to false&lt;/strong&gt;. This means we still have swapping enabled on our system. First and foremost, temporarily disable swap with &lt;code&gt;sudo swapoff -a&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We need to make sure all of the &lt;strong&gt;swap&lt;/strong&gt; entries in &lt;code&gt;/etc/fstab&lt;/code&gt; are commented out.
&lt;img src=&#34;https://i.imgur.com/St6Ip7u.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;fstab file&#34;
	
	
&gt;
At the very bottom, there is a line with &lt;code&gt;/swap.img&lt;/code&gt;, we need to make sure this line is commented out. Either manually edit the line to &lt;code&gt;#/swap.img&lt;/code&gt; or run&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;/\/swap.img/ s/^/#/&amp;#39;&lt;/span&gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s check if there&amp;rsquo;s any &lt;strong&gt;systemd&lt;/strong&gt; units left that enable swapping &lt;code&gt;systemctl list-units --type swap&lt;/code&gt;.
&lt;img src=&#34;https://i.imgur.com/qqSBJo0.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Systemctl unit list&#34;
	
	
&gt;
This machine has a systemd unit that enables swapping, meaning that after a reboot swapping will be enabled again even after disabling it in &lt;code&gt;/etc/fstab&lt;/code&gt;. Copy the name of the &lt;strong&gt;UNIT&lt;/strong&gt; and run the following command, changing &lt;code&gt;swap.img.swap&lt;/code&gt; if it differentiates from your unit name.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;systemctl mask swap.img.swap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Restart your machine, repeat the &lt;code&gt;journalctl&lt;/code&gt; command for the kubelet.
&lt;img src=&#34;https://i.imgur.com/DonwbFv.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Kubelet logs after swap disable&#34;
	
	
&gt;
The swap errors have disappeared! Our kubelet is running healthy again!&lt;/p&gt;
&lt;h3 id=&#34;containerd-cri-plugin-disabled&#34;&gt;Containerd CRI Plugin Disabled
&lt;/h3&gt;&lt;p&gt;The CRI plugin is necessary for the kubelet to interact with containerd. During the installation of containerd it&amp;rsquo;s possible that the CRI plugin get&amp;rsquo;s added to the &lt;code&gt;disabled_plugins&lt;/code&gt; list in containerd, let&amp;rsquo;s check.
&lt;img src=&#34;https://i.imgur.com/SPVHnzD.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Containerd config file disabled plugin list&#34;
	
	
&gt;
As we can see, &lt;strong&gt;cri&lt;/strong&gt; is in my disabled_plugins list. Run the following command to remove CRI from the disabled plugin list and restart containerd.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;/disabled_plugins/s/\[&amp;#34;cri&amp;#34;\]/[]/&amp;#39;&lt;/span&gt; /etc/containerd/config.toml &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;systemctl restart containerd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
        </item>
        
    </channel>
</rss>
