Troubleshooting MetalLB External IP Assignment on Bare Metal Kubernetes
When running Kubernetes on bare metal clusters, MetalLB provides load balancing functionality that cloud providers offer automatically. However, administrators frequently encounter issues where MetalLB fails to assign external IPs or the assigned IPs remain unreachable. This guide from PerLod Hosting explains why the MetalLB external IP not working and provides the best troubleshooting steps to resolve these issues.
For the complete setup guide, you can check the MetalLB Bare Metal Load Balancing tutorial, which covers installation and basic configuration.
Table of Contents
How MetalLB Assigns External IPs?
MetalLB assigns external IP addresses to LoadBalancer services through a two-component system. The controller component allocates IP addresses from configured pools, while speaker components announce these IPs on the network using Layer 2 (ARP or NDP) or BGP protocols.
When you create a LoadBalancer service, the MetalLB controller watches for the new service and selects an available IP from an IPAddressPool resource.
The controller assigns this IP to the service’s status field. After the assignment, speaker pods announce the IP on the network so external clients can reach it.
The process requires two custom resources:
1. IPAddressPool: Defines available IP ranges:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.240-192.168.1.250
2. L2Advertisement: Tells MetalLB to announce IPs via ARP:
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- production-pool
Both resources are required. Creating only the IPAddressPool without L2Advertisement is the most common configuration mistake that causes unreachable IPs.
In Layer 2 mode, MetalLB speakers answer ARP requests for service IPs. When a device asks Who has 192.168.1.240?, MetalLB replies with the node’s MAC address, so traffic routes to that node, and kube-proxy sends it to the correct pods.
Discover MetalLB External IP Issues
MetalLB failures usually happen in three ways: services stay pending, the IP address exists but can’t be reached, or a core component crashes.
1. External IP Stuck in Pending State: When services show pending in the EXTERNAL-IP column, MetalLB has not assigned an IP address.
Check the service status with the command below:
kubectl get svc -A | grep LoadBalancer
In the output, you will see:
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
default nginx-service LoadBalancer 10.96.123.45 <pending> 80:30123/TCP
This happens because:
- No Pool Created: You haven’t defined an IPAddressPool yet.
- Out of IPs: Every IP in your pool is already in use.
- Invalid IP Format: The IP range is written incorrectly.
- Controller Down: The MetalLB controller isn’t running.Wrong Namespace: The IP pool is restricted to a specific namespace that doesn’t match your service.
You can check controller logs to identify the specific cause:
kubectl logs -n metallb-system -l component=controller --tail=50
Common error messages include:
- No available IPs: Pool is exhausted.
- No matching pool: No pool matches service requirements.
- Invalid CIDR notation: IP format errors.
To verify IPAddressPool exists, you can run:
kubectl get ipaddresspool -n metallb-system
kubectl describe ipaddresspool -n metallb-system
2. External IP Assigned But Not Reachable: This occurs when the service shows an external IP, but you cannot connect to it from the network.
Check if IP is assigned with the command below:
kubectl get svc nginx-service
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
nginx-service LoadBalancer 10.96.123.45 192.168.1.240 80:30123/TCP
Testing from a client fails:
curl http://192.168.1.240
# Returns: curl: (7) Failed to connect to 192.168.1.240 port 80: No route to host
This happens because:
- Missing Advertisement: You created an IP pool but forgot the L2Advertisement resource to actually announce it.
- Subnet Mismatch: The load balancer IPs are not in the same network subnet as your cluster nodes.
- Strict ARP Disabled: You are using kube-proxy in IPVS mode but haven’t enabled strictARP.
- Nodes Excluded: Your nodes are labeled to explicitly ignore external load balancers.
- Permission Errors: The MetalLB speaker pods lack the permissions needed to reply to ARP requests.
Verify L2Advertisement exists with the following command:
kubectl get l2advertisement -n metallb-system
If missing, this is the problem.
Check speaker logs with the command below:
kubectl logs -n metallb-system -l component=speaker --tail=100
Look for announcement messages or errors like operation not permitted or failed to create ARP responder.
3. MetalLB Components Not Running: If MetalLB pods are not running, no IP assignments or announcements occur.
Check pod status with the command below:
kubectl get pods -n metallb-system -o wide
A healthy output looks like this:
NAME READY STATUS RESTARTS AGE NODE
controller-7d4c8764f4-x9k8p 1/1 Running 0 2d worker-1
speaker-2m9pm 1/1 Running 0 2d worker-1
speaker-7m4qw 1/1 Running 0 2d worker-2
If pods show CrashLoopBackOff or ImagePullBackOff, check pod details with the commands below:
kubectl describe pod -n metallb-system <pod-name>
kubectl logs -n metallb-system <pod-name>
This happens because:
- Image pull failures due to network issues.
- Insufficient resources on nodes.
- RBAC permission issues.
- CNI plugin conflicts.
How To Fix MetalLB External IP Failures?
Now that you have learned to identify MetalLB external IP failures, you can proceed to the following steps to fix the issues.
Fix Missing MetalLB L2Advertisement Configuration
In this problem, the external IP is assigned but unreachable, which means L2Advertisement is missing.
Check if the L2Advertisement exists with the command below:
kubectl get l2advertisement -n metallb-system
If it returns No resources found, you must create the L2Advertisement:
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-l2-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- production-pool # Match your IPAddressPool name
Apply it with the command below:
kubectl apply -f l2advertisement.yaml
Verify it by using the commands below:
kubectl get l2advertisement -n metallb-system
kubectl describe l2advertisement -n metallb-system
Also check speaker logs for announcements:
kubectl logs -n metallb-system -l component=speaker | grep -i "announced"
Finally, test connectivity with the following commands:
ping 192.168.1.240
curl http://192.168.1.240
Fix MetalLB IP Pool Configuration Errors
In this issue, services are stuck in a pending state due to pool configuration issues.
If it happens because of the invalid IP format, you must use the correct format:
addresses:
- 192.168.1.240-192.168.1.250 # Range format
- 192.168.1.0/24 # CIDR format
If it happens because of the wrong subnet, check the node IPs:
kubectl get nodes -o wide
If nodes are on 192.168.4.0/24, MetalLB IPs must be in the same subnet:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.4.240-192.168.4.250 # Same subnet as nodes
If the problem happens because the IP pool is exhausted, count LoadBalancer services:
kubectl get svc -A | grep LoadBalancer | wc -l
If count equals your pool size, you must expand the range:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.240-192.168.1.250
- 192.168.1.160-192.168.1.170 # Additional range
Apply and verify it with the commands below:
kubectl apply -f ipaddresspool.yaml
kubectl get ipaddresspool -n metallb-system
kubectl describe ipaddresspool -n metallb-system
Fix StrictARP Configuration for IPVS Mode
In this state, external IPs are unreachable when using kube-proxy in IPVS mode.
You can check the kube-proxy mode with the command below:
kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
If output shows ipvs mode, check strictARP:
kubectl get configmap kube-proxy -n kube-system -o yaml | grep strictARP
If strictARP is false or missing, enable it with:
kubectl edit configmap kube-proxy -n kube-system
Set the settings with:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true
Restart kube-proxy pods and verify it with the commands below:
kubectl delete pods -n kube-system -l k8s-app=kube-proxy
kubectl get configmap kube-proxy -n kube-system -o yaml | grep strictARP
Fix MetalLB Node Label Exclusions
In this problem, nodes with an exclusion label prevent MetalLB from announcing IPs.
Check for the exclusion label with the commands below:
kubectl get nodes --show-labels | grep exclude-from-external-load-balancers
#or
kubectl describe node <node-name> | grep exclude-from-external-load-balancers
You can remove the exclusion label with the following command:
kubectl label node <node-name> node.kubernetes.io/exclude-from-external-load-balancers-
For all nodes, you can run:
kubectl label nodes --all node.kubernetes.io/exclude-from-external-load-balancers-
Verify the removal process is completed with the command below:
kubectl get nodes --show-labels | grep exclude-from-external-load-balancers
Fix MetalLB Network Routing and Subnet Issues
In this step, the problem is different subnets between nodes and MetalLB IPs.
You can check node IPs with the command below:
kubectl get nodes -o wide
Check the MetalLB pool with:
kubectl get ipaddresspool -n metallb-system -o yaml
If subnets don’t match, you must update IPAddressPool to match the node subnet:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-pool
namespace: metallb-system
spec:
addresses:
- 192.168.4.240-192.168.4.250 # Match node subnet
Ensure these IPs:
- Are not assigned to other devices.
- Are excluded from DHCP ranges.
- Are not gateway addresses.
Apply and test with the following commands:
kubectl apply -f ipaddresspool.yaml
ping 192.168.1.240
curl http://192.168.1.240
Debug Issues with MetalLB Logs and Events
If your load balancer isn’t working, the logs will tell you why. You can use these commands to detect internal errors, verify ARP announcements, and track service events to identify exactly where the connection is failing.
Check controller logs with the command below:
kubectl logs -n metallb-system -l component=controller --tail=100
Look for:
- Assigned IP: Success
- No available IPs: Pool exhausted
- No matching pool: Configuration issue
Check Speaker logs with the following command:
kubectl logs -n metallb-system -l component=speaker --tail=100
You must look for:
- serviceAnnounced: Success
- Failed to create ARP responder: Permission error
- Operation not permitted: Capability issue
You can check service events with the command below:
kubectl describe svc nginx-service
In the output, look for:
- IPAllocated: IP successfully assigned
- AllocationFailed: Error message
Use the commands below to check component status:
kubectl get pods -n metallb-system
kubectl get ipaddresspool -n metallb-system
kubectl get l2advertisement -n metallb-system
For network verification, you can use:
# From client machine
arp -n | grep 192.168.1.240
ping 192.168.1.240
# From Kubernetes node
sudo tcpdump -i eth0 arp host 192.168.1.240 -n
FAQs
Why does the MetalLB service stay in pending status?
Services stay pending when MetalLB cannot assign an IP. Verify that your IPAddressPool exists and has valid addresses, then check the controller logs for errors.
Do I need to enable strictARP for MetalLB?
Yes, only for IPVS mode. If you use kube-proxy in IPVS mode, strictARP must be enabled. It is not required for iptables.
Can MetalLB use IPs from a different subnet than my nodes?
No, not in Layer 2 mode. The load balancer IPs must be in the same subnet as your Kubernetes nodes. To use a different subnet, you must configure BGP mode.
Conclusion
MetalLB external IP failures on bare metal occur because of configuration errors, missing resources, or network incompatibilities. If your service stays pending, MetalLB cannot allocate an IP; check that your IPAddressPool exists and uses valid ranges. If the IP is assigned but unreachable, MetalLB cannot announce it; verify that the L2Advertisement resource exists, the IP matches your node subnet, and strictARP is enabled for IPVS mode.
For dedicated server environments running Kubernetes, MetalLB provides essential load-balancing capabilities that would otherwise require external hardware load balancers.
Always resolve issues by checking the controller and speaker logs and testing connectivity after every fix.
We hope you enjoy this MetalLB external IP error fixes guide. Subscribe to our X and Facebook channels to get the latest updates and articles.