skip to content

How do you limit a Kubernetes LoadBalancer Service to specific client IP ranges, and what must hold for that limit to work?

level: juniorimportance: should knowfreq 44%

answer

  1. a Service spec list of CIDRs
  2. two enforcers, both conditional
  3. VIP-like balancer keeps client address
  4. nodePort is not covered
  5. empty list adds nothing

basics

~20 s

List the allowed CIDRs in the Service's spec.loadBalancerSourceRanges. The load balancer's own firewall enforces them where the provider supports it, and kube-proxy adds a filter. Both only work if the real client address arrives, and neither protects the NodePort.

solid answer

~40 s

Set `spec.loadBalancerSourceRanges` on the `type: LoadBalancer` Service to a list of CIDRs. The load-balancer implementation turns that list into firewall or security rules where it supports the field, and it ignores the field where it does not. kube-proxy also installs a source filter for packets addressed to the load-balancer IP. That filter only helps with pass-through, VIP-like load balancers that keep the client's source address. A load balancer that proxies the connection, or forwards to the node's `nodePort`, shows the node its own address instead. Two gaps remain. The Service's `nodePort` is **not** subject to the list, so anyone who can reach a node IP can bypass it. And an empty list means no restriction from Kubernetes.

code

yaml · 17 lines
yaml
apiVersion: v1
kind: Service
metadata:
  name: telemetry-gateway
  namespace: ingest
spec:
  type: LoadBalancer
  selector:
    app: telemetry-gateway
  ports:
    - name: mqtts
      port: 8883
      targetPort: 8883
      protocol: TCP
  loadBalancerSourceRanges:
    - 203.0.113.64/26
    - 198.51.100.128/27

go deeper

for a junior

Name the field, spec.loadBalancerSourceRanges, and say it takes CIDR blocks. Then say that the Service's nodePort is not covered by it.

for a middle

Explain the two enforcers, the provider's firewall and kube-proxy's filter, and why kube-proxy's only helps when the load balancer keeps the client address.

for a senior

Show you would test the bypass paths yourself: node IPs on the nodePort, provider health probes outside the ranges, and carrier NAT changing the address you have to allow.

for a principal

Frame the allowlist as one layer of exposure control, weighed against node-level firewalls, disabling NodePort allocation and device authentication. Decide who owns each layer across teams.

## What the field is A Kubernetes **Service** of `type: LoadBalancer` asks the cluster's load-balancer implementation for an external address. Usually that implementation is the cloud-controller-manager's service controller, or a bare-metal equivalent. The field `spec.loadBalancerSourceRanges` is a list of CIDR blocks. It says: *only clients whose source address falls inside one of these ranges may use this load balancer.* In our example, **an IoT telemetry ingest gateway** should accept connections only from two carrier device gateways, `203.0.113.64/26` and `198.51.100.128/27`. It runs on **a 3-control-plane, 27-worker self-managed cluster**. ## Who enforces it Two layers can act on the list, and they behave differently: | Layer | What it does | When it helps | |---|---|---| | The load-balancer implementation | Programs its own firewall or security rules from the list | Only if the implementation supports the field; the API says it is **ignored** otherwise | | **kube-proxy** on each node | Adds a filter so that packets addressed to the load-balancer IP are accepted only from listed sources | Only for **VIP-like** load balancers that deliver packets still addressed to the load-balancer IP, with the client's address intact | In iptables mode, kube-proxy's filter lives in the `nat` table and is checked **before** traffic is marked for masquerade. So it inspects the original client address under either `externalTrafficPolicy` value. ## What must hold for the limit to work 1. **The client's real address must reach whoever enforces the list.** A load balancer that ends the client's connection and opens a new one to the node presents *its own* address. kube-proxy then compares the load balancer's IP, not the device's, against the list. 2. **The implementation must support the field**, or kube-proxy's filter must apply (a VIP-like load balancer). If neither is true, the field silently does nothing. 3. **The ranges must include the addresses you actually see.** Devices behind carrier NAT show the carrier's public address, not the device's own address. 4. **Health checks must still get through.** A provider whose health probes come from addresses outside the list may mark every node unhealthy. How providers handle this varies, so check the provider's documentation. ## The gaps it leaves open - **The `nodePort` is not covered.** A `LoadBalancer` Service normally also allocates a `nodePort`, and kube-proxy does not apply `loadBalancerSourceRanges` to it. Anyone who can reach a node IP on that port reaches the gateway. Close it with network firewalls or security groups on the nodes, or set `allocateLoadBalancerNodePorts: false` where your implementation routes straight to pods. - **In-cluster clients** reach the Service through its ClusterIP, which the list does not govern. Use a **NetworkPolicy** for pod-to-pod restrictions. - **An empty list** adds no restriction from Kubernetes. What remains open depends on the provider's defaults. - **It is not authentication.** A source-address allowlist narrows exposure, but the gateway should still authenticate devices, for example with mutual TLS. ## Relationship to client-IP visibility The filter and the application see different things. Under `externalTrafficPolicy: Cluster`, kube-proxy may filter correctly on the client address and then masquerade the packet, so the gateway's logs show a **node IP**. If the application itself must see the device address, for rate limiting or audit, that is a separate requirement. `externalTrafficPolicy: Local` meets it because it skips the masquerade. ## Checking it - `kubectl get svc telemetry-gateway -o jsonpath='{.spec.loadBalancerSourceRanges}'` shows the list the API stored. - Test from an address outside the ranges: the connection should fail at the load balancer or be dropped at the node. - Test the node IP on the `nodePort` from the same outside address. If it succeeds, the bypass is open. ## Common mistakes - **Assuming the list is enforced everywhere.** Some implementations ignore it silently. Always test from an outside address rather than trusting the stored field. - **Allowing device addresses instead of the addresses the load balancer sees.** Devices behind carrier NAT show the carrier's public addresses, so an allowlist of device subnets blocks everyone. - **Forgetting the nodes.** Node IPs that are reachable from the internet on the `nodePort` bypass the whole allowlist. Pair the field with node-level firewall rules. - **Treating it as a policy for pods.** Pod-to-pod restrictions belong in a NetworkPolicy, not in a load-balancer field.

  • Why can a client outside the allowed ranges still reach the gateway after you set loadBalancerSourceRanges?
    Most likely through the `nodePort`. A `LoadBalancer` Service normally allocates one, and kube-proxy does not apply `loadBalancerSourceRanges` to NodePort traffic, so any host that can reach a node IP on that port gets in. The other common cause is a provider that does not support the field and silently ignores it. Close node ports with network-level firewalls, or disable NodePort allocation where the implementation routes directly to pods.
  • Does loadBalancerSourceRanges govern traffic from pods inside the cluster?
    No. Pods normally reach the Service through its ClusterIP, and the list applies to the load-balancer address. Even a pod sending to the load-balancer IP from inside the cluster is handled by kube-proxy with Cluster semantics rather than through the external load balancer. To restrict pod-to-pod traffic, use a NetworkPolicy, which is a separate mechanism enforced by the network plugin.

saying these in an interview costs you the question

  • loadBalancerSourceRanges only works with externalTrafficPolicy: Local
  • Setting the ranges also locks down the Service's nodePort
  • Every provider enforces the field, so it can never be silently ignored
  • A source-IP allowlist replaces authenticating the devices
  • Pod-to-pod traffic through the ClusterIP is filtered by the same list