MetalLB is the de facto standard for providing load balancing services to bare-metal Kubernetes clusters, and K3s is a popular choice for running Kubernetes on-premises or on edge devices. Together, they solve a fundamental problem: how do you expose your cluster’s services to the outside world when you don’t have an external cloud provider’s load balancer to do it for you?

Here’s K3s with MetalLB in action. Imagine we have a simple Nginx deployment running in our K3s cluster.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

And we want to expose it via a LoadBalancer type service.

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: LoadBalancer

Normally, if you’re on a cloud provider, the cloud controller manager would see this LoadBalancer service and provision a cloud load balancer. But on bare metal, there’s no such integration. This is where MetalLB comes in.

MetalLB operates in two main modes: Layer 2 and BGP.

Layer 2 Mode: In Layer 2 mode, MetalLB assigns an IP address from a configured pool to your LoadBalancer service. It then uses ARP (on IPv4) or NDP (on IPv6) to announce that it owns this IP address. When traffic arrives at the MetalLB pod (which runs on one of your nodes), it forwards the traffic to the correct Kubernetes service and pod. The key here is that only one MetalLB pod is active at a time for a given IP address, acting as the single point of contact.

BGP Mode: BGP (Border Gateway Protocol) is a more advanced and scalable option. Instead of using ARP/NDP, MetalLB establishes BGP peering with your network routers. It then advertises the IP addresses of your LoadBalancer services to your network. This allows your routers to direct traffic directly to the node running the MetalLB pod that is responsible for that service. This is more resilient and efficient as it avoids the single point of failure inherent in Layer 2 mode’s ARP announcements.

To get MetalLB running with K3s, you typically install it as a manifest. K3s has excellent built-in support for MetalLB. You can enable it during K3s installation or add it later.

First, ensure your K3s installation has MetalLB enabled. If you’re installing K3s, you can use the --disable traefik flag if you’re using a different ingress controller, and then install MetalLB separately. Or, if you’re upgrading an existing K3s, you might need to add it.

The simplest way to get MetalLB is to apply its manifests directly. You can find the latest release manifests on the MetalLB GitHub page. For example, a typical installation might look like this:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml

This command deploys MetalLB’s controller and speaker components into your cluster. The controller manages IP address allocation and BGP peering (if configured), while the speaker pods are responsible for announcing the IP addresses on the network.

Next, you need to configure MetalLB with an IP address pool. This is done via a ConfigMap.

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.1.240-192.168.1.250

Apply this ConfigMap to your cluster:

kubectl apply -f metallb-config.yaml

Now, when you create a LoadBalancer service like the nginx-service defined earlier, MetalLB will automatically pick an IP address from the 192.168.1.240-192.168.1.250 range and assign it to the service. You can verify this by checking the service’s status:

kubectl get service nginx-service

You should see an EXTERNAL-IP assigned. This IP is now reachable from outside your cluster, directing traffic to your Nginx pods.

The most surprising true thing about MetalLB’s Layer 2 mode is how it achieves high availability. While it appears a single node is handling ARP/NDP, MetalLB uses a leader election mechanism among its speaker pods. When the active speaker fails, another speaker pod quickly takes over the ARP/NDP responsibility, minimizing downtime.

The exact levers you control are the IP address pools and the protocol (Layer 2 or BGP). For BGP, you’ll need to configure peering details, including AS numbers and peer IP addresses, within the MetalLB ConfigMap. The choice between Layer 2 and BGP depends on your network infrastructure and requirements for scalability and resilience.

The next concept you’ll likely encounter is managing ingress traffic for HTTP/S services, which often involves an Ingress controller that sits behind MetalLB.

Want structured learning?

Take the full K3s course →