<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mus'ab Husaini</title>
    <description>The latest articles on DEV Community by Mus'ab Husaini (@musabhusaini).</description>
    <link>https://dev.to/musabhusaini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F314593%2F6b252e69-8e0e-415c-9ce3-ed25637b57a3.png</url>
      <title>DEV Community: Mus'ab Husaini</title>
      <link>https://dev.to/musabhusaini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/musabhusaini"/>
    <language>en</language>
    <item>
      <title>Remote development with multi-node MicroK8s cluster and Skaffold</title>
      <dc:creator>Mus'ab Husaini</dc:creator>
      <pubDate>Wed, 15 Apr 2020 06:13:40 +0000</pubDate>
      <link>https://dev.to/musabhusaini/remote-development-with-multi-node-microk8s-cluster-and-scaffold-4o1d</link>
      <guid>https://dev.to/musabhusaini/remote-development-with-multi-node-microk8s-cluster-and-scaffold-4o1d</guid>
      <description>&lt;p&gt;As part of an effort to bring parity between environments, my team recently switched to using &lt;a href="https://kubernetes.io"&gt;Kubernetes&lt;/a&gt; for development as well as production orchestration with the help of &lt;a href="http://skaffold.dev"&gt;Skaffold&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While this reduces maintenance overhead and is a great idea on the whole, it also means that the application started outgrowing my laptop rather quickly. Luckily, we have an enterprise virtual machine provisioning environment where I decided to offload my development environment.&lt;/p&gt;

&lt;p&gt;Now, it would've been easier to set up a single-node cluster with a large enough VM, but how basic would that have been? So, I went with a multi-node cluster. If you'd like to do the same, read on!&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A set of Ubuntu virtual machines. I used four machines with 16 GB RAM and 4 CPU cores each.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pick one of these instances as the primary node and the rest will be considered secondary. Let's assume that the primary node has the IP address &lt;code&gt;10.0.0.1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A shared disk between the nodes so persistent volumes can be shared across them. For the following, we'll assume there's a reasonably sized disk mounted at &lt;code&gt;/data&lt;/code&gt; on all nodes.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ports to expose
&lt;/h2&gt;

