Production clusters usually need more than one IP pool. This episode covers separating pools per environment, choosing a pool per Service via annotation, and the impact of externalTrafficPolicy Local versus Cluster on source IPs, connectivity, and failover.

One pool for every Service is simple, but rarely meets real-world needs. On a cluster handling many environments at once — dev, staging, production — or many departments, IPs must be managed with clear separation. Episode 9 covers multi-pool and traffic policies: separating pools per environment, choosing a pool per Service, and understanding the impact of externalTrafficPolicy on source IPs and failover behavior.
Traffic policy is one of the most misunderstood Kubernetes concepts, and its combination with MetalLB makes a very real difference — from preserving the client source IP to deciding whether traffic must be re-routed between nodes.
The most common separation is a pool per environment. Consider these two pools:
apiVersion: metallb.io/v1beta2
kind: IPAddressPool
metadata:
name: dev-pool
namespace: metallb-system
spec:
addresses:
- 192.168.10.200-192.168.10.240
---
apiVersion: metallb.io/v1beta2
kind: IPAddressPool
metadata:
name: prod-pool
namespace: metallb-system
spec:
addresses:
- 192.168.20.200-192.168.20.240Now there are two separate IP blocks: dev-pool for the development environment and prod-pool for production. Development traffic will never enter the production block — and vice versa — as long as pool selection is controlled correctly.
Without control, the controller is free to pick any pool. To make a specific Service use a specific pool, use an annotation on the Service:
apiVersion: v1
kind: Service
metadata:
name: api-production
annotations:
metallb.universe.tf/address-pool: prod-pool
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 8080The metallb.universe.tf/address-pool: prod-pool annotation makes the controller allocate a specific IP from prod-pool. This annotation works with pools that have autoAssign: true, and is the only way to use pools with autoAssign: false.
To make sure allocations match the intended pools:
kubectl get svc -A -o wide
kubectl get ipaddresspoolBy comparing the EXTERNAL-IP in kubectl get svc -A -o wide with the ranges in each pool, you can confirm production Services really use IPs from the production block. This is a quick audit that helps a lot in daily operations.
externalTrafficPolicy on a Service determines how external traffic is forwarded to pods:
This difference has a big impact on source IPs and failover behavior.
apiVersion: v1
kind: Service
metadata:
name: api-local
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app: api
ports:
- port: 8080externalTrafficPolicy: Local keeps the client source IP visible in the pods, because traffic isn't re-SNATed when forwarded to a pod on the same node. The consequence: nodes without an application pod won't capture traffic for that Service.
With Cluster, the source IP that reaches the pod is the node IP (because of SNAT in kube-proxy). With Local, the original client source IP is preserved. If your application needs the real source IP for logging or access policies, Local is the answer.
The main trade-off of Local is more refined failover: MetalLB only directs traffic to nodes that actually have the pod. This keeps the Service healthy when a node dies — traffic automatically avoids nodes without pods. However, if pod replicas only exist on a few nodes, some nodes sit idle as entry points.
Conversely, Cluster distributes traffic evenly across all nodes, but adds one extra hop when the pod is on another node, and the client source IP is lost.
Practical considerations for choosing a policy:
kubectl get svc api-local -o yaml
kubectl describe svc api-localkubectl describe svc api-local shows the External Traffic Policy column in the LoadBalancer section. Checking this column is a good first step when diagnosing source IP behavior in the cluster.
Episode 9 completes multi-pool and traffic policy management: pools per environment, annotations to pick a pool per Service, and the real differences between externalTrafficPolicy: Local and Cluster on source IPs, distribution, and failover.
Key takeaways:
metallb.universe.tf/address-pool annotation chooses a pool for a specific Service.externalTrafficPolicy: Local preserves the real source IP and avoids SNAT.externalTrafficPolicy: Cluster spreads load evenly at the cost of the source IP.Local, nodes without pods don't capture Service traffic.In the next episode, episode 10, we'll discuss validating admission & CRD — how the metallb-controller webhook validates configurations before they're applied, how to read configuration errors, migrating CRDs from v1beta1 to v1beta2, and best practices when upgrading MetalLB.