Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5.1 ClusterIP — Internal Communication

⏱️ 5 min read · 6 min hands-on · 🟡 Intermediate

📡 Scenario: Your e-commerce checkout pod is crashing intermittently under load. Every time a new replica boots up with a new IP, your frontend services drop checkout requests because they’re still trying to reach the old pod IP.

After this section, you’ll be able to wire services to stable virtual IPs with automatic load balancing across healthy pods in under 5 minutes.

TL;DR: ClusterIP is the default Service type. It gives a group of pods a stable virtual IP that other pods can reach by name. Pod IPs change constantly; Service IPs never do.

After this section you will be able to:

  • Explain how ClusterIP provides stable virtual IPs and DNS names for ephemeral pods
  • Understand how Endpoints and EndpointSlices link Services to healthy backend pods
  • Verify internal service discovery using curl and nslookup across namespaces

The Problem Services Solve

Pods are ephemeral. Every time a pod restarts, it gets a new IP address. If Service A hardcodes Service B’s pod IP, it breaks the moment B’s pod is replaced.

graph LR
    subgraph "Without a Service"
        A[frontend<br/>10.244.0.5] -->|"hardcoded: 10.244.1.8"| B1[backend<br/>10.244.1.8]
        B1 -->|crashes!| DEAD[💀]
        B2[backend<br/>10.244.1.9] -.->|"new pod, new IP<br/>frontend doesn't know"| Q[❓]
    end
graph LR
    subgraph "With a ClusterIP Service"
        A2[frontend] -->|"always: backend-svc<br/>10.96.45.123"| SVC[Service<br/>ClusterIP<br/>10.96.45.123]
        SVC --> P1[backend pod<br/>10.244.1.9]
        SVC --> P2[backend pod<br/>10.244.1.10]
        SVC --> P3[backend pod<br/>10.244.1.11]
    end

The Service IP never changes. The pods behind it come and go — the Service automatically routes to healthy ones.


ClusterIP Service YAML

# service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
spec:
  type: ClusterIP        # Default — omitting type also gives ClusterIP
  selector:
    app: backend         # Route traffic to pods with this label
  ports:
  - name: http
    port: 80             # Port the Service listens on
    targetPort: 8080     # Port on the pods to forward to
    protocol: TCP
kubectl apply -f service-clusterip.yaml
kubectl get svc backend-svc

Expected output:

NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
backend-svc   ClusterIP   10.96.45.123   <none>        80/TCP    10s

The CLUSTER-IP is the stable virtual IP. EXTERNAL-IP: <none> means it’s not reachable from outside the cluster — by design.


How It Works Under the Hood

When a Service is created, the Endpoints controller builds a list of pod IPs matching the selector:

# See which pod IPs are behind a Service
kubectl get endpoints backend-svc

Expected output:

NAME          ENDPOINTS                                         AGE
backend-svc   10.244.0.4:8080,10.244.0.5:8080,10.244.0.6:8080   1m

kube-proxy on every node watches these Endpoints and programs iptables rules to load-balance traffic to those IPs.

graph TB
    Client[Client Pod] -->|curl backend-svc:80| KP[kube-proxy<br/>iptables rules]
    KP -->|"random selection<br/>(round-robin)"| P1[Pod 10.244.0.4:8080]
    KP --> P2[Pod 10.244.0.5:8080]
    KP --> P3[Pod 10.244.0.6:8080]

Service DNS — The Real Power

Every Service gets an automatic DNS entry maintained by CoreDNS:

# Full DNS name format:
SERVICE-NAME.NAMESPACE.svc.cluster.local

# Examples:
backend-svc.default.svc.cluster.local
postgres.database.svc.cluster.local
redis.cache.svc.cluster.local

# Short form (same namespace only):
backend-svc

From any pod in the cluster:

# These all reach the same Service:
curl http://backend-svc                              # same namespace only
curl http://backend-svc.default                      # same cluster
curl http://backend-svc.default.svc.cluster.local    # fully qualified

🔗 Docker Parallel: In Docker Compose, containers reach each other by service name (http://backend). Kubernetes Services work the same way — but across multiple nodes and with automatic load balancing.


Port Mapping: port vs targetPort

ports:
- port: 80           # What OTHER pods use to reach this Service
  targetPort: 8080   # What port YOUR pods actually listen on

This decouples the internal implementation from the interface. Your app can change from port 8080 to 9000 — just update targetPort, no changes needed by consumers.

# Named targetPort (better — references container's port by name)
spec:
  containers:
  - name: app
    ports:
    - name: http
      containerPort: 8080

# Service can reference by name instead of number
spec:
  ports:
  - port: 80
    targetPort: http   # ← references the named port

Try It

# Deploy a backend
kubectl create deployment backend --image=nginx:1.25 --replicas=3

# Create a ClusterIP Service
kubectl expose deployment backend --port=80 --target-port=80 --name=backend-svc

# Verify
kubectl get svc backend-svc
kubectl get endpoints backend-svc

# Access from another pod (using DNS)
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- \
  curl -s http://backend-svc

# Cleanup
kubectl delete deployment backend
kubectl delete svc backend-svc

Key Takeaways

#ConceptOne-liner
1ClusterIP = stable virtual IPPod IPs change; Service IP never does
2Label selector = routingService routes to pods matching its selector
3Endpoints objectMaintained automatically; lists live pod IPs
4DNS auto-registeredEvery Service gets a DNS name via CoreDNS
5port vs targetPortService port vs pod port — can be different

✅ Quick Check

Q1: A ClusterIP Service has 3 healthy pods and 1 crashed pod (all matching the selector). Does the Service route to the crashed pod?

Answer No. The Endpoints controller only includes pods that are **Running and Ready**. If a pod's readiness probe fails or it crashes, it's removed from the Endpoints list. Traffic is only sent to healthy pods.

Q2: You change a Deployment’s label from app: backend to app: backend-v2. The Service selector is app: backend. What happens?

Answer The Service stops routing to the new pods. The Endpoints list becomes empty (no pods match `app: backend` anymore). Traffic to the Service will fail with connection refused. You'd need to update either the Service selector or the pod labels to restore connectivity.

Q3: Two Services in different namespaces are both named api. Can they coexist? How does a pod tell them apart?

Answer Yes — Services are scoped to a namespace, so two Services named `api` in different namespaces are completely separate objects. Pods distinguish them via the full DNS name: `api.namespace-a.svc.cluster.local` vs `api.namespace-b.svc.cluster.local`. Using the short form `api` only reaches the Service in the same namespace.