IPAddressPool is the heart of MetalLB's address management. This episode covers how to define IP ranges as ranges and CIDRs, the autoAssign and avoidBuggyIPs options, the controller's allocation behavior, and IP release when a Service is deleted.

In episode 3 you created your first IPAddressPool without going into detail. Episode 4 digs into the most important part of day-to-day MetalLB operations: IPAddressPool and IPAM (IP Address Management). This is where you determine where external IPs come from, how many are available, and how the controller manages them.
Wrong IPAM can cause problems that are hard to trace: two Services using the same IP, IP conflicts with other devices on the network, or a pool that's mysteriously empty. Understanding how pools work lets you prevent all of these from the start.
An IPAddressPool can contain one or more ranges, either as an explicit range or a CIDR. The following example combines both:
apiVersion: metallb.io/v1beta2
kind: IPAddressPool
metadata:
name: mixed-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.220
- 192.168.2.0/28
- fd00:1::10-fd00:1::1fThe range 192.168.1.200-192.168.1.220 and the CIDR 192.168.2.0/28 are both valid ways to express an IPv4 block, and IPv6 ranges are supported too. MetalLB will enumerate every IP covered and treat them as one set of addresses.
If you only need one IP, you can write it as a single-address range: 192.168.1.200-192.168.1.200. This is useful for giving a specific IP to a particular Service — a technique we'll use again in episode 9 for Service selection.
By default, the controller is free to take IPs from any available pool. The autoAssign: false option restricts a pool to only serve Services that explicitly request it via annotation:
apiVersion: metallb.io/v1beta2
kind: IPAddressPool
metadata:
name: reserved-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.250
autoAssign: falseA pool with autoAssign: false won't be used automatically by Services. A Service that wants an IP from this pool must request it via the metallb.universe.tf/address-pool: reserved-pool annotation.
Certain network devices have bugs with the last bit of a subnet — especially addresses ending in .255 or .0. The avoidBuggyIPs: true option makes the controller avoid IPs ending in 0 and 255 in every /24:
apiVersion: metallb.io/v1beta2
kind: IPAddressPool
metadata:
name: safe-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.0/24
avoidBuggyIPs: trueavoidBuggyIPs: true on the CIDR 192.168.1.0/24 makes this pool only offer IPs 192.168.1.1 through 192.168.1.254, minus the network and broadcast addresses. If your network is full of old, problematic switches, this option is a lifesaver.
When a LoadBalancer Service is created, the controller watches it and starts searching for an IP. Its selection logic is simple but important:
autoAssign: false are skipped unless the Service explicitly requests them.You can observe this process directly through the Service events:
kubectl describe svc nginx
kubectl get events --field-selector involvedObject.name=nginxkubectl get events --field-selector involvedObject.name=nginx will show messages like Allocated IP 192.168.1.200 written by the controller. That's the visual proof of the IPAM process.
MetalLB tracks allocations internally, so two Services will never get the same IP from the same pool. Conflicts can happen when the IP is outside MetalLB's control — for example, used by DHCP or other static devices on the network. That's why it's important to choose a genuinely free IP block, as we prepared in episode 0.
IPs are ephemeral: while a Service exists, the IP is on loan; once the Service is deleted, the IP returns to the pool and can be used by another Service. Verifying is easy:
kubectl delete svc nginx
kubectl get svc -A
kubectl get ipaddresspoolAfter kubectl delete svc nginx, the events record Deallocating IP 192.168.1.200. The IP becomes available again for the next allocation — no manual steps required.
Warning
If you delete an IPAddressPool that's still in use by an existing Service, that Service will lose its IP when its pod is restarted or when the Service is updated. Avoid deleting an active pool in production.
To find out which IPs a given pool has allocated, inspect the Services in that namespace:
kubectl get svc -A -o wideThe EXTERNAL-IP column in kubectl get svc -A -o wide shows all the IPs currently borrowed from the pool. Comparing it against the pool's address list shows how much capacity remains — a topic you'll explore in depth in episode 17.
Episode 4 completes your understanding of MetalLB IPAM: how to define pools with ranges and CIDRs, the autoAssign and avoidBuggyIPs options, the controller's allocation logic, and the IP lifecycle from borrowing to release.
Key takeaways:
IPAddressPool can contain explicit ranges, CIDRs, or both.autoAssign: false makes a pool only serve Services that request it via annotation.avoidBuggyIPs: true avoids addresses ending in 0 and 255.In the next episode, episode 5, we'll discuss Layer 2 mode in depth — how announcements work with ARP/NDP, the role of the leader node that announces IPs, the failover mechanism when a node dies, and the characteristics and limitations of this mode that often surprise beginners.