KubernetesLoadBalancerKubernetes ServicesNetworkingDevOpsCloud Computing Intermediate 12 min read

LoadBalancer Service

Learn what a LoadBalancer Service is in Kubernetes, how it exposes applications externally, how cloud load balancers work with Kubernetes Services, and how to troubleshoot common networking issues.

LoadBalancer Service in Kubernetes

Table of Contents

  1. What is a LoadBalancer Service?
  2. Why is it Used?
  3. How it Works
  4. Where it is Used
  5. LoadBalancer vs Other Service Types
  6. Hands-On: Creating a LoadBalancer Service
  7. LoadBalancer on Local Kubernetes (Minikube)
  8. Real-World Troubleshooting Examples
  9. Best Practices
  10. Summary

What is a LoadBalancer Service?

A LoadBalancer is one of the four Kubernetes Service types (along with ClusterIP, NodePort, and ExternalName). It is the standard and most production-ready way to expose your application to external internet traffic.

When you create a Service of type LoadBalancer, Kubernetes does two things automatically:

  • It provisions an external load balancer from the underlying cloud provider (AWS ELB, GCP Load Balancer, Azure Load Balancer, etc.).
  • It assigns that load balancer a stable public IP address (or DNS hostname) that external clients can use to reach your application.

Think of it as the “front door” of your application — all traffic from the internet enters through this single stable endpoint, and Kubernetes takes care of routing it to the healthy pods behind the scenes.

Internet
    │
    ▼
┌─────────────────────────┐
│  Cloud Load Balancer     │  ← Provisioned by Cloud Provider
│  (Public IP: 34.x.x.x)  │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  Kubernetes NodePort     │  ← Automatically created by K8s
│  (Node IP : 31xxx)       │
└───────────┬─────────────┘
            │
            ▼
┌─────────────────────────┐
│  ClusterIP Service       │  ← Internal routing layer
└───────────┬─────────────┘
            │
      ┌─────┴──────┐
      ▼            ▼
   Pod-1         Pod-2       ← Your application pods

Why is it Used?

The Problem Without LoadBalancer

Without a LoadBalancer service, you face several challenges:

ChallengeWithout LoadBalancerWith LoadBalancer
External accessManual setup requiredAutomatic
Single point of failurePod IP changes on restartStable IP/DNS
Traffic distributionNo built-in mechanismAutomatic round-robin
SSL terminationMust configure manuallySupported natively
Health checksNo automatic failoverBuilt-in health probing

Key Reasons to Use LoadBalancer

1. Production-Grade External Access NodePort exposes your app on a high port (30000–32767) like http://nodeIP:32000, which is not suitable for production. LoadBalancer gives you a clean IP or DNS endpoint like http://34.102.45.67 or http://myapp.example.com.

2. High Availability Traffic is distributed across all healthy pods. If a pod crashes, the load balancer automatically stops sending traffic to it, ensuring zero downtime for users.

3. Scalability As you scale your deployment (increase replica count), the load balancer automatically discovers and routes traffic to the new pods — no manual reconfiguration needed.

4. Cloud Integration In cloud environments (AWS, GCP, Azure), Kubernetes integrates natively with the cloud’s managed load balancing infrastructure, giving you enterprise-grade networking with a single YAML declaration.


How it Works

Understanding the internal flow of a LoadBalancer service helps immensely during debugging.

Step-by-Step Traffic Flow

Step 1: Client sends request to Public IP (34.x.x.x:80)
    ↓
Step 2: Cloud Load Balancer receives the request
    ↓
Step 3: Cloud LB forwards to one of the Kubernetes Nodes on NodePort (e.g., 31500)
    ↓
Step 4: kube-proxy on the node intercepts the packet via iptables/IPVS rules
    ↓
Step 5: kube-proxy performs DNAT (Destination Network Address Translation)
    ↓
Step 6: Packet is forwarded to a healthy Pod's IP:Port
    ↓
Step 7: Pod processes the request and sends response back

What Happens Internally When You Apply a LoadBalancer Service YAML

  1. kubectl apply sends the Service manifest to the API Server.
  2. The Service Controller in the controller manager watches for new LoadBalancer services.
  3. The Service Controller calls the Cloud Provider Interface (CPI) — a plugin specific to AWS, GCP, Azure, etc.
  4. The CPI makes API calls to the cloud to provision a new load balancer (e.g., AWS ELB).
  5. The cloud provisions the LB and returns the public IP/hostname.
  6. Kubernetes updates the Service’s .status.loadBalancer.ingress field with this IP.
  7. kube-proxy on every node updates iptables or IPVS rules to route traffic for the NodePort to the correct pods.
  8. Endpoints controller continuously watches pods with matching labels and updates the Endpoints object, ensuring only healthy pods receive traffic.

