LoadBalancer Straight to Pods

How does a load balancer send traffic straight to Pods instead of through Node ports? A balancer implementation that targets Pods reads the Service endpoints, here an EndpointSlice, and registers each Pod IP and port, so a connection needs no second Node hop or Node SNAT, and a replaced Pod is registered at its new address. Setting allocateLoadBalancerNodePorts false then allocates no new Node port unless one is requested, and frees none already allocated.

Play the animation 5 steps, animated, best on a wide screen

LoadBalancer Straight to Pods, Kubernetes diagram

LoadBalancer Straight to Pods, step by step

  1. By default a LoadBalancer Service allocates a nodePort, here 31000 on every Node, and a balancer that targets Node ports lists those ports as its targets. This connection lands on Node-2, which runs no backend, so Node-2 DNATs it and, under the default externalTrafficPolicy Cluster, SNATs it on to Pod web-1 on Node-1. Two Nodes handle one connection.

    Play from step 1
  2. A balancer implementation that targets Pods works from the Service instead. Here its controller watches the EndpointSlice of Service web, reads the endpoint 10.244.1.5:8080 and registers it with the balancer as a target, and the Node port rows stop being targets. The field allocateLoadBalancerNodePorts still reads true and 31000 is still allocated: the implementation chose Pod targets, not the field.

    Play from step 2
  3. The next connection goes to the target 10.244.1.5:8080 itself. It reaches Node-1 already addressed to Pod web-1, so there is no second Node hop and no Node SNAT, and Node-2 is not on its path at all. Node port 31000 is still open on both Nodes, but this balancer no longer sends anything to it.

    Play from step 3
  4. Pod web-1 is deleted and its replacement, Pod web-2, starts on Node-2 at 10.244.2.7. The EndpointSlice now lists 10.244.2.7:8080, so the controller registers the new address and 10.244.1.5:8080 stops being a target. How it health checks the new target, here a probe of 10.244.2.7:8080, is up to the implementation: the Kubernetes API does not define it.

    Play from step 4
  5. With the balancer no longer using the Node ports, the Service can set spec.allocateLoadBalancerNodePorts false, which is meant only for balancers that route straight to Pods. No new Node port is allocated unless a port asks for a specific one. Ports already allocated are not freed automatically: 31000 stays until the nodePort entry is removed explicitly from every Service port.

    Play from step 5

Sources