Exposing Web Apps running in our Raspberry Pi cluster
Published on April 25, 2022
This post is a continuation of my previous blog post called Let's build low budget AWS at home. Here, I will be explaining how to run a blog in the cluster and expose it to the public internet. I will be using Nginx for that. The interesting thing is we will use both the webserver and reverse proxy pattern of Nginx.
Setting up MetalLB load balancer
MetalLB is a network load balancer, it is capable of assigning a private IP address in the same address spaces of your cluster Nodes are in to a running service. This step is mandatory as we need to get the traffic into the cluster from the home router. We are going to run MetalLB in layer 2 mode.
Go to MetalLB releases, Download the Source Code (zip) of the latest release. Unzip it and go to the manifests folder. Copy the namespace.yaml and metallb.yaml to a known folder location.
Run the following to create metallb resources in the cluster:
kubectl apply -f namespace.yaml
kubectl apply -f metallb.yaml
It will take one or two minutes to complete the operation. You can use the Kubernetes Dashboard to monitor it. Select the metallb-system from the namespace dropdown on the top-left.
We can give the range of IPs to be used as LoadBalancer type services. MetalLB accepts this configuration from the ConfigMap. Save the following to a file called metallb-configmap.yaml.
apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: address-pool-1 protocol: layer2 addresses: - 192.168.1.200-192.168.1.250
You should pick the range considering the private IPs that your local network has. Then run this command to apply the ConfigMap to the cluster:
kubectl apply -f metallb-configmap.yaml
Now you can start a sample service with the type as the LoadBalancer to check whether you get the external IP for the service. Let's check that later in this blog.
Setting up Nginx ingress controller
The next thing we need to have is an ingress controller where we get the outside traffic into the cluster. There are many ingress controller solutions as listed here. We are going to use nginx-ingress. This is the guide for bare metal installtion. We are going to run ingress-nginx-controller as a LoadBalancer service. We have already setup up MetalLB for the sake of doing this.
First copy the content from this file location and save it to nginx-ingres.yaml file. Then find the service for ingress-nginx-controller and change the type to LoadBalancer. It is defaulted to NordPort.
--- apiVersion: v1 kind: Service metadata: ... name: ingress-nginx-controller namespace: ingress-nginx spec: ... ... type: LoadBalancer ---
Now run the following command to get nginx-ingress up and running in our cluster.
kubectl apply -f nginx-ingres.yaml
Now we can check the service information by running the following:
kubectl describe svc ingress-nginx-controller -n ingress-nginx
The output will be similar to the following:
Name: ingress-nginx-controller Namespace: ingress-nginx Labels: app.kubernetes.io/component=controller app.kubernetes.io/instance=ingress-nginx app.kubernetes.io/name=ingress-nginx app.kubernetes.io/part-of=ingress-nginx app.kubernetes.io/version=1.2.0 Annotations: <none> Selector: app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx Type: LoadBalancer IP Family Policy: SingleStack IP Families: IPv4 IP: 10.96.204.70 IPs: 10.96.204.70 LoadBalancer Ingress: 192.168.1.200 Port: http 80/TCP TargetPort: http/TCP NodePort: http 30093/TCP Endpoints: 10.244.1.53:80 Port: https 443/TCP TargetPort: https/TCP NodePort: https 30725/TCP Endpoints: 10.244.1.53:443 Session Affinity: None External Traffic Policy: Cluster Events: <none>
If all went well then you should see a LoadBalancer Ingress has been assigned an IP address from the range we configured to MetalLB in the previous step.
Creating the blog and building container images
There are many ways you can build a blog. I suggest using SSG (Static Site Generator) like Gatsby. There are many themes and templates you can get started on. That will save you more time when extending the blog and focusing on writing blog posts.
Next, we need to build the docker image for the blog. The Dockerfile will be somewhat similar to the below. Where we finally copy the build files of the blog to be run in the Nginx container.
FROM node:latest as builder WORKDIR /app COPY package.json . RUN npm install COPY . . RUN npm run build FROM nginx EXPOSE 80 COPY /app/public /usr/share/nginx/html
When running this image in a Pod it will expose port
80 to the outside. These Pods running Nginx play the role of web servers (first pattern).
I used Github Actions to build the images for
ARM64 Architecture which is the Arch for Raspberry Pi 4. I'm not going to explain how to set up the blog and build images as it totally depends on how you create your blog. Here onwards I will consider
nginx:latest image as our blog to continue without any issue. You should use your actual image for your blog.
Running Blog Deployment
Kubernetes API object for releasing and/or rolling back applications (Ex: our blog) is called Deployments. It will run a ReplicaSet to control the desired number of Pods running. I will set the number of replicas as three. You can adjust this considering the traffic you get. Increasing the number of Pods might not be enough if the cluster does not have an adequate amount of resources to utilize. If you get higher traffic, you might need to add more Raspberry Pi boards to the cluster. Now, you see the idea of how-to scale cluster.
Save following to blog-deployment.yaml file.
apiVersion: apps/v1 kind: Deployment metadata: name: dev-diary spec: replicas: 3 selector: matchLabels: app: dev-diary template: metadata: labels: app: dev-diary spec: containers: - name: dev-diary image: nginx:latest ports: - containerPort: 80 name: http protocol: TCP imagePullPolicy: Always resources: limits: memory: 200Mi cpu: 100m requests: cpu: 100m memory: 200Mi
Apply the deployment to the cluster:
kubectl apply -f blog-deployment.yaml
Running Blog Service
Now, we need a service to get traffic from the ingress towards the Pods running our blog application. Save the following Service object blog-service.yaml file.
apiVersion: v1 kind: Service metadata: name: dev-diary-svc spec: selector: app: dev-diary ports: - protocol: TCP port: 9000 targetPort: 80
Run the following command to start the service in the cluster:
kubectl apply -f blog-service.yaml
Note that we are using port
9000 as the service
port where the service gets the traffic while
tartgetPort is where the traffic is sent to. It is
80 where we defined in Dockerfile under
EXPOSE and in the Deployment under
Since I have not defined the type for this service, it will default to
ClusterIP which is only accessible within the cluster.
Ingress Spec for Blog Service
So far so good, Next we need to get the traffic from the Nginx ingress controller to our dev-diary-svc. Save the following Ingress definition to host-ingress.yaml file.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: host-ingress annotations: nginx.ingress.kubernetes.io/use-regex: "true" spec: ingressClassName: nginx rules: - host: developer-diary.com http: paths: - path: /* pathType: Prefix backend: service: name: dev-diary-svc port: number: 9000
Run the following to apply the Ingress configuration:
kubectl apply -f host-ingress.yaml
Note that we are controlling the ingress based on the HTTP host header (which is set to the DNS domain in the original URL) and direct traffic based on that header. You can keep multiple rules if have multiple services running. You can change this file in the future when you add another application to your cluster.
So far so good!
DNS and Home Router Configuration
Since we are running the cluster from the home internet connection we need a few things to sort out. You need to have a static public IP address assigned to your router from your ISP. Normally ISPs has an IP pool and use DHCP to dynamically set IPs to routers. But in our case we need to add DNS records, so if the public IP changes then you have to reconfigure it whenever it changes.
I contacted my ISP and requested a public IP. They charged some additional amount from the monthly bill.
Now it's time to purchase a good domain name for your blog. I could buy one from Cloudflare Registrar. Next, add a
DNS A record for the public IP of your home router. Check whether DNS servers get updated with your records in DNS Checker. It will take some time. So, have some tea, come back, and check.
If you wanted to run some other application in the future you can do the same by adding a new record and updating the host-ingress.yaml with another rule. Here in the ingress controller, Nginx is running as a reverse proxy (second pattern) where it gets traffic and route to the correct upstream service.
Next, we need to port forward the incoming traffic to our home router. Here also I'm not going to explain specific details as it will be different according to your router. The basic steps are as below.
- Log in to your router. It will be served from
10.0.0.1depending on your router. If you don't have a password get help from the customer care of your ISP.
- Go to the settings page for forwarding rules and Port Map Configuration.
- Select type as application and select application as Web Server (HTTP).
- Set the Internal Host address as the private IP address we got for nginx-ingress-controller which is
- Set the protocol as
TCPand internal and external port number as
80. Add another mapping for port
- Apply the changes to the router.
Now, hit the browser with your domain name. It should load your blog. Tada, we have a system where we can host web apps and check.
The end of the story!
If you like it, share it!