Kubernetes on Bare Metal with MetalLB

Khimananda Oli 8 min read Virtualization
Kubernetes on Bare Metal with MetalLB

By Khimananda Oli | Last reviewed: August 2026

Running Kubernetes on bare metal with MetalLB solves the most common on-premises networking gap: the lack of native LoadBalancer support. Without it, Services of type LoadBalancer remain stuck in a pending state indefinitely because there is no cloud controller manager to provision external IPs. MetalLB fills this void by implementing a network load balancer that integrates directly with your physical network infrastructure.

Why Do You Need MetalLB for Kubernetes on Bare Metal?

In managed environments like EKS or GKE, creating a Service with type: LoadBalancer triggers an API call to the cloud provider, which provisions a proprietary load balancer and assigns an external IP. On self-managed infrastructure, whether in a Kathmandu data center or a home lab, this automation does not exist. The Kubernetes control plane has no mechanism to allocate IPs from your physical router's DHCP range or static assignments.

MetalLB acts as a network-aware controller that watches for these pending Services and assigns IPs from a pre-defined pool. It then advertises those IPs to the network so traffic reaches the correct nodes. For teams building production clusters with Kubespray or kubeadm, MetalLB is typically the first networking component installed after the CNI. Without it, you are forced to use NodePort services, which expose non-standard ports and create unnecessary security surface area, or rely on manual Ingress controller configurations that bypass standard Service abstractions.

Kubernetes on Bare Metal with MetalLB ArchitecturePhysical RouterGateway / BGP PeerClient TrafficExternal RequestsBare Metal Cluster NodesMetalLB ControllerIP Allocation & WatchSpeaker DaemonSetkube-proxy / CNIService RoutingPod NetworkApp PodsWorkloadsEndpointsIP Address Pool (e.g., 192.168.10.200-250)Allocated from LAN subnet, not DHCP rangeAdvertised via L2 ARP/NDP or BGPAvoid conflicts with static reservationsScoped per namespace or globally
High-level architecture of Kubernetes on bare metal with MetalLB showing traffic flow from physical network through speaker daemons to application pods.

How Do You Install and Configure MetalLB?

Installation requires two distinct steps: deploying the MetalLB components and defining the IP address pools. Never skip the configuration step; without an IPAddressPool, the controller runs but cannot assign addresses.

Deploy MetalLB Components

The recommended method in 2026 is Helm, which handles CRDs and version compatibility automatically. If you prefer raw manifests for audit compliance or air-gapped environments, the official YAML works identically.

# Add the MetalLB Helm repository
helm repo add metallb https://metallb.github.io/metallb
helm repo update

# Install MetalLB into its own namespace
helm install metallb metallb/metallb \
  --namespace metallb-system \
  --create-namespace \
  --version 0.14.9

# Verify all pods reach Ready state
kubectl get pods -n metallb-system -w

Wait until both the controller pod and all speaker daemonset pods show Running. The speakers must run on every node that should participate in load balancing; they handle the actual protocol advertisement.

Define IP Address Pools and Advertisement Methods

Create a manifest that specifies which IPs MetalLB can allocate and how to advertise them. This example reserves a block outside your DHCP scope:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: production-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.10.200-192.168.10.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advert
  namespace: metallb-system
spec:
  ipAddressPools:
    - production-pool

Apply this with kubectl apply -f metallb-config.yaml. Within seconds, any existing pending LoadBalancer Services will receive an IP from this range. For teams managing secrets and configuration securely, store this manifest in GitOps rather than applying manually.

Should You Use Layer 2 or BGP Mode?

This is the most consequential architectural decision when deploying Kubernetes on bare metal with MetalLB. The wrong choice causes silent failures under load or during node maintenance.

CriteriaLayer 2 ModeBGP Mode
Traffic PathAll traffic routes through single elected nodeECMP across all advertising nodes
Bandwidth LimitCapped at one node's NIC capacityAggregated across all nodes
Failover Speed~10-30 seconds (ARP reannouncement)Sub-second (BGP convergence)
Router RequirementsNone (works with any switch)BGP-capable router (Ubiquiti, MikroTik, Cisco)
Configuration ComplexityMinimalRequires AS numbers, peer config
Best ForDev/staging, low-traffic appsProduction, high-throughput workloads

