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
- What is a LoadBalancer Service?
- Why is it Used?
- How it Works
- Where it is Used
- LoadBalancer vs Other Service Types
- Hands-On: Creating a LoadBalancer Service
- LoadBalancer on Local Kubernetes (Minikube)
- Real-World Troubleshooting Examples
- Best Practices
- 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:
| Challenge | Without LoadBalancer | With LoadBalancer |
|---|---|---|
| External access | Manual setup required | Automatic |
| Single point of failure | Pod IP changes on restart | Stable IP/DNS |
| Traffic distribution | No built-in mechanism | Automatic round-robin |
| SSL termination | Must configure manually | Supported natively |
| Health checks | No automatic failover | Built-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
- kubectl apply sends the Service manifest to the API Server.
- The Service Controller in the controller manager watches for new LoadBalancer services.
- The Service Controller calls the Cloud Provider Interface (CPI) — a plugin specific to AWS, GCP, Azure, etc.
- The CPI makes API calls to the cloud to provision a new load balancer (e.g., AWS ELB).
- The cloud provisions the LB and returns the public IP/hostname.
- Kubernetes updates the Service’s
.status.loadBalancer.ingressfield with this IP. - kube-proxy on every node updates iptables or IPVS rules to route traffic for the NodePort to the correct pods.
- 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 Provider | Kubernetes Service | Load Balancer Product |
|---|---|---|
| AWS | EKS | Elastic Load Balancer (ELB/ALB/NLB) |
| Google Cloud | GKE | Cloud Load Balancing |
| Azure | AKS | Azure Load Balancer |
| DigitalOcean | DOKS | DigitalOcean Load Balancer |
| Linode | LKE | NodeBalancers |
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
| Feature | ClusterIP | NodePort | LoadBalancer |
|---|---|---|---|
| External Access | ✗ | ✓ (high port) | ✓ (standard port) |
| Stable IP | ✓ (cluster-internal) | ✗ (node IP can change) | ✓ (cloud-assigned) |
| Production Ready | For internal | Dev/Testing only | ✓ Yes |
| Cloud Cost | Free | Free | $$$ (billed per LB) |
| Port Range | Any | 30000–32767 | Any (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:
| Cause | Fix |
|---|---|
| Minikube without tunnel | Run minikube tunnel in a separate terminal |
| Missing cloud IAM permissions | Attach correct IAM role to worker nodes |
| Cloud quota exceeded | Request quota increase from cloud provider |
| Cloud controller manager not running | Check kube-system namespace pods |
| Wrong cloud provider configured | Verify --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
| Concept | Key Takeaway |
|---|---|
| What | A Kubernetes Service type that provisions a cloud load balancer with a public IP |
| Why | Enables production-grade, stable, and scalable external access to your application |
| How | Cloud Controller Manager calls the cloud API → Cloud LB created → kube-proxy sets up iptables rules → traffic flows pod |
| Where | Cloud environments (EKS, GKE, AKS); bare-metal with MetalLB; local dev with minikube tunnel |
| EXTERNAL-IP pending | Run minikube tunnel locally; check IAM/CPI on cloud |
| 502/503 errors | Check pod readiness probes, endpoints, and resource limits |
| IP changes | Use static/reserved IPs and loadBalancerIP field |
| Cost optimization | Use 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.