5 min read

Installing MetalLB and NGINX Ingress Controller in Kubernetes

Installing MetalLB and NGINX Ingress Controller in Kubernetes

In our previous articles, we walked through the installation and configuration of a Kubernetes cluster on a bare-metal server and also configured a terminal-based dashboard tool K9s for easier access.

Installing Kubernetes: Building a Bare-Metal Cluster
In our previous articles, we explored setting up essential self-hosted media services like Jellyfin, TrueNAS, AdGuard Home, and others to build a comprehensive home media experience. Now, it’s time to take things a step further. In this article, we’ll explore the step-by-step process of installing Kubernetes on a bare-metal server,
Introducing K9s: Terminal-based Kubernetes Dashboard
In our previous article, we walked through the installation and configuration of a Kubernetes cluster on a bare-metal server right from scratch. Installing Kubernetes: Building a Bare-Metal ClusterIn our previous articles, we explored setting up essential self-hosted media services like Jellyfin, TrueNAS, AdGuard Home, and others to build a comprehensive

Now, let's take the next step: exposing services to the outside world in a clean way.

Kubernetes by default provides ClusterIP and NodePort services. These work well inside the cluster or for basic access, but what if we want to give our services a real IP address that can be accessed from our LAN or even mapped to domains?

This is where MetalLB and NGINX Ingress Controller comes in.

Why MetalLB and Ingress?

  • MetalLB: It is a load balancer implementation for bare-metal Kubernetes. Since we're not in a cloud environment (like AWS or GCP), we don't get a cloud load balancer. MetalLB fills this gap by assigning real IP addresses from our LAN to Kubernetes services of type LoadBalancer.
  • NGINX Ingress Controller: The Ingress Controller acts as a gateway into our cluster. Instead of exposing every app with a new LoadBalancer IP, we expose a single Ingress Controller service with one static IP, and then route multiple domains or subdomains to the right apps inside the cluster.

With this type of configuration, we can point app1.example.com, app2.example.com, etc. to a single IP and let Ingress handle the routing.

What We'll Cover

  1. Installing MetalLB in Kubernetes
  2. Configuring an IP Address Pool and L2Advertisement
  3. Deploying the NGINX Ingress Controller
  4. Assigning a Static IP to the Ingress Controller

Hardware / Setup Requirements

  • A working Kubernetes cluster (control plane + worker nodes) from our previous article.
  • All nodes must be in the same LAN subnet.
  • One free static IP in our home network (this will be reserved for the Ingress Controller)
  • DNS access for our domain if we plan to use real domains. (For HomeLabs we can use internal DNS like AdGuard Home, Pi-Hole)

Steps to install MetalLB:

  • We'll use the official manifests to install MetalLB.
# Install MetalLB components
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml

This will deploy MetalLB to our cluster, under the metallb-system namespace. The components in the manifest are:
1. The metallb-system/controller deployment - Cluster-wide controller that handles IP address assignments.
2. The metallb-system/speaker daemonset - Component that speaks the protocol(s) of our choice to make the services reachable.
3. Service accounts for the controller and speaker.

  • Check if MetalLB pods are running:
kubectl get pods -n metallb-system
  • Now that our pods are all running, we can proceed with configuring IPAddressPool and L2Advertisement
  • MetalLB needs to know which IP address it can allocate. Since our use case required just one IP, we'll configure it like this:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: ingress-pool
  namespace: metallb-system
spec:
  addresses:
    - 10.10.10.253-10.10.10.253   # Single IP
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: ingress-l2adv
  namespace: metallb-system
spec: {}

The first section IPAddressPool defines a pool of IP addresses that MetalLB is allowed to assign to services of the type LoadBalancer. These IPs should be within our local network and not in use by any other device. When we create a Kubernetes Service of type LoadBalancer, MetalLB will pick one IP from this pool and assign it to the service.

The second section L2Advertisement enables Layer 2 mode advertisement, which tells MetalLB to announce the allocated IPs on the local Layer 2 network. With L2 mode, MetalLB makes the chosen IP respond on the network as if it were a real device by sending response.

  • We can save the metallb config file and the apply it:
kubectl apply -f metallb-config.yaml

We have now successfully configured our MetalLB service to use the static IP that we have provided. We can now proceed with installing and configuring our NGINX Ingress Controller.

Steps to install NGINX Ingress Controller:

  • We can deploy our NGINX Ingress Controller via Helm chart:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=LoadBalancer \
  --set controller.service.loadBalancerIP=10.10.10.253

Here we are configuring service.type=LoadBalancer so that MetalLB will assign our static IP. Also, we explicitly set loadBalancerIP=10.10.10.253 so that we can ensuring that same IP is always used.

  • We can check the service status to confirm if the External IP column is showing our configured IP.
kubectl get svc -n ingress-nginx
💡
If the EXTERNAL-IP column still shows <pending>, double-check that your MetalLB IPAddressPool is correct and that the IP (10.10.10.253) is not already in use by another device on your LAN.

We can see our static IP in the EXTERNAL-IP column. We have now successfully configured our NGINX Ingress Controller. We will now be able to provide domain names and route various services using this Ingress service easily.

In this article, we've now successfully:

  • Installed MetalLB to provide a real IP in a bare-metal cluster
  • Configured a single static IP for our Ingress Controller
  • Installed the NGINX Ingress Controller with Helm

In the upcoming article, we’ll not only showcase how to route multiple apps through our Ingress Controller, but also take things further by installing Longhorn for distributed storage in our cluster. This will allow us to demonstrate a real-world use case: deploying stateful applications and accessing them via custom domains through our ingress setup.

Stay tuned for more detailed instructions on expanding our Home Labbing capabilities.

Happy Homelabbing!!!