MetalLB Cheat Sheet
Overview
MetalLB is a load-balancer implementation for bare-metal Kubernetes clusters. In cloud environments, Kubernetes Services of type LoadBalancer automatically provision external load balancers (AWS ELB, GCP LB, etc.), but on bare metal these services remain perpetually in the “pending” state. MetalLB fills this gap by providing two functions: address allocation (assigning external IPs to Services from a configured pool) and external announcement (making the network aware that the IP exists using either Layer 2 mode or BGP mode).
In Layer 2 mode, one node in the cluster takes ownership of the service IP and responds to ARP requests, making it the simplest setup but limiting failover to single-node traffic handling. In BGP mode, all nodes that host a service’s pods establish BGP peering sessions with network routers, enabling true load balancing across multiple nodes. MetalLB supports both IPv4 and IPv6, multiple address pools, and per-service pool selection via annotations. It is widely deployed in on-premises Kubernetes installations, home labs, and edge computing environments.
Installation
Helm Installation
# Add MetalLB Helm repo
helm repo add metallb https://metallb.github.io/metallb
helm repo update
# Install MetalLB
helm install metallb metallb/metallb --namespace metallb-system --create-namespace
# Verify pods
kubectl get pods -n metallb-system
Manifest Installation
# Apply MetalLB manifests
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
# Wait for pods to be ready
kubectl wait --namespace metallb-system \
--for=condition=ready pod \
--selector=app=metallb \
--timeout=90s
Configuration
Layer 2 Mode
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-l2
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
BGP Mode
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: bgp-pool
namespace: metallb-system
spec:
addresses:
- 203.0.113.0/24
---
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: router-peer
namespace: metallb-system
spec:
myASN: 64500
peerASN: 64501
peerAddress: 10.0.0.1
password: bgpsecret
holdTime: 90s
keepaliveTime: 30s
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: bgp-advert
namespace: metallb-system
spec:
ipAddressPools:
- bgp-pool
communities:
- 64500:100
aggregationLength: 32
Multiple Address Pools
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: internal-pool
namespace: metallb-system
spec:
addresses:
- 10.0.100.0/24
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: external-pool
namespace: metallb-system
spec:
addresses:
- 203.0.113.0/28
autoAssign: false
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: internal-l2
namespace: metallb-system
spec:
ipAddressPools:
- internal-pool
interfaces:
- eth0
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: external-l2
namespace: metallb-system
spec:
ipAddressPools:
- external-pool
interfaces:
- eth1
Using MetalLB with Services
Basic LoadBalancer Service
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
Request Specific IP
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
type: LoadBalancer
loadBalancerIP: 192.168.1.210
selector:
app: web
ports:
- port: 80
targetPort: 8080
Select Specific Pool
apiVersion: v1
kind: Service
metadata:
name: external-api
annotations:
metallb.universe.tf/address-pool: external-pool
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 443
targetPort: 8443
Share IP Between Services
apiVersion: v1
kind: Service
metadata:
name: http-service
annotations:
metallb.universe.tf/allow-shared-ip: "shared-web"
spec:
type: LoadBalancer
loadBalancerIP: 192.168.1.210
selector:
app: web
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: https-service
annotations:
metallb.universe.tf/allow-shared-ip: "shared-web"
spec:
type: LoadBalancer
loadBalancerIP: 192.168.1.210
selector:
app: web
ports:
- port: 443
targetPort: 8443
Core Commands
| Command | Description |
|---|---|
kubectl get ipaddresspool -n metallb-system | List IP address pools |
kubectl get l2advertisement -n metallb-system | List L2 advertisements |
kubectl get bgppeer -n metallb-system | List BGP peers |
kubectl get svc -A | Show services with External IPs |
kubectl describe svc <name> | Check service events for MetalLB assignment |
kubectl logs -n metallb-system -l app=metallb -c speaker | Speaker logs |
kubectl logs -n metallb-system -l app=metallb -c controller | Controller logs |
Advanced Usage
Node Selectors for L2 Advertisement
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: targeted-l2
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: node1
- matchLabels:
kubernetes.io/hostname: node2
BGP with Multiple Routers
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: router1
namespace: metallb-system
spec:
myASN: 64500
peerASN: 64501
peerAddress: 10.0.0.1
nodeSelectors:
- matchLabels:
rack: rack1
---
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
name: router2
namespace: metallb-system
spec:
myASN: 64500
peerASN: 64502
peerAddress: 10.0.0.2
nodeSelectors:
- matchLabels:
rack: rack2
IPv6 Support
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: ipv6-pool
namespace: metallb-system
spec:
addresses:
- 2001:db8:1::100-2001:db8:1::200
Troubleshooting
| Issue | Solution |
|---|---|
Service stuck in Pending | Check IPAddressPool has available addresses; verify MetalLB pods running |
| L2 mode: no connectivity | Verify VIP is in same subnet as nodes; check ARP with arping |
| BGP session not established | Check firewall allows TCP 179; verify ASN and peer addresses |
| Failover too slow (L2) | L2 relies on ARP cache expiry; set gratuitousARP and reduce client timeouts |
| IP not assigned from correct pool | Add metallb.universe.tf/address-pool annotation to Service |
| Speaker CrashLoopBackOff | Check if kube-proxy is in IPVS mode; set strictARP: true in kube-proxy config |
| Multiple speakers claiming IP | Verify memberlist secret exists in metallb-system namespace |