A Kubernetes Service of type LoadBalancer works without a second thought on EKS, GKE, or AKS: the cloud provider provisions an external IP and routes it to the cluster. On an on-premise cluster, that same Service stays Pending indefinitely, EXTERNAL-IP never filled in, because no cloud provider exists to satisfy the request. It’s the most concrete and least anticipated trap of a migration from legacy on-premise infrastructure: the YAML is identical, the behavior isn’t.
What MetalLB does instead of a cloud provider
MetalLB implements itself what the cloud does elsewhere: it watches for unsatisfied LoadBalancer Services, assigns them an IP from a range you define, and announces that IP to the surrounding network so it becomes genuinely routable.
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.10.100-192.168.10.150
That range must be reserved nowhere else on the network (no DHCP, no other device should be able to assign these same addresses): MetalLB doesn’t negotiate ownership of them, it assumes they’re already spoken for.
Two advertisement modes, two network trade-offs
MetalLB offers two ways to announce an IP to the rest of the network, with different infrastructure implications.
Layer 2 mode makes a single node respond to ARP requests for the assigned IP: simple to set up, no particular network configuration needed, but all traffic for that IP flows through a single node at a time, a contention point that becomes a bottleneck past modest traffic. The speaker component responsible for that announcement actually runs as a DaemonSet, present on every eligible node so it can answer wherever the election designates.
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-production
namespace: metallb-system
spec:
ipAddressPools:
- production-pool
BGP mode announces each IP via the BGP protocol to one or more routers, which then genuinely distribute traffic across several nodes. The benefit (real load distribution, faster failover if a node goes down) has a real cost: it requires a router that speaks BGP and a network team willing to configure it, a dependency Layer 2 mode entirely avoids.
The choice that decides everything: Layer 2 first, BGP only if traffic demands it
Starting with Layer 2 is the reasonable default for most migrations: it works without touching existing network infrastructure, which matters when the network team and the platform team aren’t yet used to working together. Moving to BGP is justified only once actual traffic exceeds what a single node comfortably absorbs, never in anticipation of traffic that doesn’t exist yet.
What MetalLB doesn’t solve
MetalLB gives a Service a routable external IP, nothing more: it handles no TLS certificates, no hostname-based HTTP routing, no fine-grained application load balancing. Those responsibilities stay with an Ingress placed in front, which itself uses a MetalLB LoadBalancer Service as its network entry point. The two combine without conflict: MetalLB solves the network layer, Ingress solves the application layer.
Takeaway
A LoadBalancer Service stuck in Pending on an on-premise cluster isn’t a bug: it’s the expected absence of a cloud provider, which MetalLB explicitly fills. Layer 2 mode covers the vast majority of migrations without touching existing network infrastructure; BGP mode is only justified under real load that demands it. Getting this network foundation right is part of what’s at stake from the earliest steps of a Kubernetes migration from legacy infrastructure.