Agentic AI with Java: Live Cohort
KubernetesPods & Services

ClusterIP Service Explained

Create a ClusterIP Service for the webapp pod, connect it with labels and selectors, map port to targetPort, and prove it works from inside the cluster.

Most Services in a real cluster are never seen by users. The orders service calls the payments service. The Spring Boot app calls MySQL. The frontend calls an internal API. All of that traffic stays inside the cluster, and the Service type built for it is ClusterIP.

What ClusterIP gives you

  • It is the default. If you create a Service and leave out type, you get a ClusterIP Service.
  • A stable virtual IP from the cluster's service range (on EKS, usually 10.100.0.0/16) plus a DNS name. Pods come and go, the Service address stays.
  • Internal only. Pods and nodes in the cluster can reach it. The internet cannot.
  • Load balancing across every ready pod that matches its selector.
other pod  ->  webapp-svc (10.100.113.192:80)  ->  webapp pod (192.168.x.x:8080)

How the Service finds its pods: labels and selectors

A Service does not list pod names or pod IPs. It uses a selector. Any pod whose labels match the selector becomes a backend of the Service, now or in the future.

Our pod manifest already has a label:

k8s-pod.yml (excerpt)
metadata:
  name: webapp
  labels:
    app: telusko-webapp

The Service will select app: telusko-webapp. If these two strings differ by even one character, the Service ends up with no endpoints and every request fails. We look at labels in depth in Labels and Selectors.

Step 1: Write the Service manifest

Make sure the webapp pod is running (kubectl apply -f k8s-pod.yml), then:

vi clusterIp-svc.yml
clusterIp-svc.yml
apiVersion: v1
kind: Service
metadata:
  name: webapp-svc
spec:
  selector:
    app: telusko-webapp
  ports:
    - port: 80
      targetPort: 8080
FieldMeaning
apiVersion: v1, kind: ServiceServices are in the core API group, like Pods
metadata.name: webapp-svcThe Service name. It also becomes its DNS name
spec.selectorSend traffic to pods with label app=telusko-webapp
ports[].port: 80The port the Service listens on. Clients call webapp-svc:80
ports[].targetPort: 8080The port on the pod to forward to. Must match the port the app listens on
typeNot set, so it defaults to ClusterIP

port and targetPort do not have to be equal. Here, callers use the familiar port 80 while the Spring Boot app keeps its default 8080. If you leave targetPort out, it defaults to the same value as port.

Before applying, double-check the label and selector match:

cat k8s-pod.yml clusterIp-svc.yml | grep telusko-webapp

Step 2: Create and inspect it

kubectl apply -f clusterIp-svc.yml
service/webapp-svc created
kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1       <none>        443/TCP   74m
webapp-svc   ClusterIP   10.100.113.192   <none>        80/TCP    6s

EXTERNAL-IP is <none>: there is no way in from outside. Now look closer:

kubectl describe svc webapp-svc
Name:                     webapp-svc
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=telusko-webapp
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.100.113.192
IPs:                      10.100.113.192
Port:                     <unset>  80/TCP
TargetPort:               8080/TCP
Endpoints:                192.168.5.59:8080
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>

The most important line is Endpoints. It is the list of pod IP and port pairs the Service is currently forwarding to. 192.168.5.59 is our pod's IP. If Endpoints is empty, the selector matches no ready pod, and that is the first thing to check whenever a Service "does not work".

Labels: <none> refers to labels on the Service itself, which we did not set. The Selector is what matters for routing.

Endpoints are stored as EndpointSlice objects in current Kubernetes. You can see them directly:

kubectl get endpointslices -l kubernetes.io/service-name=webapp-svc

kubectl get all shows the pod and both Services together:

kubectl get all

Step 3: Prove it works from inside the cluster

From the EKS home server you cannot open 10.100.113.192, because that address only exists inside the cluster network. So start a throwaway pod inside the cluster and call the Service from there:

kubectl run curl-test --rm -it --image=curlimages/curl --restart=Never -- curl -s http://webapp-svc
PartMeaning
kubectl run curl-testCreate a pod named curl-test
--image=curlimages/curlA tiny image that contains curl
--rm -itAttach to it, and delete the pod when the command finishes
--restart=NeverCreate a plain pod, not something that restarts
-- curl -s http://webapp-svcThe command to run inside the pod

You should see the HTML of the Spring Boot application. Notice we used the name webapp-svc, not the IP. The cluster DNS service (CoreDNS) resolves it. The full DNS name is:

webapp-svc.default.svc.cluster.local
<service>.<namespace>.svc.cluster.local

From a pod in the same namespace, the short name webapp-svc is enough. From another namespace use webapp-svc.default.

This is exactly how your applications should call each other: by Service name, never by pod IP.

When the Service has several pods

Right now there is one pod behind the Service. Once we run several copies with a ReplicaSet or Deployment, each new pod with the label app: telusko-webapp is added to Endpoints automatically, and kube-proxy spreads connections across all of them. Pods that are not ready are left out, so traffic only goes to healthy pods.

Common mistakes

Clean up

Keep the pod and Service if you continue straight to the NodePort lesson. Otherwise:

kubectl delete -f clusterIp-svc.yml
kubectl delete -f k8s-pod.yml

If you are stopping for the day, delete the cluster too: eksctl delete cluster --name telusko-learning --region ap-south-1 --wait.

Try It Yourself

  1. Create the pod and the webapp-svc Service, and confirm Endpoints shows the pod IP.
  2. Change the selector to app: wrong-label, apply, and check Endpoints again. Then call the Service with the curl pod and read the error. Fix it.
  3. Delete the pod and recreate it. Compare the pod IP before and after, and confirm the Service ClusterIP did not change.
  4. Call the Service using its full DNS name webapp-svc.default.svc.cluster.local.

Official references


Written By: Shramik Masti

How is this guide?

Last updated on