In practice, I default to BGP for any environment handling real user traffic. Layer 2 creates a bottleneck that defeats the purpose of having multiple nodes. However, if your routers do not support BGP or you lack administrative access to configure peering, Layer 2 remains functional for smaller deployments.

Layer 2 ModeRouterSingle PathNode AACTIVE LEADERNode BStandbyNode CStandby⚠ Bottleneck: All traffic via Node AFailover: 10-30s ARP updateNo router config neededBGP ModeRouterECMP EnabledNode AAdvertisingNode BAdvertisingNode CAdvertising✓ Full bandwidth aggregationFailover: Sub-second convergenceRequires BGP-capable router
Side-by-side comparison of Layer 2 single-node bottleneck versus BGP multi-path ECMP routing for Kubernetes on bare metal with MetalLB.

Configuring BGP Mode

BGP requires coordination between MetalLB and your router. Replace the L2Advertisement with a BGPAdvertisement and define peers:

apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
  name: router-peer
  namespace: metallb-system
spec:
  myASN: 64500
  peerASN: 64501
  peerAddress: 192.168.10.1
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
  name: bgp-advert
  namespace: metallb-system
spec:
  ipAddressPools:
    - production-pool

Your router must be configured to accept BGP sessions from each node's IP using ASN 64500. Consult your router vendor's documentation for exact commands; MikroTik, Ubiquiti EdgeRouter, and VyOS all support this natively.

How Do You Troubleshoot Pending LoadBalancer Services?

When Services stay pending despite MetalLB running, the issue is almost always one of three things: exhausted IP pools, misconfigured advertisements, or speaker pod failures.

  1. Check IP allocation events: Run kubectl describe svc <service-name> and look for MetalLB-related events. Messages like "no available IPs" indicate pool exhaustion.
  2. Verify speaker health: Execute kubectl get pods -n metallb-system. If any speaker pod is CrashLoopBackOff, check logs with kubectl logs -n metallb-system <speaker-pod>. Common causes include missing kernel modules (ip_vs) or firewall rules blocking ARP/BGP.
  3. Validate pool configuration: Ensure the CIDR or range in IPAddressPool does not overlap with DHCP scopes or other static assignments. Use kubectl get ipaddresspools -n metallb-system -o yaml to inspect.
  4. Test connectivity: From a machine on the same VLAN, ping the assigned external IP. If unreachable in L2 mode, verify that the leader node's interface is up and that no host firewall blocks ICMP or the service port.

A frequent mistake in Nepal-based deployments involves VLAN tagging mismatches. If your bare metal nodes connect via tagged trunks but MetalLB expects untagged traffic, ARP announcements never reach clients. Always confirm the physical switch port configuration matches your CNI and MetalLB assumptions. For deeper diagnostics, integrate with your monitoring stack to track MetalLB metrics like metallb_allocator_addresses_in_use_total.

What Are the Production Best Practices for MetalLB?

Running MetalLB in production demands more than a working installation. These practices prevent outages during scaling and maintenance:

  • Reserve IPs outside DHCP: Document your MetalLB ranges in IPAM tools or spreadsheets. Overlapping with DHCP causes intermittent connectivity loss when leases renew.
  • Use separate pools per environment: Create distinct IPAddressPool resources for staging, production, and internal services. Apply namespace selectors to prevent accidental cross-environment allocation.
  • Enable strict ARP validation: In L2 mode, set spec.nodeSelector on speakers to exclude nodes without direct LAN access. This prevents virtualized or edge nodes from winning leader elections incorrectly.
  • Monitor speaker readiness: Alert on speaker pod restarts and BGP session flaps. A downed speaker in BGP mode withdraws routes immediately; in L2 mode, failover delays impact users.
  • Version pinning: Always specify exact Helm chart versions in GitOps. MetalLB CRDs change between minor releases; unpinned installs can break during automated upgrades.
