MetalLB: a bare-metal load balancer for Kubernetes using ARP and BGP
A network load-balancer implementation for Kubernetes using standard routing protocols
At a glance
- What is it?
- MetalLB gives Kubernetes clusters outside the cloud a Service type LoadBalancer by speaking standard routing protocols. It is beta software, and the routing mode you pick decides whether it fits your network.
- Who is it for?
- Adopt MetalLB if you run Kubernetes on hardware or VMs where no cloud controller assigns external IPs, and you can dedicate an address pool plus control ARP or BGP on that segment. Do not adopt it if you need a vendor-backed SLA or if L2 mode on one node is a single point of failure you cannot accept.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What MetalLB does that a cloud controller normally does
A Kubernetes Service of type LoadBalancer does nothing on its own. On a cloud provider, a controller watches for that Service and provisions an external load balancer with a public address. On bare metal, virtual machines or a lab cluster, no such controller exists, so the Service stays in Pending with no external IP. MetalLB fills that gap: it watches Services of type LoadBalancer and assigns them addresses from pools you define, then makes those addresses reachable from outside the cluster.
The project describes itself as "a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols." That last clause is the design constraint. MetalLB does not tunnel traffic through a proprietary overlay or require a specific NIC vendor. It announces the assigned address to the surrounding network using protocols that switches and routers already understand, so the rest of your infrastructure needs no agent. The intended audience is platform engineers running clusters on hardware they control, including single-node distributions such as K3s, and managed platforms where the underlying network is still yours to configure.
The project labels itself beta in the README badge, and the maturity page is linked from that badge. That label matters more than any feature list: treat the API surface as usable but not frozen, and read release notes before upgrading.
How MetalLB assigns and announces an address
MetalLB splits the work between two components, and the split is visible in the repository layout: a controller/ directory and a speaker/ directory.
The controller holds the allocation logic. It watches Services of type LoadBalancer and the pool configuration, then picks an address for each Service from a pool. The speaker runs on nodes, and it is the part that actually puts traffic on the wire. Which speaker does the work depends on the mode.
In L2 mode, one node in the cluster takes ownership of an address and answers ARP requests for IPv4 (or NDP for IPv6) so that the address resolves to that node's MAC address. The dependencies in go.mod reflect this: the project imports github.com/mdlayher/arp, github.com/mdlayher/ethernet and github.com/mdlayher/ndp, the packet-level libraries for exactly that job. In BGP mode, speakers peer with your routers and advertise the address as a route, so traffic can arrive at more than one node and the routers spread it. The repository imports github.com/metallb/frr-k8s and carries an frr-tools/ directory, which is where FRR, the routing suite, enters the picture.
The consequence of this design is that MetalLB is not a proxy in the data path for L2 traffic. Packets reach a node and normal Kubernetes Service routing (kube-proxy or an equivalent) takes over from there. That keeps the fast path cheap but also means MetalLB cannot do things a proxy load balancer can, such as terminate TLS or inspect HTTP headers.
Installing MetalLB and claiming your first address
The README does not carry install steps; it points to the project website at metallb.io and warns that the main branch is the development branch. The README states that consuming manifests from main "may result in unstable / non backward compatible deployments" and that you should consume a stable branch as described in the official docs. The repository also ships a charts/ directory, which is where the Helm chart lives, and the release list shows chart releases such as metallb-chart-0.16.1 alongside the main v0.16.0 release. The exact manifest URL and chart version belong to the installation page, not to this article.
Once the controller and speakers are running, the first real task is defining a pool of addresses your network will route to the cluster. The repository keeps example manifests in configsamples/, and the CRD kinds live under api/. A pool is declared with the IPAddressPool custom resource, and it must be marked as usable by a Service through an L2Advertisement or a BGPAdvertisement, depending on mode. The repository does not quote these manifests in the README, so the field-level example belongs to the configsamples/ directory and the documentation rather than to this page.
What the repository does make verifiable is the module path and the Go toolchain the project builds with:
module go.universe.tf/metallb
go 1.26.0
toolchain go1.26.8That is the header of go.mod. If you build from source rather than applying released manifests, the toolchain line tells you which Go version the project expects.
With a pool registered and an advertisement referencing it, create an ordinary Service of type LoadBalancer and the controller assigns an address from the pool. The check that matters is from a host outside the cluster: the address should answer, and the node answering should be the speaker that holds the address. Without the advertisement object the pool exists but no speaker answers ARP for the addresses, and a Service can receive an external IP that nothing on the network can reach. That failure is quiet: the Service shows an address, and connections simply time out.
The L2 mode trade-off: one node carries the address
L2 mode is the easier of the two to operate. It needs no router configuration, no AS numbers and no peering sessions. It also concentrates traffic. Because the address resolves to a single node's MAC address, all inbound traffic for that Service lands on that one node, and the other nodes in the cluster do nothing for that address until failover. The documentation describes this as a limitation of the mode; the practical effect is that a Service can saturate the NIC or the CPU of one machine while the rest of the cluster sits idle.
Failover is also not instant. When the owning node stops responding, the address has to move to another speaker, and the surrounding network has to learn the new MAC mapping. ARP caches and switch forwarding tables age out on their own schedules, so recovery time is partly outside MetalLB's control. The project uses memberlist (github.com/hashicorp/memberlist in go.mod) for the node-to-node coordination that decides ownership, which keeps the cluster self-contained but does not make the external network converge faster.
BGP mode removes the single-node bottleneck by advertising the route from multiple speakers so routers can spread traffic. It buys that with configuration: you need routers that will peer with the cluster, and you need to get the peering details right. If you cannot change router configuration or your network team will not open a session, L2 is the only mode available to you, and the concentration is the price.
Limitations and cases where MetalLB is the wrong tool
MetalLB is beta, and the README's own warning about the development branch says the project does not promise backward compatibility for manifests taken from main. Upgrade planning is therefore part of operating it, not an afterthought.
The larger limitation is architectural. MetalLB operates at the routing layer, so it does not terminate TLS, does not route by HTTP host or path, and does not offer the traffic-shaping features of an application-layer load balancer. If your requirement is an ingress with certificate management and path-based routing, MetalLB is not that component; it is the thing that gives your ingress controller an external address in the first place. Teams frequently run both, and the README's ADOPTERS.md file is the project's own record of who does.
There is also a hard boundary on the address pool itself. The addresses you hand to MetalLB must be routable to the cluster and not in use by anything else on that segment. In L2 mode, every address in the pool is answered by whichever node holds it, so an address that overlaps a DHCP scope or a static assignment elsewhere on the LAN will produce intermittent, hard-to-diagnose failures. MetalLB cannot detect that conflict for you, because from its point of view the pool is simply a list of addresses.
Finally, if you are on a cloud provider that already implements Service type LoadBalancer, MetalLB adds a second controller competing for the same Services. It is designed for the case where no such controller exists.
MetalLB compared with running your own proxy tier
The obvious alternative is to skip Service type LoadBalancer entirely and expose workloads through a NodePort plus an external reverse proxy such as nginx or HAProxy running on dedicated machines. The difference in approach is where the address lives. With a proxy tier, the external address belongs to the proxy hosts, and the proxy connects onward to NodePorts or pod addresses. With MetalLB, the external address belongs to the Kubernetes Service itself, and MetalLB makes the network route to a cluster node for that address.
That changes the failure domain and the feature set. A proxy tier can terminate TLS, rewrite paths and apply rate limits, and it gives you one place to read access logs. It also adds a hop and a set of machines to keep alive, and it becomes the single thing every external client depends on. MetalLB keeps the data path inside the cluster and the hop count lower, but it hands application-layer features back to whatever you run behind it.
A second alternative is to keep using NodePort and accept the high port numbers, which requires no extra component at all. That works when clients are few and you control their configuration. It stops working when you need port 443 on a stable address, which is the point at which MetalLB's address pools start to pay for themselves.
Maintenance, licensing and what to verify first
The repository is not archived and the last push was on 2026-09-17, so the project is being worked on. The release cadence visible in the release list is modest: v0.16.0 and metallb-chart-0.16.0 landed on 2026-05-20, with a chart-only release metallb-chart-0.16.1 on 2026-05-27. The version numbering (v0.x) is consistent with the beta maturity label. Plan upgrades around the stable branch rather than main, and read the release notes for each jump, because the README explicitly warns that main-branch manifests may not be backward compatible.
The code is Apache-2.0, a permissive licence that permits commercial use and modification. That is a statement about the licence text, not legal advice; if you redistribute MetalLB inside a product, have your own counsel read the terms, including the notice and attribution requirements that Apache-2.0 carries.
The operational cost sits mostly in the network, not in the software. Someone has to own the address pools, keep them free of conflicts, and, in BGP mode, maintain the peering configuration with your routers. The frr-tools/ directory and the frr-k8s dependency mean FRR is part of that picture for BGP deployments, and it is worth understanding who upgrades it. The troubleshooting/ directory in the repository is the project's own collection of known failure patterns; read it before you file an issue.
Editorial conclusion
Adopt MetalLB if you run Kubernetes on hardware or VMs where no cloud controller assigns external IPs, and you can dedicate an address pool plus control ARP or BGP on that segment. Do not adopt it if you need a vendor-backed SLA or if L2 mode on one node is a single point of failure you cannot accept. Before rollout, check the installation page for the current stable branch and confirm the pool you plan to use is not already routed elsewhere on the network.
Frequently asked questions
What is MetalLB used for?
It assigns external addresses to Kubernetes Services of type LoadBalancer in clusters that have no cloud provider controller, and it makes those addresses reachable using standard routing protocols. The README describes it as a load-balancer implementation for bare metal Kubernetes clusters.
Is MetalLB production ready?
The README carries a project maturity badge marking it beta, and the version numbering is still v0.x. The README also warns that the main branch is the development branch and that manifests taken from it may be unstable or not backward compatible, so it recommends consuming a stable branch.
How do I configure MetalLB in Kubernetes?
You define a pool of addresses with an IPAddressPool resource and then mark it as usable with an L2Advertisement or a BGPAdvertisement, depending on which mode you run. Example manifests for these objects live in the configsamples/ directory of the repository, and the CRD kinds are defined under api/.
What are the limitations of MetalLB?
It works at the routing layer, so it does not terminate TLS or route by HTTP host or path. In L2 mode the address is answered by a single node, which concentrates traffic and makes failover dependent on how quickly the surrounding network relearns the address.
How do I install MetalLB in Kubernetes?
The README does not include install steps; it directs readers to the project website at metallb.io and to the official installation docs, and it advises consuming a stable branch rather than main. The repository also ships a charts/ directory containing the Helm chart.
What is the MetalLB speaker?
The speaker is the component that runs on nodes and announces assigned addresses on the network, using ARP or NDP in L2 mode and BGP in BGP mode. It corresponds to the speaker/ directory in the repository, while the allocation logic lives in controller/.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/metallb-metallb)