Skip to content

Service Types

Understand every Kubernetes Service type and when to use each one.

Time: ~15 minutes Difficulty: Intermediate

  • ClusterIP: internal-only access (default)
  • NodePort: expose on a static port on every node
  • LoadBalancer: external access via cloud load balancer
  • Headless: direct pod IPs without a virtual IP
  • ExternalName: DNS alias to an external service
  • When to use which type
TypeAccessible FromUse Case
ClusterIPInside cluster onlyService-to-service communication
NodePortOutside via <NodeIP>:<port>Dev/testing, on-prem without LB
LoadBalancerOutside via cloud LBProduction external access
HeadlessInside cluster (pod IPs)StatefulSets, service discovery
ExternalNameInside cluster (DNS alias)Aliasing external services

Navigate to the demo directory:

Terminal window
cd demos/service-types
Terminal window
kubectl apply -f manifests/namespace.yaml
kubectl apply -f manifests/app.yaml
kubectl apply -f manifests/clusterip.yaml
kubectl apply -f manifests/nodeport.yaml
kubectl apply -f manifests/loadbalancer.yaml
kubectl apply -f manifests/headless.yaml
kubectl apply -f manifests/externalname.yaml

Check all services:

Terminal window
kubectl get svc -n service-types-demo

This service is only reachable from inside the cluster:

Terminal window
# This works from inside the cluster
kubectl run curl --rm -it --image=curlimages/curl -n service-types-demo -- \
curl -s http://echo-clusterip
# You CANNOT reach it from your machine directly
# Use port-forward instead:
kubectl port-forward svc/echo-clusterip 8080:80 -n service-types-demo
# Then open http://localhost:8080

Reachable from outside the cluster on port 30080:

Terminal window
# Get the minikube IP
minikube ip
# Access directly (no port-forward needed)
curl http://$(minikube ip):30080

Or use minikube’s helper:

Terminal window
minikube service echo-nodeport -n service-types-demo --url

On minikube, LoadBalancer stays in “Pending” unless you run minikube tunnel:

Terminal window
# In a separate terminal, start the tunnel
minikube tunnel
# Now check the EXTERNAL-IP
kubectl get svc echo-loadbalancer -n service-types-demo
# Access via the external IP
curl http://<EXTERNAL-IP>

A headless service returns pod IPs instead of a virtual IP. DNS resolves to all pod addresses:

Terminal window
# Notice clusterIP is "None"
kubectl get svc echo-headless -n service-types-demo
# From inside the cluster, DNS returns individual pod IPs
kubectl run dig --rm -it --image=busybox:1.36 -n service-types-demo -- \
nslookup echo-headless.service-types-demo.svc.cluster.local

Compare with ClusterIP DNS (returns one virtual IP):

Terminal window
kubectl run dig --rm -it --image=busybox:1.36 -n service-types-demo -- \
nslookup echo-clusterip.service-types-demo.svc.cluster.local

An ExternalName service creates a CNAME record. No proxying, no pods:

Terminal window
# Notice there's no cluster IP or endpoints
kubectl get svc external-api -n service-types-demo
kubectl get endpoints external-api -n service-types-demo
# From inside the cluster, the service name resolves to httpbin.org
kubectl run curl --rm -it --image=curlimages/curl -n service-types-demo -- \
curl -s http://external-api/get
manifests/
namespace.yaml # service-types-demo namespace
app.yaml # echoserver Deployment (2 replicas)
clusterip.yaml # ClusterIP service (internal only)
nodeport.yaml # NodePort on port 30080
loadbalancer.yaml # LoadBalancer (needs minikube tunnel)
headless.yaml # Headless (clusterIP: None)
externalname.yaml # ExternalName pointing to httpbin.org

How traffic flows for each type:

ClusterIP: Pod ──> kube-proxy ──> Pod (cluster internal only)
NodePort: Client ──> Node:30080 ──> kube-proxy ──> Pod
LoadBalancer: Client ──> Cloud LB ──> Node:NodePort ──> kube-proxy ──> Pod
Headless: Pod ──> DNS lookup ──> Pod IP directly (no kube-proxy)
ExternalName: Pod ──> DNS CNAME ──> external.host.com
ScenarioService Type
Microservice calling another microserviceClusterIP
Expose a service during local developmentNodePort
Production external traffic (cloud)LoadBalancer
Production external traffic (on-prem)NodePort + external LB, or Ingress
StatefulSet pod discoveryHeadless
Point to an external database or APIExternalName
HTTP routing by path/hostIngress (not a Service type, but uses ClusterIP)
  1. Compare endpoints for ClusterIP vs Headless:

    Terminal window
    kubectl get endpoints echo-clusterip -n service-types-demo
    kubectl get endpoints echo-headless -n service-types-demo
  2. Scale the Deployment and watch endpoints update:

    Terminal window
    kubectl scale deployment echo-server --replicas=4 -n service-types-demo
    kubectl get endpoints echo-headless -n service-types-demo
  3. Check iptables rules created by kube-proxy:

    Terminal window
    minikube ssh -- sudo iptables -t nat -L KUBE-SERVICES | grep echo
Terminal window
kubectl delete namespace service-types-demo

See docs/deep-dive.md for a detailed explanation of kube-proxy modes (iptables vs IPVS), EndpointSlices, session affinity, external traffic policy, and Ingress vs Gateway API.

Move on to ConfigMaps & Secrets to learn how to manage application configuration and sensitive data.