<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Ian Harris</title>
 <link href="https://ianharris.co.uk/atom.xml" rel="self"/>
 <link href="https://ianharris.co.uk/"/>
 <updated>2026-06-22T14:10:44+00:00</updated>
 <id>https://ianharris.co.uk</id>
 <author>
   <name>Ian Harris</name>
   <email>hello@ianharris.co.uk</email>
 </author>

 
 <entry>
   <title>Raspberry Pi Cluster, Part 8 - Automation</title>
   <link href="https://ianharris.co.uk/2021/11/07/raspberry-pi-cluster-part-8-automation/"/>
   <updated>2021-11-07T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/11/07/raspberry-pi-cluster-part-8-automation</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wow, this has turned into a much bigger series that I initially planned! Well done if you have made it this far! I promise this is the last once (for now).&lt;/p&gt;

&lt;p&gt;At this point, the cluster is up and running and it is serving secure website pages over the Internet. However, there is a small issue with our setup whereby any changes that we wish to publish on the site requires us to got through the process of recreating the docker image and then logging into Kubernetes to redeploy it. It’s a bit of a faff.&lt;/p&gt;

&lt;p&gt;To do this after pushing up a change to DockerHub, we can restart the Kubernetes deployment to pull down the latest image with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl rollout restart deploy blog-website
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But that will get tiresome quickly. This is where &lt;a href=&quot;https://keel.sh/&quot;&gt;Keel&lt;/a&gt; comes in. It’s a very handy little app that will monitor our DockerHub repo and then update our Kubernetes deployment as and when we push up a new version of our website image.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/keel.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/keel.png&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;h3 id=&quot;installing-helm&quot;&gt;Installing Helm&lt;/h3&gt;

&lt;p&gt;To install Keel, we need to first install &lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt;. It’s the package manager for Kubernetes and uses &lt;em&gt;charts&lt;/em&gt; to deployment predefined manifests on to Kubernetes clusters. It’s easy to install, run each of these three commands in order:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As per the K3S documentation, we need to set and Environment Variable to allow Helm to work properly, set this with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export KUBECONFIG=~/.kube/config
cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
chmod 600 ~/.kube/config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;setup-webhook-relay-forwarding&quot;&gt;Setup Webhook Relay forwarding&lt;/h3&gt;

&lt;p&gt;helm upgrade –install webhookrelay-operator –namespace=auto-deploy webhookrelay/webhookrelay-operator \ –set credentials.key=$RELAY_KEY –set credentials.secret=$RELAY_SECRET&lt;/p&gt;

&lt;h3 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Host a private Docker image repository&lt;/li&gt;
  &lt;li&gt;Use solar/wind energy&lt;/li&gt;
  &lt;li&gt;Optimising the Kubernetes deployment. Maybe remove Traefik and install Nginx Ingress.&lt;/li&gt;
  &lt;li&gt;Use a CI/CD service like CircleCI&lt;/li&gt;
  &lt;li&gt;Replace the last 2 3b+ Pi with 2Gb Pi 4s&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 7 - SSL</title>
   <link href="https://ianharris.co.uk/2021/10/04/raspberry-pi-cluster-part-7-ssl/"/>
   <updated>2021-10-04T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/10/04/raspberry-pi-cluster-part-7-ssl</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here we are, the last push to complete this project. Let’s add a SSL certificate to encrypt all traffic to the cluster ensure that the site is as secure as possible. Once that is done, we can take a look at automating the deployment process.&lt;/p&gt;

&lt;p&gt;If you have been following along and you have setup Cloudflare as per &lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;the previous post&lt;/a&gt;, you should see that your website is already protected with a SSL certificate. So why do we need to add another certificate on our server?&lt;/p&gt;

&lt;p&gt;Good question. What is happening now is that all connections to the Cloudflare proxy are encrypted, but any connections from Cloudflare to our website are not and any data will be sent over the wire in plain text. Ideally, we want full encryption to our cluster, so we are going to install &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; on the cluster to solve this problem.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/lets-encrypt.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/lets-encrypt.png&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;h3 id=&quot;installing-cert-manager&quot;&gt;Installing Cert Manager&lt;/h3&gt;

&lt;p&gt;Let’s dive straight into it - the first task is to create a new namespace in Kubernetes so that we can keep all the SSL stuff in its own little bubble:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create namespace cert-manager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Installing Cert Manager is pretty easy, it’s a single command. Just make sure you replace the version number with the current available version. For me, this was &lt;strong&gt;v1.6.1&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.6.1/cert-manager.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once the install has finished, we next need to create a new ClusterIssuer resource on the Create a new file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano letsencrypt.yml&lt;/code&gt; and copy in the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    email: &amp;lt;email-address&amp;gt;
    privateKeySecretRef:
      name: blog-website-tls
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: traefik
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replace &lt;em&gt;&amp;lt;email-address&amp;gt;&lt;/em&gt; with your email address and keep an note of the name that you assigned for &lt;em&gt;privateKeySecretRef&lt;/em&gt; property.&lt;/p&gt;

&lt;p&gt;Finally, tell Kubernetes to use our cert-manager manifest file by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f letsencrypt.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can check to make sure everything is working expected by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl describe clusterissuer letsencrypt-prod
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All being well, we should see that everything is running and the Issuer is returning a Ready status. You may need to give it a couple of minutes to sort itself out.&lt;/p&gt;

&lt;h3 id=&quot;updating-the-ingress&quot;&gt;Updating the ingress&lt;/h3&gt;

&lt;p&gt;After we have install the means of issuing a Let’s Encrypt certificate on the cluster, we now need to update the Ingress that we configured previously to use TLS and to apply for a certificate. Open up the Ingress manifest again with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano ingress.yml&lt;/code&gt; and update it so it looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog-website-ingress
  annotations:
    kubernetes.io/ingress.class: traefik
    cert-manager.io/cluster-issuer: letsencrypt-prod
    acme.cert-manager.io/http01-edit-in-place: &quot;true&quot;
spec:
  tls:
    - secretName: blog-website-tls
      hosts:
        - &amp;lt;external-domain&amp;gt;
  rules:
  - host: &amp;lt;external-domain&amp;gt;
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: blog-website-service
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure you double-check the names and domains like you did before. Also make sure your annotations match the names that you used when you created the ClusterIssuer, for me this is &lt;strong&gt;letsencrypt-prod&lt;/strong&gt;. It’s very easy to get a name mis-match here and you can spend a long, long time trying to track this down. Again, I bet you can’t guess how I know this.&lt;/p&gt;

