Kubernetes Service Types Comparison
Compare Kubernetes Service types: ClusterIP for internal access, NodePort for direct port exposure, LoadBalancer for external traffic.
π‘ Quick Answer: `ClusterIP` (default) = internal-only access within the cluster. `NodePort` = exposes on every nodeβs IP at a static port (30000-32767). `LoadBalancer` = provisions an external cloud load balancer. `ExternalName` = DNS CNAME alias to an external service.
The Problem
You need to expose your application, but Kubernetes offers four Service types with different networking behaviors. Choosing wrong means either no external access when you need it, or unnecessary exposure when you donβt.
flowchart TB
subgraph CLUSTER["Kubernetes Cluster"]
CIP["ClusterIP<br/>10.96.0.1:80<br/>Internal only"]
NP["NodePort<br/>NodeIP:30080<br/>All nodes"]
LB["LoadBalancer<br/>External IP:80<br/>Cloud LB"]
PODS["Backend Pods"]
end
INTERNAL["Internal Pod"] -->|"curl svc:80"| CIP
CIP --> PODS
EXTERNAL1["External Client"] -->|"curl NodeIP:30080"| NP
NP --> PODS
EXTERNAL2["External Client"] -->|"curl ExternalIP:80"| LB
LB --> NP
NP --> PODSThe Solution
ClusterIP (Default)
Internal-only access β pods within the cluster can reach this service:
apiVersion: v1
kind: Service
metadata:
name: backend-api
spec:
type: ClusterIP # Default β can be omitted
selector:
app: backend
ports:
- port: 80 # Service port
targetPort: 8080 # Container port# Accessible only from within the cluster
kubectl exec frontend-pod -- curl http://backend-api:80
kubectl exec frontend-pod -- curl http://backend-api.default.svc.cluster.local:80NodePort
Exposes the service on a static port on every node:
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
type: NodePort
selector:
app: web
ports:
- port: 80 # ClusterIP port (internal)
targetPort: 8080 # Container port
nodePort: 30080 # External port (30000-32767)# Accessible from outside on any node IP
curl http://192.168.1.10:30080 # worker-01
curl http://192.168.1.11:30080 # worker-02
curl http://192.168.1.12:30080 # worker-03 (even if no pods here)LoadBalancer
Provisions an external load balancer (cloud or MetalLB):
apiVersion: v1
kind: Service
metadata:
name: public-web
annotations:
# AWS-specific
service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080kubectl get svc public-web
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# public-web LoadBalancer 10.96.0.50 203.0.113.100 80:31234/TCP
# Accessible from internet
curl http://203.0.113.100ExternalName
DNS alias to an external service (no proxying):
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.example.com # DNS CNAME# Resolves to db.example.com
kubectl exec app -- nslookup external-db
# external-db.default.svc.cluster.local β db.example.comComparison Table
| Feature | ClusterIP | NodePort | LoadBalancer | ExternalName |
|---|---|---|---|---|
| Internal access | β | β | β | β (DNS only) |
| External access | β | β (node IP) | β (LB IP) | N/A |
| Port range | Any | 30000-32767 | Any | N/A |
| Load balancing | kube-proxy | kube-proxy | Cloud LB | None |
| Cost | Free | Free | Cloud LB fee | Free |
| Use case | Microservices | Dev/testing | Production | External DB |
Headless Service (ClusterIP: None)
Returns pod IPs directly β no load balancing:
apiVersion: v1
kind: Service
metadata:
name: db-headless
spec:
clusterIP: None # Headless β no virtual IP
selector:
app: postgres
ports:
- port: 5432# Returns individual pod IPs
kubectl exec app -- nslookup db-headless
# db-headless.default.svc.cluster.local β 10.244.1.5, 10.244.2.8, 10.244.3.12
# Used by StatefulSets for stable network identity
# postgres-0.db-headless.default.svc.cluster.local β 10.244.1.5Common Patterns
# Internal microservice β ClusterIP
type: ClusterIP
# Development access β NodePort
type: NodePort
# Production web app β LoadBalancer (or Ingress + ClusterIP)
type: LoadBalancer
# Database in another VPC β ExternalName
type: ExternalName
# StatefulSet discovery β Headless
clusterIP: NoneCommon Issues
| Issue | Cause | Fix |
|---|---|---|
| LoadBalancer stuck in ` | No cloud LB provisioner | Install MetalLB for bare-metal |
| NodePort connection refused | Firewall blocking 30000-32767 | Open port range in firewall/security group |
| Service not resolving | Wrong selector labels | Verify `kubectl get endpoints svc-name` shows IPs |
| External traffic not reaching pods | `externalTrafficPolicy: Cluster` (default) | Set to `Local` to preserve source IP |
| ClusterIP not accessible from outside | By design β internal only | Use NodePort, LoadBalancer, or Ingress |
Best Practices
- Default to ClusterIP β expose externally only when needed
- Use Ingress/Gateway API instead of NodePort β better routing, TLS, virtual hosts
- Use `externalTrafficPolicy: Local` for LoadBalancer β preserves client IP
- Avoid NodePort in production β limited port range, no DNS, no TLS
- Use headless for StatefulSets β enables direct pod-to-pod communication
- Annotate LoadBalancer services β cloud-specific settings (NLB vs ALB, internal vs external)
Key Takeaways
- ClusterIP = internal only (default, most common)
- NodePort = all nodes listen on port 30000-32767 (dev/testing)
- LoadBalancer = cloud LB with external IP (production)
- ExternalName = DNS CNAME to external service (no proxy)
- Headless (clusterIP: None) = returns pod IPs directly (StatefulSets)
- In production, prefer Ingress/Gateway API + ClusterIP over direct LoadBalancer per service

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
