A Kubernetes Service comes in three types, but they aren’t three equivalent alternatives to freely pick between: each type adds one more layer of exposure on top of the previous one, never replacing it. Understanding this stack avoids choosing a type by reflex rather than by actual need.
ClusterIP: the foundation, invisible from outside
ClusterIP, the default type, assigns a stable virtual IP address, routable only from inside the cluster. One pod can reach another service through this IP (or its DNS name) without ever knowing the actual addresses of the pods behind it, a discovery mechanism already covered in detail elsewhere. Nothing in this type exposes anything outside the cluster: it’s the base layer the other two types build on.
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: ClusterIP
ports:
- port: 8080
NodePort: ClusterIP, plus an open port on every node
NodePort first creates a classic ClusterIP, then additionally opens an identical port (in a reserved range, 30000-32767 by default) on every cluster node’s IP address. Any node, reached on that port, routes traffic to the service, regardless of where the actual pods are running.
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: NodePort
ports:
- port: 8080
nodePort: 31000
This type exposes a service externally without depending on an external load balancer, at the cost of an impractical experience: the client needs to know a specific node’s address and a port in a high, non-standard range, rarely suited for direct public access.
LoadBalancer: NodePort, plus an automatically provisioned cloud load balancer
LoadBalancer goes one step further: it creates the underlying NodePort (itself built on a ClusterIP), then asks the cloud provider (or an implementation like MetalLB in an on-premise environment) to provision an external load balancer distributing traffic to every node’s NodePort.
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: LoadBalancer
ports:
- port: 80
This type offers the experience closest to a stable, directly usable public address, at the cost of one load balancer per Service: ten LoadBalancer-type Services bill ten distinct cloud load balancers, a cost reality explaining why an Ingress or a Gateway API generally sits in front of several ClusterIP Services, rather than multiplying LoadBalancer instances.
Why the stack matters for diagnosis
A connectivity problem on a LoadBalancer Service can sit at any of the three stacked levels: the external load balancer itself, the NodePort on the nodes, or the ClusterIP and routing to pods. Diagnosing from the lowest level first (does ClusterIP work from inside the cluster?) before moving outward quickly isolates where the chain breaks, rather than assuming the problem must be the load balancer just because it’s the layer most visible from outside.
# Check the lowest level first:
# does the ClusterIP respond from inside the cluster?
kubectl run test --image=busybox -it --rm -- wget -O- backend:8080
Takeaway
ClusterIP, NodePort, and LoadBalancer aren’t three equivalent choices but three stacked layers: NodePort includes a ClusterIP, LoadBalancer includes a NodePort. Each added layer solves one more exposure problem at a growing cost, LoadBalancer being the most expensive at scale (one external load balancer per Service), which explains why Ingress and Gateway API exist to pool that exposure in front of several Services. Diagnosing a connectivity problem starting from the lowest level of this stack, rather than the most visible one, stays the most efficient habit, a foundation underlying every network decision made during a Kubernetes migration.