The Role of kube-proxy

kube-proxy runs on every node and manages the low-level network rules:

# View iptables rules created by kube-proxy
sudo iptables -t nat -L KUBE-SERVICES -n --line-numbers

# View IPVS rules (if IPVS mode is used)
sudo ipvsadm -Ln

Where it is Used

Cloud Environments (Primary Use Case)

LoadBalancer services are natively supported and most effective on managed Kubernetes platforms:

Cloud ProviderKubernetes ServiceLoad Balancer Product
AWSEKSElastic Load Balancer (ELB/ALB/NLB)
Google CloudGKECloud Load Balancing
AzureAKSAzure Load Balancer
DigitalOceanDOKSDigitalOcean Load Balancer
LinodeLKENodeBalancers

Common Real-World Use Cases

E-Commerce Application An online store exposes its frontend (React/Next.js) behind a LoadBalancer. During a flash sale, pods scale from 3 to 30 replicas — the LB seamlessly routes traffic across all 30 pods.

Microservices API Gateway A fintech platform runs 10 microservices. Only the API gateway service is exposed via LoadBalancer. All other services use ClusterIP for internal communication.

Gaming Backend An online game exposes its matchmaking server via LoadBalancer with a static IP so game clients can connect reliably without DNS changes.

CI/CD Pipeline A company exposes Jenkins or ArgoCD via LoadBalancer so developers can access it securely from outside the office network.


LoadBalancer vs Other Service Types

ClusterIP   →  Internal only (pod-to-pod communication)
NodePort    →  External via Node IP + High Port (dev/testing)
LoadBalancer → External via Cloud LB + Stable IP (production)
ExternalName → DNS alias for external services
FeatureClusterIPNodePortLoadBalancer
External Access✓ (high port)✓ (standard port)
Stable IP✓ (cluster-internal)✗ (node IP can change)✓ (cloud-assigned)
Production ReadyFor internalDev/Testing only✓ Yes
Cloud CostFreeFree$$$ (billed per LB)
Port RangeAny30000–32767Any (80, 443, etc.)
Built-in Health Checks

Cost Note: Each LoadBalancer service provisions a separate cloud load balancer, which incurs cloud costs. For multiple services, consider using a single Ingress Controller (with one LoadBalancer) instead.


Hands-On: Creating a LoadBalancer Service

Step 1: Create a Sample Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
  namespace: default
  labels:
    app: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
        - name: webapp
          image: nginx:1.25
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "250m"
              memory: "256Mi"
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 20
kubectl apply -f deployment.yaml
kubectl get pods -l app=webapp

Step 2: Create the LoadBalancer Service

# loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp-loadbalancer
  namespace: default
  annotations:
    # AWS-specific: use Network Load Balancer instead of Classic LB
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    # GCP-specific: use a specific static IP
    # cloud.google.com/load-balancer-type: "External"
  labels:
    app: webapp
spec:
  type: LoadBalancer
  selector:
    app: webapp           # Must match pod labels
  ports:
    - name: http
      protocol: TCP
      port: 80            # Port exposed externally
      targetPort: 80      # Port on the pod/container
    - name: https
      protocol: TCP
      port: 443
      targetPort: 443
  sessionAffinity: None   # Round-robin load balancing
  # Optionally restrict source IP ranges:
  # loadBalancerSourceRanges:
  #   - "203.0.113.0/24"
kubectl apply -f loadbalancer-service.yaml

Step 3: Verify the Service

# Check if EXTERNAL-IP is assigned (may take 1-3 minutes on cloud)
kubectl get service webapp-loadbalancer

# Expected output:
# NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
# webapp-loadbalancer    LoadBalancer   10.96.45.231   34.102.100.50   80:31245/TCP   2m

# Describe for full details
kubectl describe service webapp-loadbalancer

Step 4: Test the Service

