Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

MetalLB: Load Balancing for Bare Metal

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 Bare-Metal FlowClientLAN / VLANMetalLB VIPFrom IP poolkube-proxyiptables/IPVSPodsKubernetes Cluster (Bare Metal)ControllerSpeakerWorker 1Worker 2No cloud LB — MetalLB assigns and announces VIPs
MetalLB: Load Balancing for Bare Metal — client traffic hits a pool IP, then kube-proxy forwards to Pods

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

  1. Reserve a contiguous IP range on the same L2 segment as worker nodes.
  2. Confirm no DHCP server will assign those addresses.
  3. Ensure firewall rules allow traffic to NodePort ranges if you mix types.
  4. Document which VLAN or subnet each pool maps to for future operators.
  5. 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.

CriteriaLayer 2BGP
Setup complexityLow — no router configHigh — needs peering sessions
Failover speedSeconds (gratuitous ARP)Sub-second with proper tuning
Multi-subnet reachSingle L2 domain onlyRoutes propagate across L3
Load spreadOne node owns each VIPECMP can share across nodes
Best fitLab, single rack, small officeColo, 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.

Layer 2 vs BGP ModeLayer 2ClientVIP LeaderARP/NDP on one nodeSame broadcast domainGood for labs and racksBGPRouterSpeakers/32 routes via peeringECMP across nodesGood for colo and L3
MetalLB Layer 2 uses ARP on one leader node; BGP announces VIP routes to upstream routers

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.

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.

MetalLB Config PipelineStep 1IPAddressPoolStep 2L2 or BGP AdStep 3LoadBalancerVIP LiveController watches Service → allocates pool IPSpeaker advertises VIP on networkstatus.loadBalancer.ingress updated
Configure IPAddressPool, attach L2Advertisement or BGPAdvertisement, then create a LoadBalancer Service

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.loadBalancerClass set 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.

MetalLB TroubleshootingEXTERNAL-IP Pending?Check IPAddressPoolExists and has free IPsCheck AdvertisementL2 or BGP linked to poolCheck Speaker logsARP or BGP errorsVIP assigned but no traffic?Verify kube-proxy, CNI policies, and duplicate IPstcpdump on leader node during curl test
Decision tree for MetalLB: Load Balancing for Bare Metal when Services stay Pending or VIPs do not respond

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

MetalLB is a Kubernetes controller that assigns external IPs from your pools to LoadBalancer Services on on-prem clusters and advertises them via Layer 2 ARP or BGP. Cloud clusters get this from the provider; bare-metal racks do not, so Services stay Pending without it.

You need a working cluster with kube-proxy enabled and a compatible CNI. Helm is the usual path: add the metallb repo, create the metallb-system namespace, and install the chart. Wait until controller and speaker pods are ready on eligible nodes. Speakers need host network access for Layer 2 and BGP. If Helm is unavailable, apply upstream manifests pinned to a tested release such as v0.14.9, then confirm CRDs like ipaddresspools.metallb.io exist before defining pools.

Pick based on network capability, not hype. Layer 2 is low setup complexity, needs no router config, works on a single L2 domain, and elects one leader node per VIP with failover in seconds via gratuitous ARP. BGP needs peering sessions and network-team approval but gives sub-second failover, routes across subnets, and ECMP load spread across nodes. Labs and single-rack offices suit Layer 2; colo, multi-rack, and ISP-facing setups often need BGP. Hybrid deployments are common: Layer 2 internally, BGP toward edge routers.

Modern MetalLB uses CRDs, not a single ConfigMap. Create an IPAddressPool in metallb-system with a reserved address range on the same subnet as worker nodes for Layer 2. Set autoAssign as needed. Link it with an L2Advertisement that references the pool and optional nodeSelectors such as kubernetes.io/os: linux. Apply both manifests, then create a LoadBalancer Service and watch EXTERNAL-IP populate from the pool. Test with curl from a host on the same network. Validate YAML before apply; a typo can clash with gateway IPs or DHCP ranges.

Pending EXTERNAL-IP is the most common MetalLB support issue and is usually configuration, not a controller bug. Check controller logs for messages like no available IPs or pool not found. Common causes: an empty or exhausted pool, a pool on the wrong subnet for Layer 2, missing L2Advertisement or BGPAdvertisement, kube-proxy disabled so VIPs blackhole, duplicate IPs already in use on the LAN, RBAC gaps, NetworkPolicy blocking speaker traffic, or a cloud controller fighting Service status on hybrid clusters. Ping candidate VIPs before adding them to a pool.