&lt;p&gt;The primary node should have the following ports exposed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;25000&lt;/code&gt; (so secondary nodes can join the cluster)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;32000&lt;/code&gt; (so the container registry can be accessed by secondary nodes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;16443&lt;/code&gt; (if you want to use &lt;code&gt;kubectl&lt;/code&gt; from a remote machine)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Others as needed&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Software to install
&lt;/h2&gt;

&lt;p&gt;Follow standard steps to install the following services/applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://www.docker.com"&gt;Docker&lt;/a&gt; — on all nodes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://skaffold.dev"&gt;Skaffold&lt;/a&gt; — only on primary node&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My VMs started off with Ubuntu Server 16.04, which I then upgraded to 18.04. This is not necessary, but important to note in case it changes some of the steps below.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install MicroK8s
&lt;/h1&gt;

&lt;p&gt;Let's start by &lt;a href="https://microk8s.io/#get-started"&gt;installing MicroK8s&lt;/a&gt; on all nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;microk8s &lt;span class="nt"&gt;--classic&lt;/span&gt; &lt;span class="nt"&gt;--channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.18/stable
&lt;span class="c"&gt;# check status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s status &lt;span class="nt"&gt;--wait-ready&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;No need to enable any addons for now as we'll be doing this in later steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; You might run into an issue with &lt;a href="https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1662552"&gt;NFS and snap not playing nicely&lt;/a&gt;. I was only able to run MicroK8s commands as root.&lt;/p&gt;

&lt;h2&gt;
  
  
  kubectl
&lt;/h2&gt;

&lt;p&gt;MicroK8s comes with its own namespaced &lt;code&gt;kubectl&lt;/code&gt; that can be invoked with &lt;code&gt;microk8s kubectl&lt;/code&gt;. If you're used to working directly with &lt;code&gt;kubectl&lt;/code&gt;, this might start to get tedious. There are a few ways to get around this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Connect existing &lt;code&gt;kubectl&lt;/code&gt; to your MicroK8s instance by running&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl config view &lt;span class="nt"&gt;--raw&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The same file can also be used to access the cluster from a remote machine as long as appropriate ports are accessible. See &lt;a href="https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig"&gt;documentation&lt;/a&gt; for more information. I should point out that this is the option that I have tested, but the next two might also work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a good old bash alias: &lt;code&gt;alias kubectl='microk8s kubectl'&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a snap alias: &lt;code&gt;sudo snap alias microk8s.kubectl kubectl&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Install and configure MicroK8s addons
&lt;/h1&gt;

&lt;p&gt;First, enable some basic &lt;a href="https://microk8s.io/docs/addons"&gt;MicroK8s addons&lt;/a&gt; that we're going to need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s &lt;span class="nb"&gt;enable &lt;/span&gt;dns ingress storage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you need &lt;a href="http://helm.sh"&gt;Helm&lt;/a&gt; support, be sure to add &lt;code&gt;helm&lt;/code&gt; to the list above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure storage location
&lt;/h2&gt;

&lt;p&gt;By default, the storage addon persists all volumes in &lt;code&gt;/var/snap/microk8s/common/default-storage&lt;/code&gt;. Since we're going to be sharing storage across various nodes, we need to update this to write to our &lt;code&gt;/data&lt;/code&gt; mounted disk instead. You can do this by editing the &lt;code&gt;hostpath-provisioner&lt;/code&gt; deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl get &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system deploy hostpath-provisioner | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s~/var/snap/microk8s/common/default-storage~/data/snap/microk8s/common/default-storage~g'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; -

&lt;span class="c"&gt;# restart microk8s for good measure&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s stop &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable internal registry
&lt;/h2&gt;

&lt;p&gt;When you have a multi-node cluster, the easiest way to share development images is to push them to a private registry. Skaffold knows how to push and pull from private registries, when needed, but we'll need to set one up. Luckily, MicroK8s makes it relatively easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s &lt;span class="nb"&gt;enable &lt;/span&gt;registry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will start a registry on port &lt;code&gt;32000&lt;/code&gt; that can be accessed by other nodes in the cluster via &lt;code&gt;10.0.0.1:32000&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with an insecure registry
&lt;/h3&gt;

&lt;p&gt;Without additional configuration, the registry started in the step above is insecure. If you're not comfortable with that, you could look into securing it. For the purposes of this tutorial, we will continue to use it as is, which still requires some – though less involved – changes.&lt;/p&gt;

&lt;p&gt;First, we need to make sure that Docker won't have any trouble pushing to this registry just because it is insecure. This can be done by &lt;a href="https://docs.docker.com/registry/insecure/"&gt;editing the Docker daemon configuration&lt;/a&gt; at &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; to add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"insecure-registries"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.1:32000"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Keep in mind that this file might need to be created if it's not already present. Once this is taken care of, restart the daemon for changes to take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, MicroK8s needs to be persuaded not to complain when pulling from this insecure registry. For this, find the file &lt;code&gt;/var/snap/microk8s/current/args/containerd-template.toml&lt;/code&gt; and under &lt;code&gt;[plugins] -&amp;gt; [plugins.cri.registry] -&amp;gt; [plugins.cri.registry.mirrors]&lt;/code&gt;, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight toml"&gt;&lt;code&gt;        &lt;span class="nn"&gt;[plugins.cri.registry.mirrors."10.0.0.1:32000"]&lt;/span&gt;
          &lt;span class="py"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["http://10.0.0.1:32000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Restart MicroK8s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s stop &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This needs to be done on all the nodes. See &lt;a href="https://microk8s.io/docs/registry-private"&gt;official instructions&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h1&gt;
  
  
  Form the cluster
&lt;/h1&gt;

&lt;p&gt;Finally, we're ready to form a cluster. Run the following on the master node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;microk8s add-node
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You'll get back something similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Join node with: microk8s join 10.0.0.1:25000/&amp;lt;some-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Copy the &lt;code&gt;microk8s join...&lt;/code&gt; command and run it on one of the secondary nodes. A new token will need to be generated for each secondary node that you wish to add to the cluster.&lt;/p&gt;

&lt;p&gt;Now you can run &lt;code&gt;kubectl get nodes&lt;/code&gt; on the primary and see that all nodes have joined. That's it, you now have your own fully functioning Kubernetes cluster!&lt;/p&gt;

&lt;h1&gt;
  
  
  Use Skaffold for building and deployment
&lt;/h1&gt;

&lt;p&gt;This part of the tutorial assumes some knowledge of &lt;a href="http://skaffold.dev"&gt;Skaffold&lt;/a&gt;. If you aren't familiar with it, it's a very useful tool and I'd highly recommend checking it out. In my case, we're using Skaffold to simplify building Docker images and deploying our &lt;a href="http://helm.sh"&gt;Helm&lt;/a&gt; charts during development.&lt;/p&gt;

&lt;p&gt;With the above setup in place, we need to make some minor adjustments to our &lt;code&gt;skaffold.yaml&lt;/code&gt; files in order to make them work with the multi-node cluster and private registry. I will also assume that your code has already been cloned to a location on your primary node.&lt;/p&gt;

&lt;p&gt;First, list your private registry under &lt;code&gt;build.insecureRegistries&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;insecureRegistries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.0.0.1:32000"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Second, prefix image names for all your artifacts under &lt;code&gt;build&lt;/code&gt; with your registry address like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0.0.1:32000/my-image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The same goes for where you're using the image. This might vary depending on your own setup. As an example, if you're using Helm, you might end up with something similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;releases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;imageName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0.0.1:32000/my-image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you can build and deploy with &lt;code&gt;skaffold dev&lt;/code&gt; or &lt;code&gt;skaffold run&lt;/code&gt; as usual. Development will never be the same again!&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;Along the way, I encountered some issues that might be peculiar to my setup, but are worth mentioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting &lt;code&gt;Forbidden: disallowed by cluster policy&lt;/code&gt; error
&lt;/h2&gt;

&lt;p&gt;I ran into &lt;a href="https://github.com/ubuntu/microk8s/issues/749"&gt;this issue&lt;/a&gt; when trying to install the Elasticsearch chart. The workaround for this was to do the following on the primary node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--allow-privileged"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /var/snap/microk8s/current/args/kube-apiserver
&lt;span class="c"&gt;# restart microk8s for changes to take effect&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s stop &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;microk8s start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Unable to connect to the internet from pods
&lt;/h2&gt;

&lt;p&gt;By default, the internal DNS server points to Google's DNS servers. If, for whatever reason, this doesn't work for you, you would need to &lt;a href="https://microk8s.io/docs/addon-dns"&gt;update the CoreDNS configuration&lt;/a&gt; to allow your pods to access the internet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl edit &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system configmaps coredns
&lt;span class="c"&gt;# edit the configmap by replacing the line that starts with "forward" under data.Corefile with "forward . /etc/resolv.conf"&lt;/span&gt;
&lt;span class="c"&gt;# if this doesn't work, you might try manually replacing Google's 8.8.8.8 and 8.8.4.4 servers with your own DNS servers&lt;/span&gt;
&lt;span class="c"&gt;# save and close, the configuration should get reloaded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Next steps
&lt;/h1&gt;

&lt;p&gt;Here are some improvements to this system you could explore that I have not covered here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If &lt;a href="https://code.visualstudio.com"&gt;VS Code&lt;/a&gt; is your IDE of choice, &lt;a href="https://code.visualstudio.com/docs/remote/ssh"&gt;remote SSH development&lt;/a&gt; makes interacting with your remote code a breeze!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure Helm and kubectl clients on your local machines to connect to your remote cluster by exporting the kubeconfig file described earlier. This would allow you to store your code and interact with the cluster without leaving the comforts of your local machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might even try offloading your builds to the remote Docker host instead of building locally&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope you have fun with it!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>skaffold</category>
      <category>microk8s</category>
    </item>
  </channel>
</rss>