&lt;p&gt;Last job, we need to reapply the manifest:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f ingress.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It will take a few minutes, Let’s Encrypt with dial home with the cluster details and after a few minutes, you should be able to access the cluster directly on &lt;em&gt;https:&lt;/em&gt;. If you go into the Cloudflare control panel, you should now be able to set up &lt;strong&gt;Full&lt;/strong&gt; encryption to ensure that all of the data transmissions are secure.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Part 8 - Automation&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 6 - Dynamic DNS</title>
   <link href="https://ianharris.co.uk/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/"/>
   <updated>2021-09-29T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are nearing the end of this epic journey, now we just need to point a domain to the cluster so that it accessible to the outside world and apply a SSL certificate to make sure that any connections are secure.&lt;/p&gt;

&lt;p&gt;Simple right? Well, no it isn’t (it could have been, but where is the fun in that).&lt;/p&gt;

&lt;p&gt;This isn’t the place to delve too deeply into the inner workings of DNS, quite frankly, neither of us has the time, but &lt;a href=&quot;https://www.cloudflare.com/en-gb/learning/dns/what-is-dns/&quot;&gt;there are plenty of resources available&lt;/a&gt;. As the cluster is sitting inside my home network, it means that I need to point my domain name to the external IP address of my router.&lt;/p&gt;

&lt;p&gt;Here is my first problem - my ISP is BT Infinity and as any customer of theirs will know, they do not provide static IP’s for their home customers. This is a fairly major problem when it comes to DNS, it means that my external IP will change often, maybe even a few times a day. That will result in me having to go and manually update my DNS records every time the IP changes - not ideal.&lt;/p&gt;

&lt;p&gt;Fortunately there a pretty well established solution to the problem, it’s called Dynamic DNS. The concept is pretty simple, there is normally an agent sitting somewhere inside the network, it repeatedly checks the external IP address and automatically updates a Dynamic DNS provider.&lt;/p&gt;

&lt;p&gt;I’ve been using &lt;a href=&quot;https://www.noip.com/&quot;&gt;No-IP&lt;/a&gt; for years. My router runs the agent and it works perfectly. However, one minor downside is that to use their free tier, I need to manually confirm the hostname every month, which isn’t that much of a pain, but we can do better…&lt;/p&gt;

&lt;h3 id=&quot;cloudflare&quot;&gt;Cloudflare&lt;/h3&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/cloudflare.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/cloudflare.png&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;In my professional life, I am a web developer and I’ve used &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; a few times for various clients and I’ve always been impressed by their service.&lt;/p&gt;

&lt;p&gt;(I must point out here that I am in no way associated with them, I just like their service - this ain’t no ad!)&lt;/p&gt;

&lt;p&gt;There are many, many parts to Cloudflare that sets it apart from other services, mainly their security and DDOS protection, but there is one feature I am particularly interested in and that is their DNS proxy.&lt;/p&gt;

&lt;p&gt;As I’m running this on my home network, it means that I am exposing my IP to the world, while in reality isn’t too much of an issue, but I would sleep easier at night knowing that I’m not creating easy access to my home network. Quite simply, Cloudflare has an option called DNS Proxy for your DNS records where they replace my IP address with one of theirs, then forward any requests in the background. Nice.&lt;/p&gt;

&lt;p&gt;That is enough talking, let’s set it up…&lt;/p&gt;

&lt;h3 id=&quot;updating-the-dns-records&quot;&gt;Updating the DNS records&lt;/h3&gt;

&lt;p&gt;First of all, you’ll need a &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; account, head over there and create one if you haven’t already.&lt;/p&gt;

&lt;p&gt;Second, watch this &lt;a href=&quot;https://www.youtube.com/watch?v=rI-XxnyWFnM&quot;&gt;awesome tutorial&lt;/a&gt; by NetworkChuck over on YouTube. The general idea is that we create our own DDNS agent (like we discussed earlier) via a &lt;a href=&quot;https://github.com/K0p1-Git/cloudflare-ddns-updater/&quot;&gt;very handy script&lt;/a&gt; by someone called &lt;em&gt;K0p1-Git&lt;/em&gt; (thanks for that!) that makes it easy to communicate with the Cloudflare API.&lt;/p&gt;

&lt;p&gt;All we need do is update the script with our API credentials and then execute the script on a schedule via a cron job. Job done!&lt;/p&gt;

&lt;p&gt;Except, I didn’t go through the pain of building an awesome Kubernetes cluster and not to use it, did I! Surely we can run this script via a Kubernetes deployment?&lt;/p&gt;

&lt;h3 id=&quot;migrating-the-script-to-kubernetes&quot;&gt;Migrating the script to Kubernetes&lt;/h3&gt;

&lt;p&gt;As I mentioned in &lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;part 5&lt;/a&gt;, the current stable release of Kubernetes at the time of writing this series is v1.21.4, which is handy because in v1.21 they introduced the &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/&quot;&gt;Cronjob resource&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This should have made this migration to Kubernetes easy right? Well, not quite. It did make setting the cron task super easy, but trying to run this task on Kubernetes became one of those things where it became a matter of principle to find a way of getting it to work.&lt;/p&gt;

&lt;p&gt;You’ll be please to hear, dear reader, that I persevered and in the end found an excellent solution to the problem. I also learnt a lot about Kubernetes in the process. I’ll guide you through the steps here to prevent you from spending hours trying to figure it out!&lt;/p&gt;

&lt;h4 id=&quot;step-1&quot;&gt;Step 1&lt;/h4&gt;

&lt;p&gt;Make sure you have watched the video above, completed the steps there and you have confirmed the script is working by testing on your server. Make sure you are in the same folder as your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloudflare.sh&lt;/code&gt; script.&lt;/p&gt;

&lt;h4 id=&quot;step-2&quot;&gt;Step 2&lt;/h4&gt;

&lt;p&gt;To run the cron task, we need a container to do that in with an OS, I found that the best base was &lt;a href=&quot;https://alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;. There are a couple of reasons for this, the first is the size, it’s barely 4Mb in size, which is essential as we want to keep this efficient as possible. Using Ubuntu or another flavour of Linux would be way too bloated for what we want to do here. This is what Alpine was created for.&lt;/p&gt;

&lt;p&gt;The Second reason is that we need to be able to install packages into the distro (more in this in a sec), which is something that Busybox can’t do.&lt;/p&gt;

&lt;p&gt;The problem we have now is that Alpine doesn’t include Bash, so we need to modify our script to tell it which shell binary to use. Fortunately, that is very easy. Open up the script again with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo nano cloudflare.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Change the very first line of the file from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/bin/bash&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#!/bin/sh&lt;/code&gt;, save and exit.&lt;/p&gt;

