62.2 ClusterIP Services and EndpointSlices: a stable address for changing pods
Why: the problem
Suppose Dream11's contest service calls the wallet service before every team entry. The wallet runs as 6 pods, and during a cricket match they are scaled, restarted and replaced all the time. Every replacement pod has a new IP. If the contest service kept a list of wallet pod IPs, it would be wrong within minutes. A Service fixes this. A ClusterIP Service gets one virtual IP from the service range and a DNS name like wallet.payments.svc.cluster.local. It never changes while the Service exists. The Service has a label selector, and the EndpointSlice controller keeps a live list of the IPs of matching pods that are Ready. kube-proxy on every node programs rules so traffic to the virtual IP goes to one of those pods. ClusterIP is the default type and is reachable only from inside the cluster. It is what you use for almost all service-to-service calls. NodePort and LoadBalancer, in the next lessons, are built on top of it. In this lesson we create a ClusterIP Service for a wallet Deployment, watch EndpointSlices change as pods come and go, see that unready pods are removed, use named ports, and create a Service without a selector that points to a database outside the cluster.
What: every term in plain English
- Service — A stable virtual IP and name in front of a set of pods.
- ClusterIP — The default Service type, reachable only inside the cluster.
- selector — Labels that choose which pods receive traffic.
- port / targetPort — port is what clients call; targetPort is the container port (number or name).
- EndpointSlice — An object listing the ready pod IPs and ports for a Service, up to 100 per slice.
- Endpoints (legacy) — The older single-object list; still shown but deprecated in favour of EndpointSlices.
- Ready condition — Only Ready pods receive traffic; readiness probes control this.
- Named port — A containerPort name like http that targetPort can refer to.
- Selectorless Service — A Service without a selector; you manage its EndpointSlice by hand.
- Service CIDR — The range ClusterIPs come from, 10.96.0.0/12 in our cluster.
How: step by step
- Deploy the wallet with 3 replicas. Labels app: wallet.
- Create a ClusterIP Service. Read its IP and EndpointSlice.
- Call it from a curl pod. See answers from different pods.
- Scale and delete pods. Watch the EndpointSlice update.
- Make one pod unready. It drops out of the slice.
- Create a selectorless Service for an external database. Write the EndpointSlice yourself.
- Clean up. Keep the cluster for 62.3.
Example 1: Put a ClusterIP Service in front of the wallet Deployment
Goal. Put a ClusterIP Service in front of the wallet Deployment.
wallet.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wallet
namespace: net
spec:
replicas: 3
selector:
matchLabels:
app: wallet
template:
metadata:
labels:
app: wallet
spec:
containers:
- name: wallet
image: hashicorp/http-echo:1.0
args: ["-listen=:5678", "-text=wallet from $(POD_NAME)"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- name: http
containerPort: 5678
readinessProbe:
httpGet:
path: /
port: http
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {memory: 32Mi}
---
apiVersion: v1
kind: Service
metadata:
name: wallet
namespace: net
spec:
type: ClusterIP
selector:
app: wallet
ports:
- name: http
port: 80
targetPort: http
What each line does
http-echo— A tiny server that returns the text we give it, so we can see which pod answered.$(POD_NAME)— Kubernetes expands env vars in args.selector app: wallet— Matches the three pods.port 80, targetPort http— Clients call port 80; traffic goes to the named port 5678.readinessProbe— Only pods passing it are added to the slice.
Green output
$ kubectl apply -f wallet.yaml
$ kubectl get svc -n net wallet
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
wallet ClusterIP 10.104.31.77 <none> 80/TCP
$ kubectl run curl -n net --image=curlimages/curl:8.10.1 --restart=Never -- sleep 3600
$ for i in 1 2 3 4 5 6; do kubectl exec -n net curl -- curl -s wallet; done
wallet from wallet-7c9f8d6b5-h2k4p
wallet from wallet-7c9f8d6b5-x9m2q
wallet from wallet-7c9f8d6b5-h2k4p
wallet from wallet-7c9f8d6b5-r7t5w
wallet from wallet-7c9f8d6b5-x9m2q
wallet from wallet-7c9f8d6b5-r7t5w
Expected result. One name, one IP, and requests spread across all three pods. The short name wallet works because curl runs in the same namespace.
Example 2: Watch the EndpointSlice follow scaling, deletes and readiness
Goal. Watch the EndpointSlice follow scaling, deletes and readiness.
watch.sh
kubectl get endpointslices -n net -l kubernetes.io/service-name=wallet -w &
kubectl scale deploy wallet -n net --replicas=5
sleep 10
kubectl delete pod -n net -l app=wallet --field-selector=status.phase=Running --wait=false | head -1
sleep 10
kill %1
What each line does
-l kubernetes.io/service-name— The label every slice has, naming its Service.Scale to 5— Two new IPs appear after they become Ready.Delete pods— Old IPs go away, new ones come in.Not ready— A pod failing readiness stays in the slice with ready=false and gets no traffic.No restart for clients— The ClusterIP never changed during all of this.
Green output
$ bash watch.sh
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
wallet-8x7kq IPv4 5678 192.168.73.140,192.168.104.9,192.168.104.10 2m
wallet-8x7kq IPv4 5678 192.168.73.140,192.168.104.9,192.168.104.10 + 2 more... 2m
wallet-8x7kq IPv4 5678 192.168.73.140,192.168.104.9,192.168.104.10 + 1 more... 2m
$ kubectl patch deploy wallet -n net --type json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/nope"}]'
$ kubectl get endpointslice -n net -l kubernetes.io/service-name=wallet -o jsonpath='{range .items[0].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
192.168.73.150 ready=false
192.168.104.21 ready=false
Expected result. The slice changed within seconds as pods came and went. When readiness failed, the endpoints stayed listed but marked ready=false, so kube-proxy sent them nothing.
Example 3: Point a Service at a database outside the cluster with a selectorless Service
Goal. Point a Service at a database outside the cluster with a selectorless Service.
external-db.yaml
apiVersion: v1
kind: Service
metadata:
name: ledger-db
namespace: net
spec:
ports:
- name: postgres
port: 5432
targetPort: 5432
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: ledger-db-1
namespace: net
labels:
kubernetes.io/service-name: ledger-db
addressType: IPv4
ports:
- name: postgres
port: 5432
protocol: TCP
endpoints:
- addresses: ["172.18.0.50"]
conditions:
ready: true
What each line does
No selector— Kubernetes will not manage endpoints for this Service.Manual EndpointSlice— The kubernetes.io/service-name label links it to the Service.172.18.0.50— A database IP outside the cluster, for example an RDS private IP or an old VM.Port name— Must match between Service and slice.Why— Apps use ledger-db:5432 today, and you can move the DB into the cluster later without changing them.
Green output
$ kubectl apply -f external-db.yaml
$ kubectl get svc,endpointslice -n net ledger-db
NAME TYPE CLUSTER-IP PORT(S)
service/ledger-db ClusterIP 10.98.140.12 5432/TCP
NAME ADDRESSTYPE PORTS ENDPOINTS
endpointslice.discovery.k8s.io/ledger-db-1 IPv4 5432 172.18.0.50
$ docker run -d --name ledger-pg --network kind --ip 172.18.0.50 -e POSTGRES_PASSWORD=labonly postgres:16
$ kubectl exec -n net curl -- sh -c 'nc -zv ledger-db 5432' 2>&1 | tail -1
ledger-db (10.98.140.12:5432) open
Expected result. Pods reach a database that is not a pod through a normal Service name. The password labonly is only for this local lab.
Every command, step by step (kind lab with kubectl)
-
Deploy wallet and Service
kubectl apply -f wallet.yaml kubectl get deploy,svc -n netWhat it does: 3/3 ready and a ClusterIP.
deployment.apps/wallet 3/3 3 3 service/wallet ClusterIP 10.104.31.77 <none> 80/TCP -
Describe the Service
kubectl describe svc wallet -n netWhat it does: Shows selector, IP, TargetPort and Endpoints.
Selector: app=wallet IP: 10.104.31.77 Port: http 80/TCP TargetPort: http/TCP Endpoints: 192.168.73.140:5678,192.168.104.9:5678,192.168.104.10:5678 -
Read the EndpointSlice
kubectl get endpointslice -n net -l kubernetes.io/service-name=wallet -o yaml | grep -E 'ip|ready|nodeName' | head -6What it does: Each endpoint has IP, node and conditions.
- addresses: [192.168.73.140] conditions: {ready: true, serving: true, terminating: false} nodeName: ch62-worker -
Start a curl pod
kubectl run curl -n net --image=curlimages/curl:8.10.1 --restart=Never -- sleep 3600What it does: Used for all tests in this chapter.
pod/curl created -
Call by short name
kubectl exec -n net curl -- curl -s walletWhat it does: Same namespace.
wallet from wallet-7c9f8d6b5-h2k4p -
Call by full name
kubectl exec -n net curl -- curl -s wallet.net.svc.cluster.localWhat it does: Works from any namespace.
wallet from wallet-7c9f8d6b5-r7t5w -
Call the ClusterIP from your laptop
curl -m 3 10.104.31.77What it does: Fails: ClusterIP is internal only.
curl: (28) Connection timed out after 3001 milliseconds -
Scale and watch
kubectl scale deploy wallet -n net --replicas=5 kubectl get endpointslice -n net -l kubernetes.io/service-name=walletWhat it does: Five endpoints.
wallet-8x7kq IPv4 5678 192.168.73.140,192.168.104.9,192.168.104.10 + 2 more... -
Break readiness
kubectl patch deploy wallet -n net --type json -p '[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/nope"}]' kubectl get pods -n net -l app=walletWhat it does: The rollout stalls; new pods 0/1, old pods keep serving.
wallet-5d8f... 0/1 Running wallet-7c9f... 1/1 Running -
Undo
kubectl rollout undo deploy/wallet -n net kubectl rollout status deploy/wallet -n netWhat it does: Back to healthy.
deployment "wallet" successfully rolled out -
Create the selectorless Service
kubectl apply -f external-db.yaml kubectl get endpointslice -n net ledger-db-1What it does: Manual endpoints.
ledger-db-1 IPv4 5432 172.18.0.50 -
Clean up
docker rm -f ledger-pg kubectl delete -f external-db.yamlWhat it does: Keep wallet, curl and the cluster for 62.3.
service "ledger-db" deleted
When it is red 1
Endpoints: <none>
Cause. The selector matches no Ready pods.
Fix, step by step
- Compare kubectl get pods --show-labels with the selector.
- Check readiness probes.
When it is red 2
curl: (7) Failed to connect to wallet port 80: Connection refused
Cause. targetPort does not match a port the container listens on.
Fix, step by step
- Fix targetPort or the named port.
When it is red 3
curl: (6) Could not resolve host: wallet
Cause. Caller is in another namespace or CoreDNS is down.
Fix, step by step
- Use wallet.net or the full name.
- Check coredns pods.
When it is red 4
spec.ports[0].targetPort: Invalid value: "htp"
Cause. Typo in a named port.
Fix, step by step
- Use the exact containerPort name.
Practice tasks
Easy Task 1: Service
Why: Create.
What to do: Create the wallet Service.
- wallet.yaml.
Solution
kubectl get svc -n netEasy Task 2: Describe
Why: Read.
What to do: Describe it and list endpoints.
- describe svc.
Solution
kubectl describe svc wallet -n netEasy Task 3: Call
Why: Test.
What to do: Call it 6 times.
- curl pod.
Solution
kubectl exec -n net curl -- curl -s walletMedium Task 4: Slice
Why: Inspect.
What to do: Read the EndpointSlice YAML.
- conditions.
Solution
kubectl get endpointslice -n net -o yamlMedium Task 5: Scale
Why: Watch.
What to do: Scale to 5 and watch the slice.
- -w.
Solution
kubectl get endpointslice -n net -wMedium Task 6: Ready
Why: Break.
What to do: Break readiness and see ready=false.
- patch path.
Solution
kubectl get endpointslice -n net -o yamlMedium Task 7: Selector
Why: Break.
What to do: Change the selector to app: walet.
- Endpoints none.
Solution
kubectl describe svc wallet -n netMedium Task 8: External
Why: Map.
What to do: Create the selectorless Service.
- Manual slice.
Solution
kubectl get endpointslice -n net ledger-db-1Hard Task 9: Count
Why: Main task.
What to do: Count answers per pod over 20 calls.
- sort | uniq -c.
Solution
kubectl exec -n net curl -- sh -c 'for i in $(seq 20); do curl -s wallet; done | sort | uniq -c'Hard Task 10: Explain
Why: Interview.
What to do: Explain how a Service finds pods.
- Selector, slice, kube-proxy.
Solution
kubectl explain service.specHomework: try at home
For Dream11, deploy wallet with 4 replicas and a readiness probe, a ClusterIP Service on port 80 using a named targetPort, and a contest pod that calls wallet 20 times and counts answers per pod. Then make one pod unready and show it gets no traffic.
Recap
- A ClusterIP Service gives pods a stable internal IP and DNS name.
- The selector picks pods; EndpointSlices list the ready ones.
- targetPort can be a number or a named port.
- Selectorless Services point to things outside the cluster.
- Next: NodePort, to reach a Service from outside.
FAQ
Q. Does a Service do health checks?
No, it uses the pods' readiness probes.
Q. Is the ClusterIP a real interface?
No, it is virtual; kube-proxy rules catch traffic to it.
Q. Can I choose the ClusterIP?
Yes with spec.clusterIP, if it is free and in the service range.
Q. Why EndpointSlices instead of Endpoints?
One huge Endpoints object for 5000 pods was slow to update; slices split it into chunks of 100.
Q. Can a Service have several ports?
Yes, each must have a name.
Q. Does a Service cross namespaces?
Pods can call it from any namespace using name.namespace.
Interview questions
-
What problem does a Service solve?
Model answer: Pods have changing IPs; a Service gives a stable IP, name and load balancing.
-
What is an EndpointSlice?
Model answer: A list of ready pod addresses for a Service, managed by a controller.
-
port vs targetPort vs containerPort?
Model answer: Client port on the Service, the pod port it forwards to, and the port the container declares.
-
How do you debug a Service with no endpoints?
Model answer: Check selector against pod labels and pod readiness.
-
How do you expose an external DB as a Service?
Model answer: A selectorless Service with a manual EndpointSlice, or an ExternalName Service.
Quick quiz
Q1. Can you reach a ClusterIP from outside the cluster?
No.
Q2. Which object lists a Service's pod IPs?
EndpointSlice.
Q3. Do unready pods get traffic?
No.
Q4. What links a manual EndpointSlice to a Service?
The label kubernetes.io/service-name.
Q5. What is the default Service type?
ClusterIP.
Samajla ka?