Raspberry Pi Cluster, Part 5 - Kubernetes
25 Sep 2021- Raspberry Pi Cluster, Part 1 - Hardware
- Raspberry Pi Cluster, Part 2 - Assembly
- Raspberry Pi Cluster, Part 3 - Operating Systems
- Raspberry Pi Cluster, Part 4 - Jekyll and Docker
- Raspberry Pi Cluster, Part 5 - Kubernetes
- Raspberry Pi Cluster, Part 6 - Dynamic DNS
- Raspberry Pi Cluster, Part 7 - SSL
- Raspberry Pi Cluster, Part 8 - Automation
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.
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 The Linux Foundation to do a much better job.
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 Rancher built a super lightweight stripped back version of Kubernetes called K3S that plays very nicely on Raspberry Pi’s.
Installing K3S
You’d think this part would be complicated, it couldn’t really be much easier. On your master node, run the following:
curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
Note the --write-kubeconfig-mode 644 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.
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:
kubectl get pods -A
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:
sudo cat /var/lib/rancher/k3s/server/token
Almost there! On each of the worker nodes, run the following:
curl -sfL https://get.k3s.io | K3S_URL=https://<server>:6443 K3S_TOKEN=<token> sh -
Where <server> is the IP address of your master node and <token> is the token we got from the master node in the previous step.
And once complete, the Raspberry Pi based Kubernetes cluster is complete!
Major problem No. 2
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.
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.
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.
Here was additional shopping list:
This added a further £63.90 to the budget.
Configuring our cluster
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.
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:
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-password> --docker-email=<your-email>
Replacing <your-registry-server>, <your-name>, <your-password> and <your-email> with your credentials. Kubernetes can now pull down our private image from DockerHub.
Setting up a Deployment
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 sudo nano deployment.yml and copy in the following:
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: <username>/<containername>:latest
imagePullPolicy: Always
name: blog-website
ports:
- containerPort: 80
imagePullSecrets:
- name: regcred
Replace blog-website 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 replicas. 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.
Secondly, replace <username> and <containername> with the image details that you created in the last post.
After you have saved and closed the manifest file, we need to pass it to Kubernetes. Run:
kubectl apply -f deployment.yml
After a few minutes, if you run kubectl get pods, you should see that 3 pods come online all running instances of our Docker container. Nice.
Setting up a Service
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 ClusterIP type. As before, let’s create a new file with sudo nano service.yml and copy in the following:
apiVersion: v1
kind: Service
metadata:
name: blog-website-service
spec:
selector:
app: blog-website
ports:
- protocol: TCP
port: 80
You can change the names to what you wish, just make sure they match between your different manifest files. Apply this service manifest with:
kubectl apply -f service.yml
Again, give it a few minutes and check your service is running with kubectl get service.
Setting up an Ingress
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 Traefik, so we’ll go with that.
As before, create a new file with sudo nano ingress.yml and copy in the following:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog-website-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: <external-domain>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: blog-website-service
port:
number: 80
As previously, make sure your names match what you put in the other manifest and replace <external-domain> with your own domain name that you want to use for your website.
Apply this service manifest with:
kubectl apply -f ingress.yml
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…
Setup port forwarding on network router
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 master node.
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.
Next, Part 6 - Dynamic DNS