Yes, for standard setups. MetalLB operates above Layer 2 or BGP and does not replace CNI Pod networking. Calico, Cilium, Flannel, and kube-router are common pairings mentioned in production guides. Confirm your CNI allows the traffic paths kube-proxy expects, especially if you run IPVS mode or disable kube-proxy for an eBPF dataplane. MetalLB assigns and advertises the VIP; kube-proxy still forwards traffic to Pods on each node.

Yes, in shared IP mode when Services use different ports on the same address. Configure allowSharedIP on the pool and align Service port sets so no two Services bind the same IP and identical port tuple. This pattern suits ingress controllers that multiplex HTTP and HTTPS on one VIP. Plan shared addresses deliberately in your pool documentation so operators know which VIPs are multiplexed and how much capacity remains for new LoadBalancer requests.

Yes, when pools, failover, and advertisements are tested. It is widely deployed on bare-metal clusters worldwide and actively maintained as a CNCF sandbox project. Success depends on correct pool planning, network-team involvement for BGP, and upgrade discipline. Treat MetalLB like any critical cluster component: snapshot CRDs, drain speakers one node at a time during upgrades, and verify a canary Service after each wave because speaker pods use host networking and a bad rollout can flap ARP.

HAProxy or nginx sitting outside the cluster terminates TLS, applies ACLs, and balances across NodePorts manually. MetalLB is thinner: it makes Kubernetes believe it has a cloud load balancer by filling status.loadBalancer on Services. Every new Service gets an IP without editing front-end rules. Many teams use both: MetalLB for internal platform Services and stable VIPs, plus an ingress controller or HAProxy for HTTP edge traffic and TLS. MetalLB does not pick Pod targets; kube-proxy or IPVS handles that downstream.

MetalLB is free software. Your spend is reserved IP space and engineer time, not hourly load-balancer fees. A small office may use a /28 on an existing VLAN at zero marginal cost; colo BGP may add cross-connect fees. For budget-sensitive teams, bare metal plus MetalLB often beats managed Kubernetes load balancers and cloud egress charges.

MetalLB runs two logical components installed together via Helm or manifests. The controller watches LoadBalancer Services, allocates IPs from IPAddressPool resources, and updates Service status. The speaker DaemonSet runs on each eligible node with host network access and handles Layer 2 ARP replies or BGP peering to advertise VIPs on your LAN. Both need cluster-wide RBAC to watch Services and Endpoints. Do not schedule speakers on control-plane nodes in production unless you accept that trade-off in small lab clusters.

BGP requires a BGPPeer resource and a BGPAdvertisement linked to your IPAddressPool. Define myASN, peerASN, and peerAddress toward your top-of-rack switch or border router. Unlike Layer 2, the pool range can be any routable block your upstream accepts, not only the worker subnet. Coordinate ASN, passwords, hold timers, and prefix limits with your network team. Misconfigured BGP can blackhole production prefixes, so test in a maintenance window. Failover is faster than Layer 2 because routing tables update without waiting for ARP cache expiry.

Restrict who can create LoadBalancer Services via RBAC even on private LANs. A developer typo can grab your last free VIP and expose an admin API. Namespace quotas and OPA or Gatekeeper policies help enforce policy. For API stacks, expose only ingress controllers publicly and keep databases and Redis on ClusterIP. Monitor pool utilisation and alert before it crosses eighty percent so marketing launches or new microsites do not exhaust addresses during traffic spikes.

Reserve a contiguous IP range on the same L2 segment as worker nodes and confirm no DHCP server will assign those addresses. Ensure firewall rules allow traffic to NodePort ranges if you mix Service types. Document which VLAN or subnet each pool maps to. Disable or avoid cloud provider integrations that fight for LoadBalancer Service status on hybrid clusters. Verify your Kubernetes version supports the CRDs MetalLB ships and check release notes before upgrading either side. Store pool and advertisement manifests in Git beside other cluster config.

No. MetalLB requires kube-proxy enabled and does not replace your CNI. Calico, Cilium, Flannel, and kube-router all work alongside it. MetalLB only owns IP assignment and network advertisement for LoadBalancer Services. The kube-proxy datapath on each node still handles forwarding from the VIP to Pods. If kube-proxy is disabled or misconfigured, a Service may show an EXTERNAL-IP but traffic will blackhole. MetalLB also does not PXE boot hardware; it assumes nodes already join the cluster.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: