MetalLB
MetalLB gives kinder clusters real LoadBalancer IP addresses. Without it, kubectl services of type LoadBalancer stay in <pending> forever. With it, they get an IP from the local Docker or Podman subnet within seconds of creation.
kinder installs MetalLB v0.15.3.
What gets installed
Section titled “What gets installed”| Resource | Namespace | Purpose |
|---|---|---|
| MetalLB controller | metallb-system | Watches services, assigns IPs |
| MetalLB speaker | metallb-system | Announces IPs via L2 ARP |
IPAddressPool “kind-pool” | metallb-system | Defines the assignable IP range |
L2Advertisement “kind-l2advert” | metallb-system | Enables L2 (ARP) advertisement |
IP address pool
Section titled “IP address pool”kinder auto-detects the Docker or Podman bridge subnet at cluster creation time and carves out a .200–.250 range for LoadBalancer use. For example, if the bridge is 172.20.0.0/16, the pool is 172.20.255.200–172.20.255.250.
You do not need to configure this range manually.
How to verify
Section titled “How to verify”After creating a cluster, confirm that both MetalLB pods are running:
kubectl get pods -n metallb-systemExpected output:
NAME READY STATUS RESTARTS AGEcontroller-... 1/1 Running 0 60sspeaker-... 1/1 Running 0 60sCreate a test service to confirm IP assignment works:
kubectl create deployment test --image=nginxkubectl expose deployment test --port=80 --type=LoadBalancerkubectl get svc testThe EXTERNAL-IP column should show an IP in the .200–.250 range within a few seconds.
Configuration
Section titled “Configuration”MetalLB is controlled by the addons.metalLB field in your cluster config:
apiVersion: kind.x-k8s.io/v1alpha4kind: Clusteraddons: metalLB: true # defaultSee the Configuration Reference for all available addon fields.
How to disable
Section titled “How to disable”To create a cluster without MetalLB, set metalLB: false:
apiVersion: kind.x-k8s.io/v1alpha4kind: Clusteraddons: metalLB: falseLoadBalancer services will remain in <pending> without a separate load balancer controller.
Practical examples
Section titled “Practical examples”Create a LoadBalancer service with a custom IP
Section titled “Create a LoadBalancer service with a custom IP”To request a specific IP for a service, use the metallb.universe.tf/loadBalancerIPs annotation:
apiVersion: v1kind: Servicemetadata: name: my-service annotations: metallb.universe.tf/loadBalancerIPs: 172.20.255.210spec: selector: app: my-app ports: - port: 80 targetPort: 8080 type: LoadBalancerApply and confirm the IP is assigned:
kubectl apply -f my-service.yamlkubectl get svc my-serviceExpected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEmy-service LoadBalancer 10.96.100.200 172.20.255.210 80:31234/TCP 5sWhen to use NodePort instead
Section titled “When to use NodePort instead”There are three scenarios where NodePort is preferable to LoadBalancer:
- Rootless Podman — ARP announcement requires privileges that rootless Podman does not grant. The service gets an IP but traffic does not route.
- macOS or Windows — LoadBalancer IPs are assigned inside the Docker/Podman VM network and are not directly reachable from the host. NodePort on
localhostworks instead. - Single-service access — When you only need to reach one service temporarily,
kubectl port-forwardis simpler and does not consume a pool IP.
To expose a deployment with NodePort:
kubectl expose deployment my-app --type=NodePort --port=80kubectl get svc my-appExpected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEmy-app NodePort 10.96.50.100 <none> 80:32456/TCP 3sAccess the service via localhost using the assigned node port:
curl http://localhost:32456Troubleshooting
Section titled “Troubleshooting”Service stuck in pending
Section titled “Service stuck in pending”Symptom: kubectl get svc shows <pending> in the EXTERNAL-IP column even after waiting several seconds.
Cause: Either MetalLB pods are not running, or the IPAddressPool is exhausted. The default pool supports up to 51 addresses (.200–.250), so you can have at most 51 LoadBalancer services active at once.
Fix:
Check that MetalLB pods are running:
kubectl get pods -n metallb-systemBoth controller and speaker pods must show Running. If either is in Pending or CrashLoopBackOff, describe the pod for details.
Check whether the pool has available IPs:
kubectl get ipaddresspool -n metallb-system -o yamlIf addresses in the status are all allocated, delete unused LoadBalancer services to free IPs.