How Does a LoadBalancer Service Work in Kubernetes?

intermediate|servicesdevopssreCKACKAD
TL;DR

A LoadBalancer Service provisions an external load balancer from the cloud provider, giving the Service a public or internal IP address. Traffic flows from the external LB to a NodePort on the cluster nodes and then to the backend Pods.

What Is a LoadBalancer Service?

A LoadBalancer Service is the standard way to expose a Kubernetes Service to external traffic in a cloud environment. When you create a Service with type: LoadBalancer, the cloud controller manager provisions an external load balancer (an AWS ELB/NLB, a GCP Network Load Balancer, an Azure Load Balancer, etc.) and configures it to forward traffic to the cluster nodes.

A LoadBalancer Service is a superset of NodePort, which itself is a superset of ClusterIP. Creating one effectively gives you all three access methods.

Creating a LoadBalancer Service

apiVersion: v1
kind: Service
metadata:
  name: public-api
  namespace: production
spec:
  type: LoadBalancer
  selector:
    app: public-api
  ports:
    - name: https
      protocol: TCP
      port: 443
      targetPort: 8443

After applying:

kubectl get svc public-api -n production
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)         AGE
public-api   LoadBalancer   10.96.20.100   203.0.113.42     443:31542/TCP   45s

The EXTERNAL-IP column shows the IP assigned by the cloud load balancer. The 31542 is the automatically allocated NodePort.

The Full Traffic Path

Internet Client
      │
      │  https://203.0.113.42:443
      ▼
┌───────────────────┐
│ Cloud Load        │
│ Balancer          │
│ (203.0.113.42)    │
└────────┬──────────┘
         │  forwards to NodePort 31542 on healthy nodes
         ▼
┌───────────────────┐
│ Node (any)        │
│ kube-proxy:31542  │
└────────┬──────────┘
         │  DNAT to Pod IP
         ▼
┌───────────────────┐
│ Backend Pod       │
│ :8443             │
└───────────────────┘

The cloud load balancer performs health checks on the NodePort across all nodes and only sends traffic to healthy ones.

Cloud Provider Annotations

Each cloud provider supports specific annotations to customize the load balancer behavior:

AWS

apiVersion: v1
kind: Service
metadata:
  name: public-api
  annotations:
    # Use an NLB instead of a Classic ELB
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    # Create an internal (VPC-only) load balancer
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"
    # Enable cross-zone load balancing
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
  type: LoadBalancer
  selector:
    app: public-api
  ports:
    - port: 443
      targetPort: 8443

GCP

metadata:
  annotations:
    cloud.google.com/load-balancer-type: "Internal"
    networking.gke.io/load-balancer-type: "Internal"

Azure

metadata:
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"

The Cost Problem

Every LoadBalancer Service provisions its own cloud load balancer. In a cluster with dozens of services, this adds up quickly:

| Cloud Provider | Approx. Monthly Cost per LB | |---|---| | AWS (NLB) | ~$16 + data charges | | GCP | ~$18 + data charges | | Azure | ~$18 + data charges |

For HTTP/HTTPS services, the common solution is to deploy a single Ingress controller (NGINX, Traefik, etc.) behind one LoadBalancer Service and use Ingress resources to route traffic to multiple backend Services:

                   ┌─────────────┐
                   │  1 Cloud LB │
                   └──────┬──────┘
                          │
                   ┌──────▼──────┐
                   │  Ingress    │
                   │  Controller │
                   └──────┬──────┘
              ┌───────────┼───────────┐
              ▼           ▼           ▼
         Service A   Service B   Service C

LoadBalancer on Bare-Metal: MetalLB

On bare-metal or on-premises clusters, there is no cloud controller to provision a load balancer. The Service stays in a Pending state:

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
public-api   LoadBalancer   10.96.20.100   <pending>     443:31542/TCP   5m

MetalLB solves this by acting as a network load balancer for bare-metal clusters. It supports two modes:

  1. Layer 2 mode -- Uses ARP/NDP to announce the Service IP from a node.
  2. BGP mode -- Peers with your network routers to advertise Service IPs.
# MetalLB IPAddressPool example
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: production-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.240-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: production-l2
  namespace: metallb-system
spec:
  ipAddressPools:
    - production-pool

With MetalLB installed, your LoadBalancer Service receives an IP from the pool.

Health Checks and externalTrafficPolicy

Cloud load balancers perform health checks on the NodePort. When externalTrafficPolicy: Local is set, only nodes running at least one matching Pod pass the health check. This ensures:

  • No extra network hops between nodes
  • Client source IP is preserved
  • Traffic is not sent to nodes without relevant Pods
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  healthCheckNodePort: 30200

The healthCheckNodePort is automatically allocated, but you can set it explicitly.

Restricting Source IPs

You can limit which external IPs are allowed to reach the Service:

spec:
  type: LoadBalancer
  loadBalancerSourceRanges:
    - 203.0.113.0/24
    - 198.51.100.0/24
  selector:
    app: public-api
  ports:
    - port: 443
      targetPort: 8443

This is implemented as firewall rules on the cloud load balancer.

Summary

LoadBalancer is the production-standard Service type for external traffic on cloud-managed Kubernetes clusters. It provisions a real cloud load balancer that forwards traffic through the NodePort and ClusterIP layers to backend Pods. While powerful and simple to use, it can become expensive at scale, making Ingress controllers a preferred pattern for HTTP workloads. On bare-metal, MetalLB fills the gap by providing load balancer IP allocation.

Why Interviewers Ask This

Interviewers ask this because LoadBalancer is the standard way to expose production services in cloud environments, and understanding its cost, limitations, and relationship to NodePort/ClusterIP is essential for real-world operations.

Common Follow-Up Questions

What happens when you create a LoadBalancer Service on a bare-metal cluster?
The Service stays in a Pending state with no external IP assigned because there is no cloud controller to provision a load balancer. Tools like MetalLB can fill this gap on bare-metal.
How can you create an internal (private) LoadBalancer instead of a public one?
Cloud providers support annotations to request an internal LB. For example, on AWS you add service.beta.kubernetes.io/aws-load-balancer-internal: 'true'.
Does each LoadBalancer Service get its own cloud load balancer?
Yes. Each LoadBalancer Service provisions a separate cloud LB, which can become expensive. An Ingress controller backed by a single LB is more cost-efficient for multiple HTTP services.

Key Takeaways

  • LoadBalancer builds on NodePort and ClusterIP, adding an external cloud load balancer.
  • Each LoadBalancer Service creates a separate cloud LB, so cost management is important.
  • On bare-metal clusters, you need a solution like MetalLB to use LoadBalancer Services.