# Get the external IP
EXTERNAL_IP=$(kubectl get svc webapp-loadbalancer -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

# Test HTTP access
curl http://$EXTERNAL_IP

# Or if hostname-based (AWS uses DNS names):
HOSTNAME=$(kubectl get svc webapp-loadbalancer -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
curl http://$HOSTNAME

LoadBalancer on Local Kubernetes (Minikube)

On local environments like Minikube, there is no cloud provider to provision a real LoadBalancer. The service will stay in <pending> status for the EXTERNAL-IP.

Solution 1: minikube tunnel

# In a separate terminal, run:
minikube tunnel

# This requires sudo and creates a network route
# Now EXTERNAL-IP will be assigned (usually 127.0.0.1 or a local IP)
kubectl get service webapp-loadbalancer
# NAME                  TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
# webapp-loadbalancer   LoadBalancer   10.96.45.231  127.0.0.1     80:31245/TCP   5m

Solution 2: MetalLB (Bare Metal Load Balancer)

For on-premise or bare-metal clusters (no cloud), use MetalLB:

# Install MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.3/config/manifests/metallb-native.yaml

# Configure an IP address pool
cat <<EOF | kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.200-192.168.1.250   # IPs available on your local network
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
EOF

Real-World Troubleshooting Examples

Troubleshooting Scenario 1: EXTERNAL-IP Stuck in <pending>

Symptom:

kubectl get svc webapp-loadbalancer
# NAME                  TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
# webapp-loadbalancer   LoadBalancer   10.96.45.231  <pending>     80:31245/TCP   10m

Step-by-Step Diagnosis:

# Step 1: Check events on the service
kubectl describe svc webapp-loadbalancer
# Look for "Events:" section at the bottom

# Step 2: Check cloud controller manager logs
kubectl logs -n kube-system -l component=cloud-controller-manager --tail=50

# Step 3: On Minikube - check if tunnel is running
minikube tunnel --cleanup
minikube tunnel

# Step 4: Check if cloud provider permissions are correct (AWS example)
aws iam get-role --role-name my-eks-node-role

Common Causes and Fixes:

CauseFix
Minikube without tunnelRun minikube tunnel in a separate terminal
Missing cloud IAM permissionsAttach correct IAM role to worker nodes
Cloud quota exceededRequest quota increase from cloud provider
Cloud controller manager not runningCheck kube-system namespace pods
Wrong cloud provider configuredVerify --cloud-provider flag in kube-apiserver

Troubleshooting Scenario 2: External IP Assigned but Service Unreachable

Symptom:

curl http://34.102.100.50
# curl: (7) Failed to connect to 34.102.100.50 port 80: Connection refused

Step-by-Step Diagnosis:

# Step 1: Verify pods are running and ready
kubectl get pods -l app=webapp
kubectl describe pod <pod-name>

# Step 2: Check endpoints — are pods registered?
kubectl get endpoints webapp-loadbalancer
# If ENDPOINTS shows <none>, the selector is not matching any pods!

# Step 3: Verify pod labels match service selector
kubectl get pods --show-labels
# Look for "app=webapp" label

kubectl get svc webapp-loadbalancer -o yaml | grep -A5 selector
# Should show: app: webapp

# Step 4: Test connectivity directly to a pod
kubectl port-forward pod/<pod-name> 8080:80
curl http://localhost:8080    # If this works, the issue is in LB routing

# Step 5: Check if the port is correct on the container
kubectl exec -it <pod-name> -- netstat -tulnp | grep 80
# or
kubectl exec -it <pod-name> -- curl localhost:80

Fix Example — Label Mismatch:

# Service selector says: app: webapp
# But pod labels say: app: web-app  ← MISMATCH!

# Fix by patching the service selector:
kubectl patch svc webapp-loadbalancer -p '{"spec":{"selector":{"app":"web-app"}}}'

# Verify endpoints are now populated:
kubectl get endpoints webapp-loadbalancer
# NAME                  ENDPOINTS                                         AGE
# webapp-loadbalancer   10.244.1.5:80,10.244.2.3:80,10.244.3.7:80        1m

Troubleshooting Scenario 3: Intermittent 502/503 Errors Under Load

Symptom: Users occasionally get 502 Bad Gateway or 503 Service Unavailable during peak traffic.

Step-by-Step Diagnosis:

# Step 1: Check pod health and restarts
kubectl get pods -l app=webapp
# Look for high RESTARTS count

# Step 2: Check pod resource usage
kubectl top pods -l app=webapp
# If CPU/Memory is near limits, pods may be OOMKilled

# Step 3: Check pod logs for errors
kubectl logs -l app=webapp --tail=100 --prefix=true

# Step 4: Check liveness/readiness probe failures
kubectl describe pod <pod-name> | grep -A10 "Liveness\|Readiness"

# Step 5: Check if HPA is scaling correctly
kubectl get hpa
kubectl describe hpa webapp-hpa

Fix — Add/Tune Readiness Probes:

# Add proper readiness probe so LB doesn't send traffic to unready pods
readinessProbe:
  httpGet:
    path: /healthz
    port: 80
  initialDelaySeconds: 10    # Wait 10s before first check
  periodSeconds: 5           # Check every 5s
  failureThreshold: 3        # Mark unready after 3 consecutive failures
  successThreshold: 1        # Mark ready after 1 success

# Add pod disruption budget to prevent all pods from restarting simultaneously
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: webapp-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: webapp

Troubleshooting Scenario 4: LoadBalancer IP Changed After Recreation

Symptom: After deleting and recreating the service, the external IP changed and DNS/clients are broken.

Prevention — Use Static IP:

# GKE: Reserve a static IP first
# gcloud compute addresses create webapp-static-ip --global

apiVersion: v1
kind: Service
metadata:
  name: webapp-loadbalancer
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "webapp-static-ip"  # GKE
    # service.beta.kubernetes.io/aws-load-balancer-eip-allocations: "eipalloc-xxx"  # AWS
spec:
  type: LoadBalancer
  loadBalancerIP: "34.102.100.50"   # Request a specific static IP
  ...

Quick Troubleshooting Command Reference

# ─── Service Inspection ───────────────────────────────────────────
kubectl get svc                                  # List all services
kubectl describe svc <svc-name>                  # Full service details + events
kubectl get svc <svc-name> -o yaml               # Raw YAML with status

# ─── Endpoints (Pod Registration) ────────────────────────────────
kubectl get endpoints <svc-name>                 # See which pods are registered
kubectl describe endpoints <svc-name>            # Detailed endpoint info

# ─── Pod Health ───────────────────────────────────────────────────
kubectl get pods -l app=<label> -o wide          # Pod status + node placement
kubectl describe pod <pod-name>                  # Events, probes, conditions
kubectl logs <pod-name> --previous               # Logs from crashed container
kubectl exec -it <pod-name> -- /bin/sh           # Shell into pod

# ─── Network Debugging ────────────────────────────────────────────
kubectl port-forward svc/<svc-name> 8080:80      # Test service locally
kubectl run debug --image=busybox --rm -it -- wget -qO- http://<svc-name>  # DNS test

# ─── Events (Cluster-Wide) ────────────────────────────────────────
kubectl get events --sort-by='.lastTimestamp'    # Recent cluster events
kubectl get events -n kube-system                # System namespace events

# ─── Cloud Controller ─────────────────────────────────────────────
kubectl logs -n kube-system -l component=cloud-controller-manager

Best Practices

1. Use Annotations for Cloud-Specific Configuration

metadata:
  annotations:
    # AWS: Use Network Load Balancer (better performance than Classic)
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"

    # GCP: Internal load balancer (VPC-only access)
    cloud.google.com/load-balancer-type: "Internal"

    # Azure: Use Standard SKU (required for AZ support)
    service.beta.kubernetes.io/azure-load-balancer-sku: "standard"

2. Restrict Access with loadBalancerSourceRanges

spec:
  type: LoadBalancer
  loadBalancerSourceRanges:
    - "10.0.0.0/8"       # Allow internal corporate network
    - "203.0.113.5/32"   # Allow specific office IP

3. Prefer Ingress for Multiple HTTP Services

Instead of creating one LoadBalancer per service (expensive), use a single Ingress Controller:

One LoadBalancer (Ingress Controller)
        │
        ├── /api      → api-service (ClusterIP)
        ├── /frontend → frontend-service (ClusterIP)
        └── /admin    → admin-service (ClusterIP)

4. Always Define Resource Requests and Limits

Pods without resource limits can starve other pods, leading to 503 errors under load.

5. Set externalTrafficPolicy: Local for Source IP Preservation

spec:
  type: LoadBalancer
  externalTrafficPolicy: Local   # Preserves client source IP
                                  # Note: May cause uneven traffic distribution

6. Monitor with Metrics

# Check load balancer metrics via kubectl top
kubectl top pods -l app=webapp
kubectl top nodes

# Integrate with Prometheus/Grafana for production monitoring
# Use kube-state-metrics to track service endpoint availability

Summary

ConceptKey Takeaway
WhatA Kubernetes Service type that provisions a cloud load balancer with a public IP
WhyEnables production-grade, stable, and scalable external access to your application
HowCloud Controller Manager calls the cloud API → Cloud LB created → kube-proxy sets up iptables rules → traffic flows pod
WhereCloud environments (EKS, GKE, AKS); bare-metal with MetalLB; local dev with minikube tunnel
EXTERNAL-IP pendingRun minikube tunnel locally; check IAM/CPI on cloud
502/503 errorsCheck pod readiness probes, endpoints, and resource limits
IP changesUse static/reserved IPs and loadBalancerIP field
Cost optimizationUse Ingress instead of one LB per service

The LoadBalancer service type is the simplest and most reliable way to get external traffic into your Kubernetes cluster in production. For local development, use minikube tunnel or MetalLB to simulate the cloud load balancer behavior. Always pair LoadBalancer services with proper health probes, resource limits, and monitoring to ensure high availability.


Next: Module 5 - Kubernetes Networking → 04-Ingress — Learn how to use a single LoadBalancer with path-based routing for multiple services.