
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
MetalLB: Load Balancing for Bare Metal solves a gap every on-prem Kubernetes operator hits early. Cloud clusters get external IPs from the provider's load balancer. Bare-metal nodes have no such integration. Your type: LoadBalancer Services sit in Pending forever unless you add a controller. MetalLB assigns routable IPs from a pool you control and advertises them on your LAN. If you already run Kubernetes on bare metal with MetalLB, this page walks through install, mode choice, and the failures I see in production.
What is MetalLB and why do bare-metal Kubernetes clusters need it?
Kubernetes defines a LoadBalancer Service type for north-south traffic. On AWS, GCP, or Azure, the cloud controller provisions an ELB or equivalent and writes the VIP into status.loadBalancer.ingress. On a rack in Kathmandu or a colo in Singapore, that controller does not exist. The Service never gets an external address.
MetalLB fills that role. It watches Services of type LoadBalancer. It picks an IP from an IPAddressPool. It configures node interfaces or BGP sessions so clients on your LAN can reach that IP. The kube-proxy datapath on each node still handles forwarding to Pods. MetalLB only owns IP assignment and network advertisement.
I have deployed this pattern on client infrastructure where Linux system administration and application hosting share one small ops team. The alternative is NodePort plus an external HAProxy or nginx layer. That works, but it duplicates config. Every new Service needs manual front-end rules. MetalLB keeps the cloud-native workflow intact on bare metal.
MetalLB runs two logical components. The controller allocates IPs and updates Service status. The speaker DaemonSet handles Layer 2 replies or BGP peering on each node. You install both via Helm or manifests. They need cluster-wide RBAC to watch Services and Endpoints.
Compare this to HAProxy load balancing sitting outside the cluster. HAProxy terminates TLS, applies ACLs, and balances across NodePorts. MetalLB is thinner. It makes Kubernetes believe it has a cloud load balancer. Many teams use both: MetalLB for internal platform Services, HAProxy or an ingress controller for HTTP edge traffic.
How do you install MetalLB in a Kubernetes cluster?
MetalLB requires a working Kubernetes cluster with kube-proxy enabled. It does not replace CNI. Calico, Cilium, Flannel, or kube-router all work. Verify your cluster version supports the CRDs MetalLB ships. The project tracks recent Kubernetes releases; check the release notes before upgrading either side.
Install with Helm
Helm is the path I use on fresh clusters. Add the repo and install into a dedicated namespace:
helm repo add metallb https://metallb.github.io/metallb
helm repo update
kubectl create namespace metallb-system
helm install metallb metallb/metallb -n metallb-system Wait for pods to become ready:
kubectl -n metallb-system get pods -w You should see controller and speaker pods running on all eligible nodes. Speakers need host network access for Layer 2 and BGP. Do not schedule them on control-plane nodes unless you accept that trade-off in small lab clusters.
Install with manifests
If Helm is unavailable, apply upstream manifests directly:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml Pin the version tag to a release you have tested. Blindly tracking main breaks production during API changes. After apply, confirm CRDs exist:
kubectl get crd | grep metallb Expect ipaddresspools.metallb.io, l2advertisements.metallb.io, and bgpadvertisements.metallb.io at minimum.
Pre-flight checklist
- Reserve a contiguous IP range on the same L2 segment as worker nodes.
- Confirm no DHCP server will assign those addresses.
- Ensure firewall rules allow traffic to NodePort ranges if you mix types.
- Document which VLAN or subnet each pool maps to for future operators.
- Disable or avoid cloud provider integrations that fight for Service status.
For enterprise application development projects that later move to managed Kubernetes, MetalLB config does not port over. Treat pools and advertisements as environment-specific. Store them in Git beside your other cluster manifests.
Which MetalLB mode should you use: Layer 2 or BGP?
MetalLB supports two advertisement modes. Layer 2 is simpler. BGP scales better across routers and data centres. Pick based on network team capability, not blog hype.
| Criteria | Layer 2 | BGP |
|---|---|---|
| Setup complexity | Low — no router config | High — needs peering sessions |
| Failover speed | Seconds (gratuitous ARP) | Sub-second with proper tuning |
| Multi-subnet reach | Single L2 domain only | Routes propagate across L3 |
| Load spread | One node owns each VIP | ECMP can share across nodes |
| Best fit | Lab, single rack, small office | Colo, multi-rack, ISP-facing |
Layer 2 elects one node as leader for each Service IP. That node answers ARP for the VIP. If the node dies, another speaker takes over and sends gratuitous ARP. Clients refresh their cache at different speeds. Expect a brief blip during failover. For internal dashboards that is usually fine.
BGP mode peers each speaker with your top-of-rack switch or border router. MetalLB announces /32 host routes for Service IPs. Routers ECMP-hash flows across nodes. Failover is faster because routing tables update without waiting for ARP timeouts. Your network team must approve ASN, hold timers, and prefix limits.
Read load balancing algorithms compared when you design the layer above MetalLB. MetalLB does not pick Pod targets. It only exposes the Service VIP. kube-proxy or IPVS applies round-robin or session affinity rules downstream.
Hybrid setups appear in larger deployments. Internal Services use Layer 2 on a management VLAN. Public-facing ingress uses BGP toward edge routers. MetalLB allows multiple pools and advertisements scoped by namespace labels.
How do you configure MetalLB IPAddressPool and L2Advertisement?
Modern MetalLB uses CRDs instead of a single ConfigMap. You define pools, then link them to advertisements. A minimal Layer 2 setup for a lab might look like this.
Define an IP address pool
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.50.240-192.168.50.250
autoAssign: true
avoidBuggyIPs: false Replace the range with addresses your network admin reserved. The pool must sit on the same subnet as node interfaces for Layer 2. For BGP, the range can be any routable block your upstream accepts.
Advertise with Layer 2
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: production-l2
namespace: metallb-system
spec:
ipAddressPools:
- production-pool
nodeSelectors:
- matchLabels:
kubernetes.io/os: linux Apply both manifests:
kubectl apply -f ipaddresspool.yaml
kubectl apply -f l2advertisement.yaml Expose a test Service
apiVersion: v1
kind: Service
metadata:
name: nginx-lb
namespace: default
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80 Watch until MetalLB assigns an IP:
kubectl get svc nginx-lb -w The EXTERNAL-IP column should show an address from your pool within seconds. Curl it from a host on the same network:
curl -v http://192.168.50.240 BGP configuration sketch
BGP requires a BGPPeer resource and a BGPAdvertisement. Example peer toward a router at 10.0.0.1:
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: tor-switch
namespace: metallb-system
spec:
myASN: 64512
peerASN: 64513
peerAddress: 10.0.0.1
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: production-bgp
namespace: metallb-system
spec:
ipAddressPools:
- production-pool Coordinate ASN and password with your network team. Misconfigured BGP can blackhole production prefixes. Test in a maintenance window.
Validate YAML with a JSON formatter or kubeconform before apply. A typo in addresses can exhaust DHCP or clash with gateway IPs. I log every pool change in Git alongside service discovery and load balancing docs so the next deploy is reproducible.
On platforms like Adventure Third Pole Trek, external traffic often terminates at nginx before it reaches the cluster. MetalLB still helps for internal gRPC, metrics, and staging namespaces that need stable VIPs without editing edge configs.
How do you troubleshoot MetalLB when LoadBalancer services stay pending?
A stuck Pending external IP is the most common support ticket. Work through these checks in order. Most issues are pool misconfiguration or RBAC, not MetalLB bugs.
Verify controller logs
kubectl -n metallb-system logs deploy/metallb-controller
kubectl -n metallb-system logs daemonset/metallb-speaker --tail=50 Look for "no available IPs" or "pool not found". Those messages point to CRD gaps. Confirm the pool namespace matches where MetalLB watches resources.
Common failure modes
- Empty or exhausted pool — every IP is already assigned. Expand the range or delete unused Services.
- Wrong subnet — Layer 2 cannot ARP across routers. Move the pool to the worker VLAN.
- kube-proxy disabled — VIP arrives but traffic blackholes. Ensure kube-proxy or a compatible replacement runs.
- Duplicate IP on LAN — another device uses the VIP. Ping the address before adding it to the pool.
- NetworkPolicy blocking — rare for external clients, but speakers need node-to-node traffic.
- Cloud controller conflict — hybrid clusters may need
spec.loadBalancerClassset on Services.
Run load testing with k6 after MetalLB is stable. A correct VIP does not guarantee Pod capacity. I pair MetalLB cutover with testing and optimization so latency spikes surface before launch day.
For deeper platform work, pair MetalLB with MAAS metal as a service for node provisioning and Tinkerbell bare-metal provisioning for image workflows. MetalLB assumes nodes already join the cluster. It does not PXE boot hardware.
Official references stay authoritative. The MetalLB project site documents CRD fields and upgrade notes. The Kubernetes LoadBalancer Service documentation explains how cloud and bare-metal controllers differ. Read both before changing production advertisements.
Security matters even on private LANs. Restrict who can create LoadBalancer Services via RBAC. A developer typo can grab your last free VIP and expose an admin API. Namespace quotas and OPA/Gatekeeper policies help. For API development stacks, expose only ingress controllers publicly. Keep databases and Redis on ClusterIP.
Upgrades deserve the same care as support and maintenance windows on Laravel or WordPress hosts. Snapshot MetalLB CRDs, drain speakers one node at a time, and verify a canary Service after each wave. Speaker pods use host networking; a bad rollout can flap ARP across the rack.
Cost-wise, MetalLB is free software. Your spend is IP space and engineer time. A small office might reserve a /28 on an existing VLAN at zero marginal cost. Colo BGP may need a cross-connect fee. Compare that to managed Kubernetes load balancers billed hourly. For budget-sensitive teams in Nepal, bare metal plus MetalLB often beats cloud egress fees. Use the Nepal EMI calculator when financing hardware leases against monthly cloud bills.
I treat MetalLB as infrastructure glue, not a product surface. Document pools in runbooks. Tag nodes that may speak BGP. Alert when pool utilisation crosses eighty percent. Those habits prevent 2 a.m. pages when marketing launches a new microsite and requests three more LoadBalancers.
Key Takeaways
- MetalLB gives bare-metal Kubernetes real LoadBalancer IPs without a cloud provider integration.
- Start with Layer 2 and a small IPAddressPool; move to BGP when your network team needs L3 routing.
- Install via Helm, define IPAddressPool plus L2Advertisement or BGPAdvertisement, then test with a simple nginx Service.
- Pending EXTERNAL-IP almost always means pool exhaustion, missing advertisement, or wrong subnet — check controller logs first.
- Pair MetalLB with ingress controllers or HAProxy for HTTP TLS; MetalLB handles VIP assignment only.
- Lock down RBAC on LoadBalancer Services and monitor pool usage before production traffic spikes.
People Also Ask
Does MetalLB work with all Kubernetes CNIs?
Yes, for standard setups. MetalLB operates above L2 or BGP and does not replace CNI Pod networking. Calico, Cilium, Flannel, and kube-router are common pairings. Confirm your CNI allows the traffic paths kube-proxy expects, especially if you run in IPVS mode or disable kube-proxy for eBPF dataplanes.
Can MetalLB assign the same IP to multiple Services?
MetalLB supports shared IP mode when Services use different ports on the same address. Configure allowSharedIP on the pool and align Service port sets. Two Services cannot bind the same IP and identical port tuple. Plan shared VIPs for ingress controllers that multiplex HTTP and HTTPS on one address.
Is MetalLB production-ready for bare metal?
MetalLB is widely deployed in production on-prem clusters worldwide. Success depends on correct pool planning, tested failover, and network team involvement for BGP. It is a CNCF sandbox project with active maintenance. Treat upgrades like any critical cluster component and maintain staging parity.
How does MetalLB compare to kube-vip or external HAProxy?
MetalLB integrates natively with the LoadBalancer Service type and supports large IP pools with BGP. kube-vip often targets control-plane VIPs and lighter Service exposure. External HAProxy gives richer L7 features but sits outside Kubernetes reconciliation. Many teams run MetalLB for Service VIPs plus HAProxy or nginx ingress for HTTP routing.
Ship LoadBalancer Services on your own hardware
MetalLB: Load Balancing for Bare Metal closes the biggest portability gap between cloud and on-prem Kubernetes. Reserve a pool, pick Layer 2 or BGP, install the controller, and your Services behave like they would on EKS or GKE. I have wired this into stacks that also need directory platforms and custom backends — the pattern scales from a three-node lab to a colo rack. Read more on the blog, review the portfolio, or contact us if you want help designing bare-metal Kubernetes networking for your next release.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

