{"id":3148,"date":"2022-02-01T13:02:09","date_gmt":"2022-02-01T01:02:09","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=3148"},"modified":"2022-02-02T09:56:48","modified_gmt":"2022-02-01T21:56:48","slug":"kubernetes-home-powershell-vms","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/3148\/","title":{"rendered":"Kubernetes @ Home: PowerShell VM&#8217;s"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">10<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>\n<p>Following <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/3099\" data-type=\"URL\" data-id=\"https:\/\/www.deltics.co.nz\/blog\/posts\/3099\">the Vagrant experiment<\/a> (reminding me of a Bill Bailey metaphor&#8230; &#8220;<em><a rel=\"noreferrer noopener\" href=\"https:\/\/youtu.be\/4wyOy3ws8cY?t=282\" data-type=\"URL\" data-id=\"https:\/\/youtu.be\/4wyOy3ws8cY?t=282\" target=\"_blank\">a long walk down a windy beach to a cafe that was closed<\/a><\/em>&#8220;) I next set about automating my VM creation using <strong>PowerShell<\/strong>, with greater success.  Though still not perfect, the final gap to full automation is something I <em>could<\/em> close, if I wished.  And we get to install <strong>kubernetes<\/strong> itself!<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-powershell-solution\">The PowerShell Solution<\/h2>\n\n\n\n<p>Reasoning that anything the <a href=\"https:\/\/www.vagrantup.com\/docs\/providers\/hyperv\" data-type=\"URL\" data-id=\"https:\/\/www.vagrantup.com\/docs\/providers\/hyperv\"><strong>Hyper-V<\/strong> provider for <strong>Vagrant<\/strong><\/a> was able to do must be possible via some other means, my first thought was to explore <a href=\"https:\/\/docs.microsoft.com\/en-us\/powershell\/module\/hyper-v\/?view=windowsserver2022-ps\" data-type=\"URL\" data-id=\"https:\/\/docs.microsoft.com\/en-us\/powershell\/module\/hyper-v\/?view=windowsserver2022-ps\">the <strong>Hyper-V<\/strong> module for <strong>PowerShell<\/strong><\/a>.  Sure enough, it had everything I needed.<\/p>\n\n\n\n<p>Far more quickly than I achieved with <strong>Vagrant<\/strong>, I soon had a simple script to create all my VM&#8217;s:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"499\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?resize=640%2C499&#038;ssl=1\" alt=\"\" class=\"wp-image-3244\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?resize=512%2C399&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?resize=768%2C599&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/create-vms.ps1_.png?resize=334%2C260&amp;ssl=1 334w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>create-vms.ps1<\/figcaption><\/figure>\n\n\n\n<p class=\"has-black-color has-luminous-vivid-orange-background-color has-text-color has-background\"><strong>NOTE:<\/strong> Unfortunately I have had to resort to posting the scripts as screen-shots due to an issue with WordPress.  Something about these scripts made the post editor unable to save my post.<br><br>I tried with a variety of plugins as well as using a plain preformatted, unhighlighted block, without success.  Trying to resolve this issue is what has delayed this post!<br><br>I can only hope whatever issue is behind this isn&#8217;t causing problems with code posted in historic posts!  If you encounter any, please let me know.<\/p>\n\n\n\n<p>This straight-forward script declares a function (lines 1-39) that does all the heavy lifting which is then called 4 times, once for each VM that I want.<\/p>\n\n\n\n<p>With default values defined for most parameters to the function, each call only has to name the VM being created and provide the desired MAC address (again, the values in the script here are illustrative only).<\/p>\n\n\n\n<p>The function prepares each VM.  The initial <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">new-vm<\/mark> command (line 18) creates the VM with a (virtual) HDD.  Immediately a benefit over <strong>Vagrant<\/strong> is apparent in that the required network switch is also specified in this command.<\/p>\n\n\n\n<p>Further tweaks to the VM are then applied, including setting the RAM and CPU resources, disabling secure boot and applying the required properties to the network adapter of the VM (VLAN ID and MAC address).<\/p>\n\n\n\n<p>Then follows the most complicated part of the script (lines 24-35).  This section:<\/p>\n\n\n\n<ol><li>Adds a virtual DVD drive with the Ubuntu Server installation iso mounted<\/li><li>Removes the network from the boot device list<\/li><li>Sets the virtual DVD as the first boot device<\/li><\/ol>\n\n\n\n<p>None of this is strictly necessary but is a convenience for what follows. <\/p>\n\n\n\n<p>Finally, auto-checkpoints are disabled since I intend to create specific checkpoints myself at various points in the process that follows and don&#8217;t want these conflated and confused with automatic ones.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"os-installation\">OS Installation<\/h2>\n\n\n\n<p>Actual installation of the Ubuntu Server OS in each VM remains a manual step in this approach.<\/p>\n\n\n\n<p>This is the automation gap I mentioned, that <a href=\"https:\/\/ubuntu.com\/server\/docs\/install\/autoinstall\" data-type=\"URL\" data-id=\"https:\/\/ubuntu.com\/server\/docs\/install\/autoinstall\">I could close in the future if I wished<\/a> by creating fully unattended installs for my Ubuntu machines.<\/p>\n\n\n\n<p>For now, I install the OS &#8220;manually&#8221; in each VM.  The <strong>PowerShell<\/strong> script to ensure that each new VM is configured to initially boot straight into the installation process provides enough convenience for the time being.<\/p>\n\n\n\n<p>Although it (currently) has to be repeated for each VM, installation is far more straightforward with these VM&#8217;s.  Installation for use as a Kubernetes node is almost &#8220;vanilla&#8221;, with defaults accepted for almost all installation options other than network hostname, my preferred user account name and formatting the full capacity of the virtual HDD since, for some reason, the installation intends to format only half the capacity by default.  This may be related to the virtual and dynamic nature of the &#8220;disk&#8221;, though I&#8217;m not sure.<\/p>\n\n\n\n<p>A good sign that the diminutive Asus PC is more than capable of supporting these 4 VM&#8217;s was that I was able to launch all 4 newly created VM&#8217;s and go through the installation of each in parallel, without any sign of the platform straining under the load or even triggering the cooling fans on the host &#8211; an increasingly common measure of system load. \ud83d\ude42<\/p>\n\n\n\n<p>One slight concession to the scripted steps that were to follow, was to copy my <strong>ssh<\/strong> key to each VM and to make my user account capable of password-less <strong>sudo<\/strong>, making it possible to run subsequent bash scripts entirely remotely using <strong>ssh<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"onward-to-kubernetes\">Onward, To Kubernetes!<\/h2>\n\n\n\n<p class=\"has-black-color has-luminous-vivid-orange-background-color has-text-color has-background\">Apologies.  bash scripts in this section are by necessity all reproduced as screen-shots to avoid the WordPress issue mentioned above.<\/p>\n\n\n\n<p>With the VM&#8217;s ready to go, I then had just 3 scripts left to run to create my cluster.  They had to be run in a particular order, which was:<\/p>\n\n\n\n<ol><li>main.sh<\/li><li>master.sh<\/li><li>workers.sh<\/li><\/ol>\n\n\n\n<p>Each of these is a script that runs <em>another<\/em> script in one, some or all of each of my 4 VMs.  As an example, let&#8217;s look at <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">main.sh<\/mark> which runs a <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">node-install.sh<\/mark> script on <strong>all 4<\/strong> of the VM&#8217;s:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"108\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?resize=640%2C108&#038;ssl=1\" alt=\"\" class=\"wp-image-3245\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?resize=512%2C86&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?resize=768%2C129&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/main.sh_.png?resize=380%2C64&amp;ssl=1 380w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>main.sh<\/figcaption><\/figure>\n\n\n\n<p>For anyone with scripting over <strong>ssh<\/strong>, this should be familiar (if somewhat naive in places, as I am still an egg when it comes to this stuff).<\/p>\n\n\n\n<p>The <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">node-install.sh<\/mark> script itself is the largest of all of the scripts, but although it does a fair amount of work it is all very straight-forward:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"639\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?resize=640%2C639&#038;ssl=1\" alt=\"\" class=\"wp-image-3247\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?resize=512%2C512&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?resize=160%2C160&amp;ssl=1 160w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?resize=768%2C767&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/node-install.sh_.png?resize=261%2C260&amp;ssl=1 261w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>node-install.sh<\/figcaption><\/figure>\n\n\n\n<p>Tasks 1 thru 5 in this script (lines 1-42) are all about preparing the system for Kubernetes.<\/p>\n\n\n\n<p>After some initial housekeeping to elevate the script to <strong>sudo<\/strong> and ensure all apt packages are up to date, the system swap file and firewall are turned off.<\/p>\n\n\n\n<p>Tasks 3 thru 5 then configure Kubernetes to use the <strong>containerd<\/strong> runtime.<\/p>\n\n\n\n<p>Kubernetes itself is then installed by first (Task 6, lines 44-46) adding the kubernetes repo and the key for it, before finally (Task 7, lines 48-49) installing <strong>kubeadm<\/strong>, <strong>kubelet<\/strong> and <strong>kubectl<\/strong>.<\/p>\n\n\n\n<p>To recap on those three packages:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>kubeadm<\/strong><\/td><td>Command-line utility for performing Kubernetes administrative operations such as initialising a cluster or joining a node to an existing cluster.<\/td><\/tr><tr><td><strong>kubelet<\/strong><\/td><td>The process that runs on a Kubernetes node to communicate with other nodes in the cluster (this process is fundamentally what turns a machine into a node).<\/td><\/tr><tr><td><strong>kubectl<\/strong><\/td><td>Command-line utility that provides the primary means of interacting with a Kubernetes cluster.  It is used to add, remove and query the status of objects within the cluster, among other things.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Now is a good time for a brief diversion, to talk about <strong>containerd<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"docker-vs-containerd\">Docker vs containerd<\/h2>\n\n\n\n<p>Some time ago, the <strong>Kubernetes<\/strong> developers announced their intention to <a href=\"https:\/\/kubernetes.io\/blog\/2020\/12\/02\/dont-panic-kubernetes-and-docker\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/blog\/2020\/12\/02\/dont-panic-kubernetes-and-docker\/\">remove support for Docker<\/a>.  This did not mean that <strong>Kubernetes<\/strong> would be unable to &#8220;<em>run Docker containers<\/em>&#8221; only that it would no longer support &#8220;<em><strong>using<\/strong> Docker to run containers<\/em>&#8220;, i.e. using Docker as the container runtime.<\/p>\n\n\n\n<p>A subtle but important difference.<\/p>\n\n\n\n<p>The alternative is to use a container runtime called <strong>containerd<\/strong>.<\/p>\n\n\n\n<p>Containers themselves are a standardised commodity.  Indeed, they were already a &#8220;thing&#8221; before <strong>Docker<\/strong> made them popular.<\/p>\n\n\n\n<p>A &#8220;<em>Docker container<\/em>&#8221; is just &#8220;<em>a container<\/em>&#8221; that can be run on any standards-compliant container platform.  Whilst <strong>Docker<\/strong> does indeed use a standards-compliant platform to run its containers, that platform lives inside a <em>Docker-ish<\/em> environment.  For <strong>Kubernetes<\/strong> to work with the underlying platform, it has to use a compatibility layer to get through the <em>Docker-ish<\/em> layers in-between.<\/p>\n\n\n\n<p>And the standards-compliant runtime that <strong>Docker<\/strong> employs is, in fact, &#8230; <strong>containerd<\/strong>.<\/p>\n\n\n\n<p>You may already be thinking that this all sounds a bit&#8230; messy.  The <strong>Kubernetes<\/strong> team agrees with you.<\/p>\n\n\n\n<p>The prior support for this was only ever intended as a temporary compatibility concession.  It introduces additional &#8220;layers&#8221; between <strong>Kubernetes<\/strong> and the container runtime itself and ideally <strong>Kubernetes<\/strong> would work directly with the underlying container platform (<strong>containerd<\/strong>).  Arguably that&#8217;s the whole point of deprecating <strong>Docker<\/strong> support in <strong>Kubernetes<\/strong>&#8230; to wean people off of the <strong>Docker<\/strong> compatibility layer and switch over to running directly on <strong>containerd<\/strong>.<\/p>\n\n\n\n<p>It is more efficient and involves one less thing (the <strong>Docker<\/strong> compatibility layer) to have to maintain and which could go wrong.<\/p>\n\n\n\n<p>When I first manually installed <strong>Kubernetes<\/strong> I went down the <strong>docker<\/strong> route since this is what the instructions I was following did.  Although that didn&#8217;t require any configuration (beyond installing the <strong>docker.io<\/strong> package itself), it also didn&#8217;t initially work and although the configuration tweak required to get it working was fairly small, it had a bad &#8220;smell&#8221; about it, like I was changing something that wasn&#8217;t intended to be changed (well, it was deprecated after all).<\/p>\n\n\n\n<p>Ultimately I decided that since <strong>Docker<\/strong> support was going away I should get used to not using it sooner rather than later and found a more current guide that provided the <strong>containerd<\/strong> configuration steps that I have now incorporated in my scripts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-about-kubernetes-in-docker\">What About Kubernetes in Docker?<\/h2>\n\n\n\n<p>If you use <strong>Docker Desktop<\/strong>, you may be familiar with the fact that you can in fact create yourself a Kubernetes cluster with a single click by simply <strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\"><em>Enabling  Kubernetes<\/em><\/mark><\/strong> in <strong>Docker Desktop<\/strong> itself.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"221\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=640%2C221&#038;ssl=1\" alt=\"\" class=\"wp-image-3139\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=1024%2C353&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=300%2C103&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=768%2C265&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=1536%2C530&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=2048%2C706&amp;ssl=1 2048w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?resize=380%2C131&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?w=1280&amp;ssl=1 1280w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Kubernetes-in-Docker-Desktop.png?w=1920&amp;ssl=1 1920w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>This is unrelated to the &#8220;Kubernetes support for Docker&#8221; issue and is purely a convenient means of standing up a <em>single-node<\/em> <strong>Kubernetes<\/strong> cluster on a machine that already has <strong>Docker Desktop<\/strong> installed.<\/p>\n\n\n\n<p><strong>Why didn&#8217;t I just use <em>that<\/em> to learn about Kubernetes?<\/strong><\/p>\n\n\n\n<p>As a way of learning to work with Kubernetes it is indeed a great place to start, but being a single-node cluster it doesn&#8217;t provide scope for more learning about aspects of Kubernetes that arise in more complex &#8211; i.e. realistic &#8211; scenarios.  Besides, I suspected that toggling a check-box was perhaps not representative of what was involved in establishing a &#8220;proper&#8221; cluster. \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"back-to-installing-kubernetes\">Back to Installing Kubernetes<\/h2>\n\n\n\n<p>So, back to the business of installing <strong>Kubernetes<\/strong>.<\/p>\n\n\n\n<p>With <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">node-install.sh<\/mark> having been run on each on my machine, I now had 4 VM&#8217;s <em>capable<\/em> of being <strong>Kubernetes<\/strong> nodes but which were not yet actual nodes.<\/p>\n\n\n\n<p>Three steps remain:<\/p>\n\n\n\n<ol><li>Initialise (create) a cluster<\/li><li>Configure the <strong>master<\/strong> node and install a container network<\/li><li>Join the <strong>worker<\/strong> nodes to the cluster <\/li><\/ol>\n\n\n\n<p>This of course was the purpose of the remaining scripts, although the first step is manual.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-the-cluster\">Creating the Cluster<\/h2>\n\n\n\n<p>To initialise the cluster itself, all that is required is to <strong>ssh<\/strong> to the node that is to be the <strong>master<\/strong> node and run the following command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\" style=\"font-size:14px\">sudo kubeadm init<\/pre>\n\n\n\n<p>If all goes well, this creates the cluster with a swathe of diagnostic output, before finally providing some information that is critical for the steps that follow.  This is one reason it remains a manual step (that and the fact that it is trivially simple and not worth scripting).<\/p>\n\n\n\n<p>The output you get after successfully initialising a cluster should be similar to this: <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"247\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?resize=640%2C247&#038;ssl=1\" alt=\"\" class=\"wp-image-3242\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?w=928&amp;ssl=1 928w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?resize=512%2C198&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?resize=768%2C296&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubeadm-init-output.png?resize=380%2C147&amp;ssl=1 380w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>Successful output from kubeadm init<\/figcaption><\/figure>\n\n\n\n<p>The three critical pieces of information are highlighted <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">red<\/mark>, <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">blue<\/mark> and <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">green<\/mark>.<\/p>\n\n\n\n<p>The <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">red<\/mark> instruction is easily automated as indeed is the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">blue<\/mark> instruction, to install a pod network.  This is what establishes the internal network within the cluster over which the pods in that cluster communicate with each other.  There are numerous options available in this space, all described at <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/cluster-administration\/addons\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/concepts\/cluster-administration\/addons\">the Url provided in the output<\/a>.<\/p>\n\n\n\n<p>I did a little research and chose <a href=\"https:\/\/www.weave.works\/docs\/net\/latest\/kubernetes\/kube-addon\/\" data-type=\"URL\" data-id=\"https:\/\/www.weave.works\/docs\/net\/latest\/kubernetes\/kube-addon\/\">Weave<\/a>, attracted by the observation that it provided a plug-and-play option with simple installation and no configuration required.<\/p>\n\n\n\n<p>The two steps to configure the <strong>master<\/strong> and install the <strong>Weave<\/strong> network add-on were incorporated into a <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">master-config.sh<\/mark> script:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"114\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?resize=640%2C114&#038;ssl=1\" alt=\"\" class=\"wp-image-3248\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?resize=512%2C92&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?resize=768%2C137&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master-config.sh_.png?resize=380%2C68&amp;ssl=1 380w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>master-config.sh<\/figcaption><\/figure>\n\n\n\n<p>This is the first demonstration of installing something into <strong>Kubernetes<\/strong>.  The command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\" style=\"font-size:14px\">kubectl apply -f &lt;filename&gt;<\/pre>\n\n\n\n<p>asks <strong>Kubernetes<\/strong> to apply the file identified.  Within the specified file is a description of services and other objects that Kubernetes should ensure are available.  If those services need container images, then Kubernetes will pull those images as required.<\/p>\n\n\n\n<p>In this case, the file describes the <strong>Weave<\/strong> addon and is referenced by URL, with a bit of <strong>bash<\/strong> magic to ensure that the version of the file is used that is appropriate to the version of <strong>Kubernetes<\/strong> installed.<\/p>\n\n\n\n<p>I then created a <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">master.sh<\/mark> script which runs that config script on the <strong>master<\/strong> node over <strong>ssh<\/strong> and then downloads the configuration required to enable <strong>kubectl<\/strong> running on my workstation to communicate with the cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"116\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?resize=640%2C116&#038;ssl=1\" alt=\"\" class=\"wp-image-3249\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?resize=512%2C93&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?resize=768%2C139&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/master.sh_.png?resize=380%2C69&amp;ssl=1 380w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>master.sh<\/figcaption><\/figure>\n\n\n\n<p>The configuration file downloaded is simply placed in the current folder.  On a &#8220;fresh&#8221; workstation it can then be simply moved and renamed as <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">~\/.kube\/config<\/mark>.  This isn&#8217;t done automatically since if a machine already has <strong>Kubernetes<\/strong> configuration for working with other clusters, then the contents need to be incorporated into that <em>existing<\/em> configuration.<\/p>\n\n\n\n<p>The final <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\">green<\/mark> instruction provided the command to run on each of the <strong>worker<\/strong> nodes that wish to join the cluster.<\/p>\n\n\n\n<p>I created a script to do this (<mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">node-join.sh<\/mark>) which contained the command as provided in the output from <strong>kubeadm<\/strong> above, then created a further (<mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">workers.sh<\/mark>) script to run <em>that<\/em> script on each of my <strong>worker<\/strong> nodes:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"88\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?resize=640%2C88&#038;ssl=1\" alt=\"\" class=\"wp-image-3250\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?resize=512%2C71&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?resize=768%2C106&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/workers.sh_.png?resize=380%2C52&amp;ssl=1 380w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>workers.sh<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"testing-the-cluster\">Testing the Cluster<\/h2>\n\n\n\n<p>All being well, I now had a <strong>Kubernetes<\/strong> cluster up and running!<\/p>\n\n\n\n<p>To test that, all that remained was to install <strong>Kubernetes<\/strong> command-line tools on my workstation and configure them.<\/p>\n\n\n\n<p>Since I already had <strong>Docker Desktop<\/strong> installed, the <strong>Kubernetes<\/strong> CLI tools (specifically, <strong>kubectl<\/strong>) was already available as part of that.  Indeed, after installing the <strong>Kubernetes<\/strong> CLI with <strong>homebrew<\/strong>, my system was still using the <strong>Docker Desktop<\/strong> tools, which was undesirable since they were older.  <strong>Homebrew<\/strong> <strong>Doctor<\/strong> helps identify such problems and suggest fixes, which in this case meant removing a <em>symlink<\/em> that was pointing <strong>kubectl<\/strong> at the <strong>Docker Desktop<\/strong> version.<\/p>\n\n\n\n<p>Since I had no other <strong>Kubernetes<\/strong> cluster configuration to worry about, I then simply moved and renamed the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">kube-config<\/mark> file downloaded from my <strong>master<\/strong> node to <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">~\/.kube\/config<\/mark> and I was all set!<\/p>\n\n\n\n<p>As a simple test, I could ask Kubernetes to list all of the nodes in my default cluster by issuing the command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\" style=\"font-size:14px\">kubectl get nodes<\/pre>\n\n\n\n<p>If everything has gone smoothly, the result should resemble:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"62\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=640%2C62&#038;ssl=1\" alt=\"\" class=\"wp-image-3239\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=1024%2C99&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=512%2C50&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=768%2C75&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=1536%2C149&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?resize=380%2C37&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?w=1832&amp;ssl=1 1832w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-nodes-in-cluster.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>Nodes in the cluster<\/figcaption><\/figure>\n\n\n\n<p>Success!<\/p>\n\n\n\n<p>The &#8220;12d&#8221; age of my nodes reflects the fact that this screenshot was taken 12 days after having done all this.<\/p>\n\n\n\n<p>But what are the nodes <em>doing<\/em>?<\/p>\n\n\n\n<p>We can find out by asking <strong>Kubernetes<\/strong> to list all of the <em>services<\/em> that are running in the cluster, with this command:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\" style=\"font-size:14px\">kubectl get services -A<\/pre>\n\n\n\n<p>The <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">-A<\/mark> option directs Kubernetes to list all services in <em>all namespaces<\/em>, and we should get something resembling:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=640%2C68&#038;ssl=1\" alt=\"\" class=\"wp-image-3237\" width=\"640\" height=\"68\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=1024%2C109&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=512%2C55&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=768%2C82&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=1536%2C164&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=380%2C40&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?w=1802&amp;ssl=1 1802w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><figcaption>Services in the cluster<\/figcaption><\/figure>\n\n\n\n<p>Well, actually, you won&#8217;t.<\/p>\n\n\n\n<p>In a fresh cluster, you will have a <strong>default<\/strong> namespace and a <strong>kube-system<\/strong> namespace, and that&#8217;s it.<\/p>\n\n\n\n<p>The <strong>Kubernetes Dashboard<\/strong> service is not something that is installed by default.  And the fact that this service is of type <strong>LoadBalancer<\/strong> is also not something that a cluster is capable of providing out of the box.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"next-dashboard-loadbalancers-and-namespaces\">Next: Dashboard, LoadBalancers and Namespaces<\/h2>\n\n\n\n<p>Next time we&#8217;ll look at how we establish these things, why and what they do for us, as well as looking at the concept of namespaces.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">10<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Following the Vagrant experiment (reminding me of a Bill Bailey metaphor&#8230; &#8220;a long walk down a windy beach to a cafe that was closed&#8220;) I next set about automating my VM creation using PowerShell, with greater success. Though still not perfect, the final gap to full automation is something I could close, if I wished. [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":3111,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"After a delay caused by a Wordpress problem with incorporating script source in my post, the third instalment in my journey to establish a Kubernetes cluster at home.\n\nUsing PowerShell to create my VM's and some home-grown bash to provision the cluster!","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[324,4,321],"tags":[357,358,352,338],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-OM","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":3099,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3099\/","url_meta":{"origin":3148,"position":0},"title":"Kubernetes @ Home: Vagrant Nodes","date":"27 Jan 2022","format":false,"excerpt":"The first significant practical step in establishing my Bare Metal Kubernetes cluster was to provision 4 VM's. At this point it is worth mentioning that actually installing Kubernetes is almost trivial once you have the necessary hardware in place. Since my 'hardware' was going to be virtual, this meant that\u2026","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3076,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3076\/","url_meta":{"origin":3148,"position":1},"title":"A Kubernetes Cluster @ Home","date":"26 Jan 2022","format":false,"excerpt":"I set myself a new challenge for the New Year: Stand Up a Kubernetes cluster of my own. At home. I have reasons (long term costs being [potentially] lower than cloud-hosted alternatives being just one of them) but honestly since Kubernetes is a dominant presence in my day-to-day work these\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3233,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3233\/","url_meta":{"origin":3148,"position":2},"title":"Kubernetes @ Home: Dashboard","date":"07 Feb 2022","format":false,"excerpt":"At the end of the previous post in this series, we reached the point where I had my Kubernetes cluster up and running, including a Dashboard service. As I mentioned, this Dashboard is not part of a default installation. Taking a look at how I got this up and running\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3291,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3291\/","url_meta":{"origin":3148,"position":3},"title":"Kubernetes @ Home: Load Balancer","date":"11 Feb 2022","format":false,"excerpt":"In the previous post we installed the Kubernetes Dashboard and saw how we could access the dashboard service which itself runs in the Kubernetes cluster. We also saw how clunky that was and I promised a better way, which we turn to now. Kubernetes Services and IP Addresses To this\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2861,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2861\/","url_meta":{"origin":3148,"position":4},"title":"Azure DevOps &#8211; Building Some Code","date":"09 Sep 2019","format":false,"excerpt":"In this post we create a (very!) simple project, build it using Delphi (7) and run it. All with Azure DevOps.","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/pipelines-hero-code-1024x256.jpg?fit=1024%2C256&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2919,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2919\/","url_meta":{"origin":3148,"position":5},"title":"Azure DevOps &#8211; Now You Too Can Use My Template(s)","date":"14 Sep 2019","format":false,"excerpt":"Learn how you too can use my Delphi build template in your own Azure DevOps pipelines, and a sneak preview of something special coming soon...","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/github-512.png?fit=512%2C512&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3148"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=3148"}],"version-history":[{"count":72,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3148\/revisions"}],"predecessor-version":[{"id":3251,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3148\/revisions\/3251"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media\/3111"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=3148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=3148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=3148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}