author-pic

Amila Senadheera

Tech enthusiast

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 --from=builder /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 containerPort.

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.

  1. Log in to your router. It will be served from 192.168.1.1 or 10.0.0.1 depending on your router. If you don't have a password get help from the customer care of your ISP.
  2. Go to the settings page for forwarding rules and Port Map Configuration.
  3. Select type as application and select application as Web Server (HTTP).
  4. Set the Internal Host address as the private IP address we got for nginx-ingress-controller which is 192.168.1.200 for me.
  5. Set the protocol as TCP and internal and external port number as 80. Add another mapping for port 443 as well.
  6. 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!


© 2022, All Rights Reserved.

Made withusing Gatsby, served to your browser from a home grown Raspberry Pi cluster