Kubernetes Service Types
Learn how ClusterIP, NodePort, and LoadBalancer services work in Kubernetes with a hands-on exercise and troubleshooting tips.
Focus: kubernetes service types
You've deployed a containerized app on Kubernetes. Pods are running — but how does the rest of the world, or even other Pods inside the cluster, actually reach them? Without a stable network endpoint, Pods are ephemeral, IPs change on restart, and load balancing is non-existent. Kubernetes Services solve exactly this problem, abstracting away Pod IP churn and giving you a reliable way to expose your application — whether it's for internal microservice communication, external testing, or production traffic.
The problem this lesson solves
Kubernetes Pods are designed to be thrown away and recreated. Each time a Pod restarts, scales up, or moves to a different node, it gets a new IP address. If your frontend Pod talks to a backend Pod by hardcoding an IP, that connection is guaranteed to break the moment the backend restarts. Worse, if you have multiple replicas of your backend, you need some way to distribute traffic among them.
Services are the Kubernetes abstraction that provides a stable virtual IP (VIP), DNS name, and load balancing across a set of Pods. They decouple the consumer from the Pod lifecycle, enabling zero-downtime scaling and self-healing. This lesson covers the three fundamental service types: ClusterIP, NodePort, and LoadBalancer.
Core concept / mental model
Think of a Service as a traffic cop sitting in front of a group of Pods. The Service selects which Pods to forward traffic to based on labels (just like a Deployment uses labels to manage Pods). It has its own stable IP address and port — that never changes, even when Pods come and go.
Service types as network exposure levels
- ClusterIP → Internal-only VIP. Traffic cannot leave the cluster. Perfect for backend microservices talking to one another (e.g., your
payment-servicetalking touser-service). - NodePort → Opens a specific port on every node in the cluster. External traffic can reach your Service via
<NodeIP>:<NodePort>. Good for development or when you have a static node IP — but not cloud-production-grade. - LoadBalancer → Built on top of NodePort. When you run Kubernetes on a cloud provider (AWS, GCP, Azure), a
LoadBalancerService provisions a real cloud load balancer (e.g., AWS ALB/NLB) that distributes traffic to your Pods. Production-ready.
Pro tip: All three types are built on a common foundation. A
NodePortis effectively aClusterIPwith a port opened on every node. ALoadBalanceris effectively aNodePortplus a cloud load balancer pointing at those node ports.
How it works step by step
1. Label-Based Pod Selection
A Service uses a selector field to match Pods. For example, if you have Pods with the label app: my-app, the Service will automatically discover and forward traffic to all Pods with that label. It doesn't care about individual Pod IPs — it watches the API server for endpoint changes.
2. Stable Virtual IP (ClusterIP)
Kubernetes assigns a static virtual IP from the service CIDR range (usually 10.96.0.0/12). This IP persists for the Service's lifetime. When a client sends a packet to the Service's IP, kube-proxy on each node intercepts it and performs load balancing (either iptables-based or IPVS) to one of the matching Pods.
3. NodePort Allocation
When you specify type: NodePort, Kubernetes allocates a port (by default between 30000-32767) on every node in the cluster. The in-cluster ClusterIP still exists, but now external traffic hitting <any-node-ip>:<node-port> gets routed to the Service VIP → Pod.
4. Cloud Load Balancer Integration
With type: LoadBalancer, the cloud-controller-manager for your provider provisions a real load balancer (e.g., AWS NLB, GCP TCP LB). The cloud LB sends traffic to the NodePort on any node, and from there kube-proxy takes over to the Pods.
Hands-on walkthrough
Let's create a simple web app (nginx) and expose it using each service type.
Prerequisites
- A running Kubernetes cluster (minikube, kind, or real cluster)
kubectlconfigured
Step 1: Deploy a test application
# deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
kubectl apply -f deploy.yaml
kubectl get pods -l app=nginx
Step 2: Create a ClusterIP Service
# svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service-clusterip
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80 # Port exposed by the Service
targetPort: 80 # Port on the Pod
type: ClusterIP
kubectl apply -f svc-clusterip.yaml
kubectl get svc nginx-service-clusterip
Expected output (IP will differ):
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service-clusterip ClusterIP 10.96.123.45 <none> 80/TCP 10s
Test from inside a Pod:
# Run a temporary Pod that curls the Service
kubectl run test-pod --image=busybox --rm -it --restart=Never -- sh -c 'wget -qO- http://nginx-service-clusterip:80'
You should see the nginx welcome page HTML.
Step 3: Upgrade to NodePort
# svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service-nodeport
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080 # Optional: let K8s assign if omitted
type: NodePort
kubectl apply -f svc-nodeport.yaml
kubectl get svc nginx-service-nodeport
Expected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service-nodeport NodePort 10.96.234.56 <none> 80:30080/TCP 10s
Access from outside the cluster (using minikube):
# If using minikube
minikube service nginx-service-nodeport
Or use the node IP:
# Get node IP (for minikube: minikube ip)
NODE_IP=$(minikube ip)
curl http://$NODE_IP:30080
Step 4: Create a LoadBalancer Service
Note: LoadBalancer only works fully on cloud clusters or minikube tunnel. On local clusters, the EXTERNAL-IP shows
<pending>until a load balancer is provisioned.
# svc-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service-lb
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
kubectl apply -f svc-loadbalancer.yaml
kubectl get svc nginx-service-lb -w
Wait for the cloud LB to provision (can take a minute). Once ready:
# Get the external IP
kubectl get svc nginx-service-lb --output jsonpath='{.status.loadBalancer.ingress[0].ip}'
Then curl that IP.
Compare options / when to choose what
| Feature | ClusterIP | NodePort | LoadBalancer |
|---|---|---|---|
| Access from outside cluster | ❌ No | ✅ Yes (node IP + port) | ✅ Yes (cloud LB) |
| Stable IP | ✅ Virtual IP | ✅ Virtual IP | ✅ Cloud LB DNS/IP |
| Port range | Any (no allocation) | 30000-32767 | Cloud LB port (usually 80/443) |
| Load balancing across Pods | ✅ Round-robin via kube-proxy | ✅ Same as ClusterIP | ✅ Hybrid (cloud + kube-proxy) |
| Use case | Microservice-to-microservice | Dev/test, on-prem, or direct access | Production internet-facing |
| Cloud dependency | None | None | Requires cloud provider integration |
| Cost | Free (cluster internal) | Free (cluster internal) | Extra cost (cloud LB charges) |
Pro tip: Starting with
ClusterIPfor internal services and only upgrading toNodePortorLoadBalancerwhen external access is genuinely needed is a best practice. OverusingLoadBalancerfor every microservice will rack up cloud costs quickly.
When to choose what
- Choose ClusterIP if the service is only called by other services inside the cluster (e.g., database, internal API, cache).
- Choose NodePort if you need a quick way to test from outside, or you are running on bare metal without a cloud LB. It's also used as a building block for Ingress controllers.
- Choose LoadBalancer if you need a production-grade external endpoint with a fixed IP and SSL termination. Many teams use one
LoadBalancerper application (or per namespace) and useIngressto route multiple services through a single LB.
Troubleshooting & edge cases
Pods aren't selected
If your Service shows endpoints as empty (kubectl get endpoints <svc>), the selector labels don't match any pods. Verify pod labels:
kubectl describe service nginx-service-clusterip
kubectl get pods --show-labels
NodePort not accessible
- Ensure you are using the node's IP, not the Pod IP.
- Check firewall/security groups — the NodePort range must be open.
- For minikube, use
minikube service <svc>to open a tunnel automatically.
LoadBalancer stuck on <pending>
Common causes:
- Running on a cluster without cloud provider integration (e.g., bare metal, kind, Docker Desktop). Use metallb or an Ingress instead.
- Cloud provider quota exceeded (e.g., too many LBs per region).
- kubectl describe svc nginx-service-lb shows events with error details.
Headless Services (an edge case)
Setting clusterIP: None creates a headless Service — no load balancing, no single IP. Instead, DNS returns all Pod IPs. Useful for stateful workloads (e.g., databases) where each Pod needs a unique identity.
What you learned & what's next
You now understand the three core Kubernetes Service types:
- ClusterIP — stable internal VIP for Pod-to-Pod communication.
- NodePort — opens a port on every node for external access.
- LoadBalancer — cloud-provisioned LB for production internet exposure.
You learned how to create each type, test connectivity, and choose the right type for different scenarios. You also saw common pitfalls like label mismatches and LB provisioning issues.
Next step: Services expose Pods, but managing multiple services externally can become complex and expensive. The next lesson introduces Ingress — a smarter way to route HTTP/HTTPS traffic to multiple services through a single external endpoint, with path- and host-based routing.
Practice recap
Create a second Deployment (app: web) and expose it with a LoadBalancer Service. Then scale the Deployment to 3 replicas and watch the Service endpoints update automatically (kubectl get endpoints <svc> -w). Verify you can curl the external IP from outside the cluster.
Common mistakes
- Forgetting to match the Service selector with the Pod labels — always run
kubectl get pods --show-labelsto verify. - Using NodePort on a cloud cluster without opening the node port range in the security group — traffic will be silently dropped.
- Assuming LoadBalancer works on a local cluster (minikube, kind, Docker Desktop) without a tunnel or MetalLB — it will remain
pending. - Hardcoding a Pod IP instead of using the Service DNS name — breaks on restart.
Variations
- ExternalName Service: Maps a Kubernetes Service to an external DNS name (e.g.,
my-service.default.svc.cluster.local→api.example.com) without proxying traffic through kube-proxy. - Headless Service (
clusterIP: None): Used for stateful apps like databases where each Pod needs its own stable network identity and DNS resolution returns all Pod IPs. - Multi-port Services: Define multiple
portsentries in a single Service to expose different protocols (e.g., port 80 HTTP, port 443 HTTPS) — requiresportornamedisambiguation.
Real-world use cases
- Microservice backend: A
payment-servicePod communicates to auser-servicevia a ClusterIP Service with DNS nameuser-service.default.svc.cluster.local. - Staging environment: A NodePort Service on port 30080 allows developers to test a new release from their laptops by hitting any cluster node IP.
- Production web app: A LoadBalancer Service provisions an AWS ALB that terminates SSL and forwards traffic to the app Pods, serving millions of requests.
Key takeaways
- Kubernetes Services provide a stable virtual IP and DNS name that decouples clients from ephemeral Pod IPs.
- ClusterIP is the default and only works inside the cluster — ideal for internal microservices.
- NodePort opens a static port on every node (range 30000-32767) for external access — good for dev/test.
- LoadBalancer provisions a cloud load balancer and is the production choice for internet-facing services.
- All three types use label selectors to find matching Pods; mismatched labels mean no endpoints.
- Services are the foundation for Ingress — an Ingress points to a ClusterIP Service, not directly to Pods.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.