Lesson 10 LoadBalancer Deep Dive
Theme:
Lesson 10 · Slide 1

LoadBalancer Deep Dive

Understand how Kubernetes LoadBalancer services provision real infrastructure, how cloud-specific annotations shape that infrastructure, and how to template everything cleanly in Helm.

Learning Objectives

Lesson 10 · Slide 2

How LoadBalancer Services Work

When you create a Service of type LoadBalancer, the cloud controller manager detects the new object and calls the cloud provider API to provision a real load balancer (AWS ELB, GCP forwarding rule, Azure LB, etc.).

The Reconciliation Loop

  1. User creates a Service with type: LoadBalancer
  2. Cloud controller watches for Service events, calls cloud API
  3. Cloud provisions an external LB with a public or internal IP
  4. The controller writes the IP back into status.loadBalancer.ingress
  5. Traffic flows: Client → Cloud LB → NodePort → Pod
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: my-app
Lesson 10 · Slide 3

Cloud-Specific Annotations

Each cloud provider uses annotations on the Service to customize the load balancer it provisions.

AWS

metadata:
  annotations:
    # NLB instead of Classic LB
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    # Instance vs IP targets
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
    # Internal-only
    service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
    # Health check
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: "HTTP"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/healthz"

GCP & Azure

# GCP — internal LB
metadata:
  annotations:
    networking.gke.io/load-balancer-type: "Internal"

# Azure — internal LB
metadata:
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"
    service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/healthz"
Lesson 10 · Slide 4

MetalLB for On-Premises

No cloud controller in bare-metal clusters means type: LoadBalancer stays in Pending forever. MetalLB fills that gap with two modes: L2 (ARP/NDP) and BGP.

# IPAddressPool — defines which IPs MetalLB can hand out
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: production-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.200-192.168.1.250
---
# L2Advertisement — announce IPs via ARP
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advert
  namespace: metallb-system
spec:
  ipAddressPools:
    - production-pool
L2 vs BGP: L2 mode is simpler (one node owns the IP via ARP), but creates a single point of failure. BGP mode distributes routes across multiple nodes for true high availability.
Lesson 10 · Slide 5

externalTrafficPolicy & Session Affinity

Two critical Service fields control how traffic reaches pods and whether client identity is preserved.

externalTrafficPolicy

ValueBehaviour
ClusterDefault. Traffic may hop between nodes. Source IP is SNATed.
LocalTraffic only goes to pods on the receiving node. Source IP is preserved. Uneven distribution possible.

Session Affinity

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

Ensures a client always reaches the same pod for the duration of the timeout. Useful for stateful apps, websockets, file uploads.

Tip: If you need source IP preservation and even distribution, consider using an Ingress controller with proxy protocol instead.
Lesson 10 · Slide 6

Static vs Dynamic IP & Health Checks

IP Allocation

spec:
  type: LoadBalancer
  loadBalancerIP: 35.200.10.50   # pre-allocated static IP
  ports:
    - port: 443
      targetPort: 8443

Health Check Configuration

Cloud LBs perform their own health checks (separate from liveness/readiness probes). Configure via annotations:

metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "10"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5"
    service.beta.kubernetes.io/aws-load-balancer-healthy-threshold: "2"
    service.beta.kubernetes.io/aws-load-balancer-unhealthy-threshold: "3"
Lesson 10 · Slide 7

Templating LB Annotations in Helm

Real charts must work across AWS, GCP, Azure, and bare-metal. Template annotations conditionally from values.yaml.

# values.yaml
service:
  type: LoadBalancer
  annotations: {}
  # Override per cloud:
  # annotations:
  #   service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
  #   service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "mychart.fullname" . }}
  {{- with .Values.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.service.type }}
  {{- if .Values.service.loadBalancerIP }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
  {{- end }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
  selector:
    {{- include "mychart.selectorLabels" . | nindent 4 }}
Lesson 10 · Slide 8

Cost Considerations & Summary

One LB per Service vs Shared Ingress

Per-Service LB

  • AWS NLB: ~$16/month + data processing
  • 10 services = 10 LBs = $160+/month
  • Simple, fully isolated, no shared config

Shared Ingress Controller

  • One LB for the ingress controller
  • All services route via Ingress rules
  • 10 services = 1 LB = ~$16/month
  • Requires L7 routing configuration

Key Takeaways

1 / ?