Start: MetalLB PlanningDoes router support BGP?(MikroTik, Ubiquiti, Cisco, VyOS)NOYESUse Layer 2 ModeAccept single-node bandwidth limitUse BGP ModeConfigure ASNs + ECMP on routerDefine IPAddressPoolExclude DHCP range, document in IPAMDefine IPAddressPool + BGPPeerMatch router ASN, verify peeringValidate & MonitorTest Service creation, check speaker logs, add alerts
Decision flowchart for selecting MetalLB mode and validating configuration when deploying Kubernetes on bare metal.

Final Steps for Reliable Bare Metal Load Balancing

Deploying Kubernetes on bare metal with MetalLB transforms self-managed clusters into platforms that match cloud-native ergonomics. Start with Layer 2 for validation, migrate to BGP before production traffic, and enforce IP reservation discipline from day one. Automate the configuration through GitOps to maintain audit trails and enable rapid recovery. If your team needs assistance designing compliant, observable bare metal infrastructure, reach out to discuss your specific requirements.

Frequently Asked Questions

MetalLB provides a load balancer implementation for bare metal clusters where cloud provider LBs are unavailable. It assigns external IPs to services using L2 or BGP, enabling standard LoadBalancer service types without proprietary infrastructure dependencies.

Cloud LBs provision external hardware automatically via API. MetalLB operates entirely within your cluster, announcing IPs over the local network using ARP or BGP protocols, requiring manual IP pool configuration and network integration instead of automated provisioning.

Use BGP mode for production environments needing ECMP routing and fast failover. Reserve L2 mode for simple single-node announcements or testing, as it lacks true load distribution across multiple nodes and causes brief outages during leader transitions.

No. True HA requires BGP-capable upstream routers supporting ECMP. L2 mode only provides failover with downtime during leader election. For HA without BGP hardware, consider kube-vip or keepalived as alternatives for virtual IP management.

Define an IPAddressPool CRD specifying your assignable CIDR range, then create an L2Advertisement or BGPAdvertisement resource linking to that pool. Apply both manifests; MetalLB controller validates ranges against existing allocations before assigning service IPs.

Yes. MetalLB supports dual-stack and IPv6-only pools natively. Configure separate IPAddressPool resources with IPv6 CIDRs and ensure your underlying network fabric and CNI plugin also support IPv6 routing and neighbor discovery correctly.

In L2 mode, another speaker wins leader election via ARP/NDP, causing 10-30 seconds of traffic loss. In BGP mode, routes withdraw immediately and reconverge via ECMP if redundant paths exist, minimizing disruption to seconds depending on router timers.

Yes. MetalLB integrates with any CNI. With Cilium, you may alternatively use its native BGP control plane for simpler stack reduction. With Calico, MetalLB remains the standard choice since Calico lacks built-in external IP announcement capabilities.

MetalLB itself adds no encryption or filtering. Secure exposed services using ingress controllers with TLS termination, network policies restricting pod-to-pod traffic, and firewall rules on upstream routers limiting announced IP ranges to intended subnets only.

Check kubectl get events for allocation errors. Common causes include exhausted IP pools, misconfigured address ranges overlapping node IPs, missing L2/BGP advertisement CRDs, or speaker pods failing readiness probes due to interface binding issues.

Not natively. Each cluster manages independent IP pools risking overlap. Use Submariner or Liqo for cross-cluster service discovery, or coordinate IPAM externally via Infoblox or NetBox to prevent conflicts across geographically distributed bare metal sites.

MetalLB eliminates client-side port mapping overhead by providing standard port 80/443 access. Throughput matches direct pod networking since traffic flows through kube-proxy or eBPF dataplanes identically; latency differences are negligible compared to NodePort's extra hop.

Yes. Annotate services with metallb.universe.tf/loadbalancer-ips specifying exact addresses from configured pools. MetalLB reserves these IPs persistently. Ensure requested IPs fall within defined IPAddressPool ranges to avoid validation failures during assignment.

Track metallb_speaker_announced_services_total, address pool utilization percentage, and BGP session state flaps. Alert on zero announced services despite pending requests, pool exhaustion above 90%, or persistent BGP peer down states indicating upstream connectivity failures.

Usually no. L2 uses standard ARP/NDP which switches forward normally. Disable port security MAC limiting if speakers announce multiple virtual MACs. Avoid IGMP snooping interference with gratuitous ARP. BGP mode requires explicit peering configuration on upstream routers.