&lt;h4 id=&quot;step-3&quot;&gt;Step 3&lt;/h4&gt;

&lt;p&gt;The Cronjob deployment we are creating needs a way to be able to read our script. As it’s just a simple file, we can utilise a config map here. This is a really nice way to give Kubernetes access to the script:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl create configmap cloudflare --from-file=cloudflare.sh&lt;/code&gt;&lt;/p&gt;

&lt;h4 id=&quot;step-4&quot;&gt;Step 4&lt;/h4&gt;

&lt;p&gt;Create a new file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano cloudflare-cronjob.yml&lt;/code&gt; and copy in the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: CronJob
metadata:
  name: cloudflare
spec:
  schedule: &quot;*/5 * * * *&quot;
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: cloudflare
            image: carronmedia/alpine-curl-arm64
            command: [&quot;/scripts/cloudflare.sh&quot;]
            volumeMounts:
            - name: cloudflare
              mountPath: /scripts
          volumes:
          - name: cloudflare
            configMap:
              name: cloudflare
              defaultMode: 0744
          restartPolicy: OnFailure

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can edit this to your needs, but here are a couple of notes. This will work on older versions of Kubernetes, but you’ll need to change the first line to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apiVersion: batch/v1beta1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The schedule is currently set to run the script every minute, this is standard cron notation, so you can update this as you wish.&lt;/p&gt;

&lt;p&gt;Finally, the last point is the image. This is where a lot of my headaches came from.&lt;/p&gt;

&lt;p&gt;At first, I set this to use the base &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alpine&lt;/code&gt; image, but the script was failing with the dreaded &lt;em&gt;exec user process caused “exec format error”&lt;/em&gt; error. I mentioned this in &lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;part 4&lt;/a&gt;, which I forgot about. So after a lot of reading and messing about, I remembered that this is because arm64 CPU architecture of the Raspberry Pi isn’t compatible with the standard &lt;em&gt;alpine&lt;/em&gt; image. I needed to use the &lt;em&gt;arm64/alpine&lt;/em&gt; image instead.&lt;/p&gt;

&lt;p&gt;Next it threw an error about cURL not being installed. Alpine by default doesn’t include cURL, so I created a pair of simple Docker images to run the cURL install for us. These are available on DockerHub: &lt;a href=&quot;https://hub.docker.com/r/carronmedia/alpine-curl&quot;&gt;carronmedia/alpine-curl&lt;/a&gt; for the standard version and &lt;a href=&quot;https://hub.docker.com/r/carronmedia/alpine-curl-arm64&quot;&gt;carronmedia/alpine-curl-arm64&lt;/a&gt; for the arm64 version for Raspberry Pis.&lt;/p&gt;

&lt;h4 id=&quot;step-5&quot;&gt;Step 5&lt;/h4&gt;

&lt;p&gt;Finally, let’s deploy our Cronjob with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create -f cloudflare-cronjob.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This creates all the necessary pods on Kubernetes and this will run our script to update our DNS settings on Cloudflare. We can confirm that everything is working by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get cronjob cloudflare
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All being well, you should see your Cronjob deployment in an active state.&lt;/p&gt;

&lt;p&gt;Well done! That was a long post, but hopefully you found it useful.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Part 7 - SSL&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 5 - Kubernetes</title>
   <link href="https://ianharris.co.uk/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/"/>
   <updated>2021-09-25T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/25/raspberry-pi-cluster-part-5-kubernetes</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s time to join all of these dots together and really get into the depths of it. We have a functional Raspberry Pi Cluster, we have a functional Docker image hosting a website, so let’s install the container orchestration system known as Kubernetes.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/kubernetes.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/kubernetes.png&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;I mentioned Kubernetes briefly in the previous post and what it is, but instead of going into the details here, I’ll leave that to &lt;a href=&quot;https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/&quot;&gt;The Linux Foundation to do a much better job&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As Kubernetes is an enterprise grade system, it has enterprise grade system requirements and under normal circumstances, we wouldn’t even consider running such a thing on a Raspberry Pi. Thankfully, the nice people at &lt;a href=&quot;https://rancher.com/&quot;&gt;Rancher&lt;/a&gt; built a super lightweight stripped back version of Kubernetes called &lt;a href=&quot;https://rancher.com/products/k3s&quot;&gt;K3S&lt;/a&gt; that plays very nicely on Raspberry Pi’s.&lt;/p&gt;

&lt;h3 id=&quot;installing-k3s&quot;&gt;Installing K3S&lt;/h3&gt;

&lt;p&gt;You’d think this part would be complicated, it couldn’t really be much easier. On your master node, run the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--write-kubeconfig-mode 644&lt;/code&gt; argument here. This is important as it installs Kubernetes with the correct permissions. Without it, when the cluster reboots, it won’t be able to access the file system. You won’t need many guesses to know how I found this out.&lt;/p&gt;

&lt;p&gt;Give a few minutes and that is it. Kubernetes v1.21 (at the time of writing) is installed and in theory, the cluster is alive! We can check this by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get pods -A
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All good! The next task is to install Kubernetes of each of the worker nodes. However, before we can do this, we need to get the cluster’s unique access token that we use to connect the worker nodes to the master. Run this and make a note of the result:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo cat /var/lib/rancher/k3s/server/token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Almost there! On each of the worker nodes, run the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -sfL https://get.k3s.io | K3S_URL=https://&amp;lt;server&amp;gt;:6443 K3S_TOKEN=&amp;lt;token&amp;gt; sh -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;em&gt;&amp;lt;server&amp;gt;&lt;/em&gt; is the IP address of your master node and &lt;em&gt;&amp;lt;token&amp;gt;&lt;/em&gt; is the token we got from the master node in the previous step.&lt;/p&gt;

&lt;p&gt;And once complete, the Raspberry Pi based Kubernetes cluster is complete!&lt;/p&gt;

&lt;h3 id=&quot;major-problem-no-2&quot;&gt;Major problem No. 2&lt;/h3&gt;

&lt;p&gt;Full disclosure, I didn’t realise I had the problem that I’m just about to describe until a bit further down the line, but it makes sense to talk about it here quickly.&lt;/p&gt;

&lt;p&gt;Whilst Kubernetes installs without any issues on a Raspberry Pi 3b+, after around 24 hours of use, the master node runs out of steam and locks itself up. After quite a bit research and a fair amount of swearing, I discovered that any versions of K3S later that v1.17 don’t play nicely on the 3b+ variants of the Pi.&lt;/p&gt;

&lt;p&gt;The solution was to upgrade the master node to a Raspberry Pi 4 4Gb. I probably should have just bitten the bullet on done this in the first place. Never
mind. As the Pi 4 runs host, especially running Kubernetes, I added a fan shim to keep it cool.&lt;/p&gt;

&lt;p&gt;Here was additional shopping list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Raspberry Pi 4 4Gb (&lt;a href=&quot;https://shop.pimoroni.com/products/raspberry-pi-4?variant=29157087445075&quot;&gt;Pimoroni&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Fan SHIM for Raspberry Pi (&lt;a href=&quot;https://shop.pimoroni.com/products/fan-shim&quot;&gt;Pimoroni&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This added a further £63.90 to the budget.&lt;/p&gt;

&lt;h3 id=&quot;configuring-our-cluster&quot;&gt;Configuring our cluster&lt;/h3&gt;

&lt;p&gt;We now need to tell Kubernetes what to do and we do this via a series of manifest files of different types. Again, this post is long enough already, so I’m not going into the details here, but this will give you an idea of how I got it all working.&lt;/p&gt;

&lt;p&gt;Fortunately, there isn’t much in the way of configuration needed, the K3S install pretty much does it all. However, as the Docker image we created is in a private repo on DockerHub, Kubernetes needs the access credentials to pull the image down. Run the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl create secret docker-registry regcred --docker-server=&amp;lt;your-registry-server&amp;gt; --docker-username=&amp;lt;your-name&amp;gt; --docker-password=&amp;lt;your-password&amp;gt; --docker-email=&amp;lt;your-email&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replacing &lt;em&gt;&amp;lt;your-registry-server&amp;gt;&lt;/em&gt;, &lt;em&gt;&amp;lt;your-name&amp;gt;&lt;/em&gt;, &lt;em&gt;&amp;lt;your-password&amp;gt;&lt;/em&gt; and &lt;em&gt;&amp;lt;your-email&amp;gt;&lt;/em&gt; with your credentials. Kubernetes can now pull down our private image from DockerHub.&lt;/p&gt;

&lt;p class=&quot;message&quot;&gt;
  WARNING: Be aware that there is a potential security issue here where you credentials are visible in your bash history. If this is going to be a problem, there are options available to help. Give it a Google. For me this is not a factor - if someone can view my bash history, I have bigger problems to deal with!
&lt;/p&gt;

&lt;h3 id=&quot;setting-up-a-deployment&quot;&gt;Setting up a Deployment&lt;/h3&gt;

&lt;p&gt;A deployment manifest in basic terms tells the Kubernetes where to find the image that will be used as the base of the application that we want to run on the cluster. Let’s create the manifest file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano deployment.yml&lt;/code&gt; and copy in the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog-website
spec:
  replicas: 3
  selector:
    matchLabels:
      app: blog-website
  template:
    metadata:
      labels:
        app: blog-website
    spec:
      containers:
      - env:
        image: &amp;lt;username&amp;gt;/&amp;lt;containername&amp;gt;:latest
        imagePullPolicy: Always
        name: blog-website
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: regcred
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replace &lt;em&gt;blog-website&lt;/em&gt; with what ever name you want. Just remember what you change it to as we’ll need to link to this shortly. There are some important lines to consider, the first is &lt;strong&gt;replicas&lt;/strong&gt;. I chose 3 as I have 3 worker nodes in my cluster. You can set this how you like, you can have more that 1 replica per node too. I could have set this to 20, but that is probably a little excessive.&lt;/p&gt;

&lt;p&gt;Secondly, replace  &lt;em&gt;&amp;lt;username&amp;gt;&lt;/em&gt; and &lt;em&gt;&amp;lt;containername&amp;gt;&lt;/em&gt; with the image details that you created in the last post.&lt;/p&gt;

&lt;p&gt;After you have saved and closed the manifest file, we need to pass it to Kubernetes. Run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f deployment.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After a few minutes, if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get pods&lt;/code&gt;, you should see that 3 pods come online all running instances of our Docker container. Nice.&lt;/p&gt;

&lt;p class=&quot;message&quot;&gt;
  WARNING: This is where you are likely to see issues if you have any problems with your configuration or servers. One to look out for is an **EXEC FORMAT** error. This is most likely caused by you trying to run a container that was built to be run on a different processor architecture. Remember Raspberry Pi&apos;s have ARM processors. Images built for x86 based systems will not work on a Pi.
&lt;/p&gt;

&lt;h3 id=&quot;setting-up-a-service&quot;&gt;Setting up a Service&lt;/h3&gt;

&lt;p&gt;The next manifest we want to create is a service, this will give network access to our deployment. There are a few different types of service, however, we don’t need to specify one and this will use the default &lt;em&gt;ClusterIP&lt;/em&gt; type. As before, let’s create a new file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano service.yml&lt;/code&gt; and copy in the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: blog-website-service
spec:
  selector:
    app: blog-website
  ports:
  - protocol: TCP
    port: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can change the names to what you wish, just make sure they match between your different manifest files. Apply this service manifest with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f service.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, give it a few minutes and check your service is running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get service&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;setting-up-an-ingress&quot;&gt;Setting up an Ingress&lt;/h3&gt;

&lt;p&gt;Like everything Kubernetes, there appears to be at least 100 ways of doing anything, so it’s no surprise that setting up Ingress can be confusing. An Ingress essentially routes external traffic to our service (which in turn exposes our deployment). Most people appear to favour the Nginx Ingress in all of the tutorials that I read researching this, but K3S ships with an Ingress controller called &lt;a href=&quot;https://traefik.io/&quot;&gt;Traefik&lt;/a&gt;, so we’ll go with that.&lt;/p&gt;

&lt;p&gt;As before, create a new file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo nano ingress.yml&lt;/code&gt; and copy in the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog-website-ingress
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  rules:
  - host: &amp;lt;external-domain&amp;gt;
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: blog-website-service
            port:
              number: 80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As previously, make sure your names match what you put in the other manifest and replace &lt;em&gt;&amp;lt;external-domain&amp;gt;&lt;/em&gt; with your own domain name that you want to use for your website.&lt;/p&gt;

&lt;p&gt;Apply this service manifest with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f ingress.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Congratulations! Once the ingress comes online, you should have a cluster based website that is accessible on the network. There is one last task to make our master piece available to the outside world…&lt;/p&gt;

&lt;p class=&quot;message&quot;&gt;
  TOP TIP: You can combine all of these manifest into a single file, all you need to do is separate each declaration with ---. I separated it out here to make it easier to explain.
&lt;/p&gt;

&lt;h3 id=&quot;setup-port-forwarding-on-network-router&quot;&gt;Setup port forwarding on network router&lt;/h3&gt;

&lt;p&gt;In terms of instructions, you are on your own I’m afraid. Every router is different, but the process is essentially the same. In your network router configuration, find the port forwarding section and forward port TCP 80 (might as well do 443 too if you enable SSL like I do in a future post) to the internal IP of your &lt;em&gt;master&lt;/em&gt; node.&lt;/p&gt;

&lt;p&gt;This was a pretty epic post, well done if you have made it this far. Now we have the website up and running, let’s help people find it by pointing a domain at it.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Part 6 - Dynamic DNS&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 4 - Jekyll and Docker</title>
   <link href="https://ianharris.co.uk/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/"/>
   <updated>2021-09-22T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 4- Jekyll and Docker&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now I have my Pi’s all set up and ready to go, I’m ready to lose myself down the rabbit hole that is Kubernetes. However, before I can go there, I need to think about what I’m actually going to use the cluster for (apart from a &lt;a href=&quot;https://codeburst.io/run-a-minecraft-server-for-free-on-kubernetes-ac82a892969e&quot;&gt;Minecraft server&lt;/a&gt;, obvs).&lt;/p&gt;

&lt;p&gt;Well, as I mentioned in the first part of this series, my main plan is to host this very website on the cluster, so let’s set that up now.&lt;/p&gt;

&lt;p&gt;Even though Raspberry Pi’s are amazing, they do have limitations and I want to squeeze every bit of performance and longevity out of my cluster. This essentially means that the site needs to be as lightweight as possible. While I could just install WordPress or Drupal on the cluster, this means that I would have to install PHP and MySQL, which will add quite a big strain to the Pi’s and their relatively fragile SD cards.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/docker-jekyll.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/docker-jekyll.jpg&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;h3 id=&quot;jekyll&quot;&gt;Jekyll&lt;/h3&gt;

&lt;p&gt;The solution is to keep everything simple, all we want to serve from the site is plain on HTML, without an expensive (in terms or performance) CMS using up all the memory. This is where &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; comes in, it’s a static site generator that spits out plain HTML. Ideal.&lt;/p&gt;

&lt;p&gt;Jekyll is a ruby gem that has been around for ages and works by compiling markdown templates into a static HTML site. It’s pretty simple to setup, if you are following along, checkout the &lt;a href=&quot;https://jekyllrb.com/docs/&quot;&gt;quick start guide&lt;/a&gt;. If everything goes well, you should end up with a HTML site in a folder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt;. We’ll need that next.&lt;/p&gt;

&lt;h3 id=&quot;packaging-up-the-website&quot;&gt;Packaging up the website&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; is an open source container orchestration tool, which is basically means it manages the deployment of virtual machines on clusters of computers. Containers can contain pretty much anything and there many different flavours of containers, but I’m going to use &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; and I’m going to run an Nginx instance within the container to host my shiny new Jekyll website.&lt;/p&gt;

&lt;p&gt;It’s actually quite a straight-forward process, in the root of the Jekyll site (i.e the parent folder of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt;), create a new file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; and add the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM arm64v8/nginx

COPY _site /usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s probably about as simple as a Dockerfile can get. To explain, the first line tells Docker to use &lt;strong&gt;nginx&lt;/strong&gt;, but not just the standard image - as we are deploying this on 64 bit Raspberry Pi’s, we have to use a specific version that is compatible with the Pi’s ARM based processor architecture. This is what the &lt;em&gt;arm64v8&lt;/em&gt; part signifies.&lt;/p&gt;

&lt;p&gt;The second line simply copies our Jekyll website to the default root folder of nginx.&lt;/p&gt;

&lt;p&gt;Now we need to build our new Docker container. The best way to do this is to install the &lt;a href=&quot;https://www.docker.com/products/docker-desktop&quot;&gt;Docker Desktop&lt;/a&gt; app, this will give you access to the CLI. At this point, create an account too, we’ll need this in a sec. Once installed, build the container:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build -t &amp;lt;username&amp;gt;/&amp;lt;containername&amp;gt; .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replace &lt;em&gt;&amp;lt;username&amp;gt;&lt;/em&gt; and &lt;em&gt;&amp;lt;containername&amp;gt;&lt;/em&gt; with the relevant info. This will take a few seconds, but once finished, we have successfully built our container image that hosts our Jekyll website.&lt;/p&gt;

&lt;h3 id=&quot;pushing-up-to-the-cloud&quot;&gt;Pushing up to the cloud&lt;/h3&gt;

&lt;p&gt;I had been toying with the idea of creating my own private docker container repository on the cluster itself. It would fit in with the project ethos of making everything as complicated as possible. But I want to be able to update the site from anywhere in the world and not just on my local network. I could create a VPN, but even by my standards, that is a bit overkill.&lt;/p&gt;

&lt;p&gt;A much better solution is to simply push my container image up to &lt;a href=&quot;https://hub.docker.com/&quot;&gt;DockerHub&lt;/a&gt;, which is a cloud based repository specifically for storing Docker images (as the name would suggest).&lt;/p&gt;

&lt;p&gt;If you created an account earlier when installing Docker Desktop, it’s quite easy. Make sure you are logged into your account in Docker Desktop, then run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker push &amp;lt;username&amp;gt;/&amp;lt;containername&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et volia, the image is pushed up to the cloud. There is one extra step I took here: I logged into DockerHub and found my repo, I then marked it as private. Each DockerHub account gets one free private repo in the free tier. As this is a cloud based system, it means that any public repos can be found and pulled down by anyone. So technically, if someone found our image, the could spin up their own version of our site pretty quick. This probably isn’t ideal.&lt;/p&gt;

&lt;p&gt;If you are following along, excellent work. Now we’re going to setup Kubernetes. Which is proper geeky. You’ll love it.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Part 5 - Installing and configuring Kubernetes&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 3 - Operating Systems</title>
   <link href="https://ianharris.co.uk/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/"/>
   <updated>2021-09-20T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/20/raspberry-pi-cluster-part-3-operating-systems</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the hardware is sorted, it’s time to turn my attention to the software side of the project. More accurately, the decision I need to make is which operating system I’m going to run on the Pi’s.&lt;/p&gt;

&lt;p&gt;There is actually a wider choice here that I originally anticipated, but I’ll keep this bit brief and explain my choices. I worked my way through a couple of options before settling on my operating system of choice.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-logo.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-logo.jpg&quot; alt=&quot;&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;&lt;/p&gt;
  
&lt;/div&gt;

&lt;h3 id=&quot;the-initial-experiments&quot;&gt;The initial experiments&lt;/h3&gt;

&lt;p&gt;To keep things small and simple, one requirement that I scoped out straight-away was that there is no need for any sort of GUI. I want to utilise as much of the onboard power of the Pi’s to run the cluster software, there isn’t any need to waste it on a desktop, etc.&lt;/p&gt;

&lt;p&gt;I originally installed the 32 bit version of the official &lt;a href=&quot;https://www.raspberrypi.com/software/&quot;&gt;Raspberry Pi OS&lt;/a&gt;, but I really wanted to utilise the full 64 bit goodness of the Arm64 processor that the Raspberry Pi’s have, so I rebuilt all of the Pi’s with Ubuntu 21.04 which supports 64 bit. However, and I’m aware that this sounds a little weird, but it just didn’t feel right not running the dedicated Pi OS.&lt;/p&gt;

&lt;p&gt;So third time lucky, I rebuilt all of the SD cards again, this time using the 64 bit edition of Raspberry Pi OS.&lt;/p&gt;

&lt;p&gt;Even though Kubernetes (or more accurately, the &lt;a href=&quot;https://rancher.com/products/k3s&quot;&gt;Rancher K3S&lt;/a&gt; flavour of Kubernetes) does work on 32 bit OS’s, it seems to be much happier on the 64 bit OS’s and most of the docker images that I originally worked with all were built for 64 bit architectures, so this seems like the best way to go.&lt;/p&gt;

&lt;h3 id=&quot;installing-raspbien-os-64-bit&quot;&gt;Installing Raspbien OS 64 bit&lt;/h3&gt;

&lt;p&gt;At the time of writing, the 64bit version of the Raspberry Pi OS is still not officially published by the Raspberry Pi Foundation, but it is available to download here: &lt;a href=&quot;https://downloads.raspberrypi.org/raspios_arm64/images/&quot;&gt;https://downloads.raspberrypi.org/raspios_arm64/images/&lt;/a&gt;. Grab the latest .zip and extract the files.&lt;/p&gt;

&lt;p&gt;While you’re there, if you haven’t already, download the Raspberry Pi Imager application too as this makes pushing the OS to the SD card a breeze.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-imager.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-imager.jpg&quot; alt=&quot;The very handy Raspberry Pi Imager&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;The very handy Raspberry Pi Imager&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Click on the &lt;strong&gt;Choose OS button&lt;/strong&gt;, scroll to the bottom of the list, select the &lt;strong&gt;Use custom&lt;/strong&gt; option and load up the Raspberry Pi OS 64 image we downloaded earlier. Similarly, click the &lt;strong&gt;Choose Storage&lt;/strong&gt; button and select the SD card that you want to write to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DON’T CLICK THE WRITE BUTTON YET!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As with any OS install, we need to do some configuration. Fortunately, there is a hidden menu in the app that gives us an easy way to configure the most important settings. On a Mac, if you press &lt;strong&gt;Ctrl + Shift + x&lt;/strong&gt;, you’ll get a new menu popup (I’m not sure what this would be on Windows, I’m sure a quick Google will sort you out).&lt;/p&gt;

&lt;p&gt;Now, check &lt;strong&gt;Set hostname&lt;/strong&gt; and give the Pi a unique hostname. I called my first Pi &lt;em&gt;einstein-master&lt;/em&gt;, then the next three &lt;em&gt;einstein-worker-01&lt;/em&gt;, &lt;em&gt;einstein-worker-02&lt;/em&gt; and &lt;em&gt;einstein-worker-03&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, check &lt;strong&gt;Enable SSH&lt;/strong&gt;, I choose the &lt;strong&gt;Use password authentication&lt;/strong&gt; and then set a good password. I’m actually going to setup the public key authentication once the Pi is online, but this means that I can set a good sudo password now. The rest you can leave, unless of course you wish to add some more config now.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt;, now you can click that &lt;strong&gt;Write&lt;/strong&gt; button to image the SD card.&lt;/p&gt;

&lt;p&gt;Just for your info, this setup can all be done on the Pi itself and when I built the 32 bit versions, I did all this configuration manually, but it’s much quicker to do it this way.&lt;/p&gt;

&lt;h3 id=&quot;setting-up-the-pis&quot;&gt;Setting up the Pi’s&lt;/h3&gt;

&lt;p&gt;We’re getting there now! Stick the SD card into the Pi and power it up. To be honest, I’m not sure if it is necessary to have a static IP for the Pi, but at the very least, it makes it a lot easier to manage. So set a static IP by which ever means floats your boat. I just log into my router and find the IP address and then set a DHCP reservation so that the Pi gets the same IP all the time.&lt;/p&gt;

&lt;p&gt;Now we know the IP, we can transfer over our public key to secure up the Pi a little bit better. If you don’t have a key setup, &lt;a href=&quot;https://www.raspberrypi.com/documentation/computers/remote-access.html#generate-new-ssh-keys&quot;&gt;follow these instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Copy over the key with (insert the IP address of your Pi):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh-copy-id pi@&amp;lt;IP-ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can now login to the Pi:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh pi@&amp;lt;IP-ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Kubernetes requires the control groups kernel feature to be enabled (&lt;a href=&quot;https://kubernetes.io/docs/setup/production-environment/container-runtimes/&quot;&gt;more info here&lt;/a&gt;), run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo nano /boot/cmdline.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And add the following to the end of the line, making sure that everything is on the same line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Save and close. Next, we can reduce the amount of memory that is allocated to the GPU as we don’t require any graphical features. Run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo nano /boot/config.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And add the following to the end of the file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gpu_mem=16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Almost there! Next we need to disable the SWAP file, again this is for Kubernetes, run each of the following commands:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dphys-swapfile swapoff
sudo dphys-swapfile uninstall
sudo apt purge dphys-swapfile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I mentioned earlier that we are going to disabled password authentication to increase the security, run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And update the following option (you may need to removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; at the beginning of the line):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PasswordAuthentication no
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, let’s update everything (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-y&lt;/code&gt; automatically says yes to every question):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt dist-upgrade -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is probably take a little while, but we are done configuring the Pi. We just need to run reboot the Pi with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo reboot&lt;/code&gt; to make sure all of the new configuration options take effect.&lt;/p&gt;

&lt;p&gt;Rinse and repeat for all of your Pi’s, they all need the same setup.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Part 4 - Setting up Jekyll and Docker&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 2 - Assembly</title>
   <link href="https://ianharris.co.uk/2021/09/18/raspberry-pi-cluster-part-2-assembly/"/>
   <updated>2021-09-18T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/18/raspberry-pi-cluster-part-2-assembly</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/15/raspberry-pi-cluster-part-1-hardware/&quot;&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here was the bit I was looking forward too, actually sticking all the bits together to build the Raspberry Pi cluster. It’s a pretty straight-forward process, it’s just a case of following the instructions - a bit like Lego.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-build-1.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-build-1.jpg&quot; alt=&quot;Putting together the layers of the cluster&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;Putting together the layers of the cluster&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Start by bolting the Pi’s onto the acrylic layers of the case. At this point, stick the heatsinks onto the Pi’s chips as it will be obviously harder to do this later down the line.&lt;/p&gt;

&lt;p&gt;Using the case kit stand offs, attached the layers together to build the Pi stack (there must be a gag in there somewhere).&lt;/p&gt;

&lt;p&gt;Finally attach the mini CAT5 cables to the switch and the USB cables to the powered hub. That’s, err, pretty much it.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-build-2.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-build-2.jpg&quot; alt=&quot;The assembled Raspberry Pi cluster. Pretty isn&apos;t it.&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;The assembled Raspberry Pi cluster. Pretty isn&apos;t it.&lt;/p&gt;
  
&lt;/div&gt;

&lt;h3 id=&quot;major-problem-no-1&quot;&gt;Major problem No. 1&lt;/h3&gt;

&lt;p&gt;Every was going great, until I turned it all on. The timeline of the posts goes a little out of order here, as I didn’t find this problem out until I had created the operating system images, but it makes sense to talk about this here in this post.&lt;/p&gt;

&lt;p&gt;The problem was that only two of the Pi’s turned on, the other two kind of flickered on and off, but mainly stayed off. Then one of the good Pi’s turned itself off and one of the dead ones sprang into life. And so on.&lt;/p&gt;

&lt;p&gt;To be completely honest with you, I had a feeling that the powered USB Hub that I dug out of junk cupboard might not cut the mustard. Turns out that was the case, it simply couldn’t supply enough power for all of the Pi’s to be on at the same time.&lt;/p&gt;

&lt;h3 id=&quot;the-power-solution&quot;&gt;The power solution&lt;/h3&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-build-3.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-build-3.jpg&quot; alt=&quot;Anker PowerPort 5. Much more capable.&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;Anker PowerPort 5. Much more capable.&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;What I needed is a power supply that is designed to delivery at least 2A on each port simultaneously and a quick search turned up the Anker PowerPort. This is much more suitable to powering the Pi’s. Now all the Pi’s can run happily, even when under a high load. Problem solved.&lt;/p&gt;

&lt;p&gt;As the new power supply has 5 ports, that left a spare port and that got me thinking. I wondered if I could power the Netgear switch via USB as well? Another quick search later and it turns out you can!&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-build-4.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-build-4.jpg&quot; alt=&quot;This tiny cable made my OCD much happier.&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;This tiny cable made my OCD much happier.&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Now I only need one power cable for the whole cluster and all the ports on both the PSU and the switch are filled. The whole thing looks better and that makes me very happy.&lt;/p&gt;

&lt;p&gt;Here was additional shopping list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Anker USB Charger PowerPort 5  (&lt;a href=&quot;https://www.amazon.co.uk/dp/B00VTI8K9K&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;USB Type A Male to DC Plug Barrel Connector (&lt;a href=&quot;https://www.amazon.co.uk/gp/product/B07CZ4M4V4&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This added another £30 to the hardware budget.&lt;/p&gt;

&lt;p class=&quot;message&quot;&gt;
  SPOILER: This STILL isn&apos;t the final setup. There are more updates needed, but I&apos;ll cover that in a future post.
&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-build-5.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-build-5.jpg&quot; alt=&quot;A rather bad photo of the cluster on my desk, taken before I realised I could power the switch from the Anker PowerPort as well.&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;A rather bad photo of the cluster on my desk, taken before I realised I could power the switch from the Anker PowerPort as well.&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Now it’s time to work out what I’m actually going to do with this thing.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Part 3 - Installing the operating systems&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Raspberry Pi Cluster, Part 1 - Hardware</title>
   <link href="https://ianharris.co.uk/2021/09/15/raspberry-pi-cluster-part-1-hardware/"/>
   <updated>2021-09-15T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/09/15/raspberry-pi-cluster-part-1-hardware</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Raspberry Pi Cluster, Part 1 - Hardware&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Raspberry Pi Cluster, Part 2 - Assembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/20/raspberry-pi-cluster-part-3-operating-systems/&quot;&gt;Raspberry Pi Cluster, Part 3 - Operating Systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/22/raspberry-pi-cluster-part-4-jekyll-and-docker/&quot;&gt;Raspberry Pi Cluster, Part 4 - Jekyll and Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/25/raspberry-pi-cluster-part-5-kubernetes/&quot;&gt;Raspberry Pi Cluster, Part 5 - Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/09/29/raspberry-pi-cluster-part-6-dynamic-dns/&quot;&gt;Raspberry Pi Cluster, Part 6 - Dynamic DNS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/10/04/raspberry-pi-cluster-part-7-ssl/&quot;&gt;Raspberry Pi Cluster, Part 7 - SSL&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2021/11/07/raspberry-pi-cluster-part-8-automation/&quot;&gt;Raspberry Pi Cluster, Part 8 - Automation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;

&lt;p&gt;Ever since I first saw a &lt;a href=&quot;https://en.wikipedia.org/wiki/Raspberry_Pi&quot;&gt;Raspberry Pi&lt;/a&gt; back in 2012, I’ve been a little bit obsessed with them. I don’t know if it was simply because of it’s size, it’s super low price or the endless possibilities that it gives an enquiring mind, but I remember how my mind was blown by this credit card size computer that cost around £20 (if my memory serves me correct).&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/raspberry-pi-1.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/raspberry-pi-1.jpg&quot; alt=&quot;My original Raspberry Pi 1B&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;My original Raspberry Pi 1B&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Fast forward a few years. I started to read more and more about &lt;a href=&quot;https://www.pidramble.com/&quot;&gt;clustering Raspberry Pi’s&lt;/a&gt; and how people have built some amazing projects on their clusters, so I decided that I definitely, 100% needed one in my life. In this series of blog posts, I’m going to detail the journey and the approach that I took to get the cluster online.&lt;/p&gt;

&lt;p&gt;If this project goes anything like most of my previous projects, it will most likely be totally over-the-top and unnecessarily complicated. But that’s fine with me, it’s all about the learning and fixing issues as I find them.&lt;/p&gt;

&lt;p&gt;This post is a bit meta - it has been written before I have completed the project. The plan is to host this site on the cluster itself, so if you are reading this, it means I must have worked it all out and got the site live.&lt;/p&gt;

&lt;p&gt;Well done future me!&lt;/p&gt;

&lt;h3 id=&quot;what-this-series-is-and-isnt&quot;&gt;What this series is and isn’t&lt;/h3&gt;

&lt;p&gt;The series is intended to document what I did to get this cluster up and running, it’s not designed to be a step-by-step tutorial. There are plenty of other tutorials like this out there on the Internet, so have a look around and you’ll find a few such tutorials.&lt;/p&gt;

&lt;p&gt;However, it’s worth saying that it is a still a pretty niche subject and the tutorials do vary in quality. Also, bear in mind that the hardware and software is updated regularly, so what worked a year ago may not (actually, that is more like probably won’t) work today. It should give you a good start through.&lt;/p&gt;

&lt;h3 id=&quot;the-hardware-list&quot;&gt;The hardware list&lt;/h3&gt;

&lt;p class=&quot;message&quot;&gt;
  SPOILER: This isn&apos;t the final setup. If you are following along, read the entire series first as there are many issues that I had to resolve before I got it all working.
&lt;/p&gt;

&lt;p&gt;The main choice I had to make at the beginning was which flavour of Raspberry Pi I was going to use. At the time of writing, the Pi 4 has been around for a few years and is pretty much the recommended base for a Pi Cluster (Alex Ellis recommends &lt;a href=&quot;https://blog.alexellis.io/self-hosting-kubernetes-on-your-raspberry-pi/&quot;&gt;giving away the Pi 3b+’s&lt;/a&gt; as they are not viable anymore), but a few of the tutorials out there do use the older Pi 3b+ boards.&lt;/p&gt;

&lt;p&gt;One of the main objectives of the project was to keep the costs down as much as possible. As I already owned a couple of Pi 3b+ boards, I decided to simply purchase a couple more of these to build a 4 node Pi 3b+ cluster.&lt;/p&gt;

&lt;p&gt;So here was my initial shopping list:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;4 x Raspberry Pi 3b+ (2 I already owned, 2 I bought second-hand on eBay)&lt;/li&gt;
  &lt;li&gt;Netgear GS305 Switch (&lt;a href=&quot;https://www.amazon.co.uk/NETGEAR-Ethernet-Unmanaged-Internet-Splitter/dp/B07PYSNSDD/&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;4 layer Raspberry Pi cluster case (&lt;a href=&quot;https://www.amazon.co.uk/dp/B07P2VLJXV&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;5 x 0.25m CAT5 cables (&lt;a href=&quot;https://www.amazon.co.uk/gp/product/B007AT4GSQ/&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;4 x 0.25m microUSB cables (&lt;a href=&quot;https://www.amazon.co.uk/gp/product/B079DCSG19&quot;&gt;Amazon&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;4 x 8Gb mini SD cards (already owned)&lt;/li&gt;
  &lt;li&gt;Belkin powered USB hub (already owned)&lt;/li&gt;
  &lt;li&gt;4 x Pi 3b+ heatsink kits (&lt;a href=&quot;https://shop.pimoroni.com/products/heatsink?variant=16851450375&quot;&gt;Pimoroni&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All in, I spent around £110 on the extra bits, on top of the parts I already owned.&lt;/p&gt;

&lt;div class=&quot;image&quot;&gt;
  &lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;/assets/images/hardware-kit.webp&quot; /&gt;
    &lt;img src=&quot;/assets/images/hardware-kit.jpg&quot; alt=&quot;The individual hardware components&quot; /&gt;
  &lt;/picture&gt;
  
  &lt;p class=&quot;caption&quot;&gt;The individual hardware components&lt;/p&gt;
  
&lt;/div&gt;

&lt;p&gt;Now I have all the components, it’s time to stick them all together to build myself an awesome little super computer.&lt;/p&gt;

&lt;p&gt;Next, &lt;a href=&quot;/2021/09/18/raspberry-pi-cluster-part-2-assembly/&quot;&gt;Part 2 - Assembling the cluster&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Introducing My Blog</title>
   <link href="https://ianharris.co.uk/2021/08/21/introducing-my-blog/"/>
   <updated>2021-08-21T00:00:00+00:00</updated>
   <id>https://ianharris.co.uk/2021/08/21/introducing-my-blog</id>
   <content type="html">&lt;p&gt;Welcome to my blog!&lt;/p&gt;

&lt;p&gt;This is a place where I am sharing my notes and progress on a number of different projects that I have on the go. In all honesty, it exists primarily so I remember what I did and I can refer back to when needed. But if you are here and this is useful to you, then that is great!
&lt;!--more--&gt;&lt;/p&gt;

&lt;h3 id=&quot;why-did-you-bother&quot;&gt;Why did you bother?&lt;/h3&gt;

&lt;p&gt;Good question, thanks for asking. I like a challenge and I like learning even more. There are a (literal) million ways to get a simple blog online, but I wanted to develop a place where I can learn new skills and have fun learning at the same time.&lt;/p&gt;

&lt;p&gt;I’m a web developer who likes to get involved in the tech side of things too. You can expect to see many web based projects, but also a lot of hardware based ones too. I’ve started dabbling in electronics, so expect more posts about that. I’m also currently building various garden buildings and features, so that will probably feature on here too. Basically, anything that I’m working on that may prove useful to someone else, I’ll probably post on here.&lt;/p&gt;

&lt;p&gt;I’ve used the Internet to pretty much self-teach myself everything I know. It will be nice to give as much of that knowledge back to future generations of builders and makers.&lt;/p&gt;

&lt;h3 id=&quot;what-is-coming-next&quot;&gt;What is coming next?&lt;/h3&gt;

&lt;p&gt;The first series of posts will be a detailed rundown of how this site was built, where it is hosted (SPOILER: it’s self hosted on a rather awesome Raspberry Pi cluster) and the challenges I faced in getting it up and running.&lt;/p&gt;

&lt;p&gt;The chances are that when you are reading this, you are being served this very post from a shelf in my office, in which I am probably working away building a website for a client (or working on one of the many ongoing projects that I seem to be amassing).&lt;/p&gt;

&lt;p&gt;I think that is pretty awesome.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;a href=&quot;/about&quot;&gt;about page&lt;/a&gt; for more information about the site and the technology used to bring this article to the world.&lt;/p&gt;

&lt;p&gt;Thanks for reading, please feel free to &lt;a href=&quot;mailto:hello@ianharris.co.uk&quot;&gt;get in touch&lt;/a&gt;!&lt;/p&gt;
</content>
 </entry>
 

</feed>
