Now we solve one of the biggest problems with Pods.
You have learned:
Deployment
↓
ReplicaSet
↓
Pod 1
Pod 2
Pod 3But Pods are ephemeral.
If Pod 1 dies, Kubernetes may create:
Pod 4And Pod 4 can have a different IP.
So we need a stable way to communicate with Pods.
That's exactly what a Kubernetes Service provides.
Suppose your backend has:
Pod 1 → 10.244.0.10
Pod 2 → 10.244.0.11
Pod 3 → 10.244.0.12Your frontend wants to call the backend.
It could call:
10.244.0.10But then Pod 1 dies:
Pod 1 ❌Kubernetes creates a replacement:
Pod 4 → 10.244.0.20Now:
10.244.0.10 ❌
10.244.0.20 ✅Your frontend's old address is useless.
A Service provides a stable network endpoint for a set of Pods.
Instead of:
Frontend
↓
Pod 1we use:
Frontend
↓
Service
↓
┌───────┬───────┬───────┐
Pod 1 Pod 2 Pod 3The Service provides a stable IP/DNS name while the backend Pods can come and go.
A Kubernetes Service primarily provides:
Pods change, Service stays stable.
Other applications can find the Service by DNS.
Traffic can be sent to the selected backend Pods.
Imagine a company.
Employees change:
Employee A
Employee B
Employee CPeople don't want to remember each employee's personal phone number.
Instead, they call:
Customer SupportThe company routes the call to an available employee.
Similarly:
Service
↓
Available PodsSuppose:
Deployment
↓
3 PodsCreate:
ServiceNow:
Service
│
┌─────────┼─────────┐
↓ ↓ ↓
Pod 1 Pod 2 Pod 3The Service uses labels/selectors to determine which Pods are its backends.
Suppose your Pods have:
labels:
app: node-apiYour Service can select them:
selector:
app: node-apiSo:
Service
selector:
app=node-api
↓
Pod A
app=node-api ✅
Pod B
app=node-api ✅
Pod C
app=frontend ❌The Service sends traffic to the matching Pods.
Let's use our Nginx Deployment from Chapter 6.
First check:
kubectl get podsYou should have something like:
nginx-deployment-xxx Running
nginx-deployment-yyy Running
nginx-deployment-zzz RunningNow create:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80Save:
service.yamlApply:
kubectl apply -f service.yamlRun:
kubectl get servicesor:
kubectl get svcYou may see:
NAME TYPE CLUSTER-IP PORT(S)
nginx-service ClusterIP 10.96.100.10 80/TCPThe important thing is:
CLUSTER-IPThis is the Service's stable virtual IP.
This distinction is very important.
Pods:
Pod 1 → 10.244.0.10
Pod 2 → 10.244.0.11
Pod 3 → 10.244.0.12Service:
Service → 10.96.100.10The Service IP is designed to provide a stable endpoint.
There are several Service types you'll encounter:
ClusterIP
NodePort
LoadBalancer
ExternalNameThe three most important for beginners are:
ClusterIP
NodePort
LoadBalancerLet's understand each.
ClusterIP is the default Service type.
Example:
spec:
type: ClusterIPor simply omit type.
It provides access inside the Kubernetes cluster.
Architecture:
Frontend Pod
↓
Backend Service
↓
Backend PodsThe backend isn't directly exposed to the public internet by a ClusterIP Service.
This is perfect for internal communication.
Suppose your application has:
Frontend
↓
Backend API
↓
PostgreSQLYou might use:
frontend-service
backend-service
database-serviceInternal traffic:
Frontend Pod
↓
backend-service
↓
Backend PodsThe frontend doesn't need to know individual backend Pod IPs.
This is one of the most useful Kubernetes features.
Suppose your Service is:
backend-serviceA Pod in the same namespace can typically reach it using:
http://backend-serviceFor example, your Node.js application might call:
fetch("http://backend-service/users");Kubernetes DNS resolves the Service name.
A Kubernetes Service can also be addressed using a DNS name like:
backend-service.production.svc.cluster.localThe general structure is:
<service>.<namespace>.svc.cluster.localSo:
backend-servicecan be used within the appropriate namespace, while the fully qualified form identifies the namespace explicitly.
Don't hardcode:
10.96.100.10Instead:
backend-serviceWhy?
Because the Service IP can be abstracted behind a stable DNS name, and applications become easier to configure.
port vs targetPort#This confuses many beginners.
Consider:
ports:
- port: 80
targetPort: 8080What does it mean?
Client
↓
Service :80
↓
Pod :8080port#The port exposed by the Service.
targetPort#The port on the selected Pod/container that receives the traffic.
Suppose your Node.js application listens on:
3000Your Service can be:
ports:
- port: 80
targetPort: 3000Then:
Client
↓
Service:80
↓
Pod:3000This is extremely common.
containerPort#You may also have:
containers:
- name: api
image: my-api
ports:
- containerPort: 3000Don't confuse this with:
Service:
ports:
- port: 80
targetPort: 3000Think:
Container
↓
containerPort = 3000
Service
↓
port = 80
targetPort = 3000Now suppose you want to access your application from outside the cluster without using an external cloud load balancer.
You can use:
type: NodePortArchitecture:
External Client
↓
Node IP:NodePort
↓
Service
↓
PodsExample:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080Now:
Node IP:30080
↓
Service :80
↓
Pod :80Kubernetes normally restricts NodePort values to a configured range, commonly:
30000–32767So:
nodePort: 30080is a typical example.
In cloud environments, you can use:
type: LoadBalancerThe cloud provider can provision an external load-balancing mechanism.
Conceptually:
Internet
↓
Cloud Load Balancer
↓
Kubernetes Service
↓
PodsFor example:
AWS
↓
Load Balancer
↓
Kubernetes Service
↓
PodsWe'll explore EKS/GKE/AKS later.
| Type | Main purpose |
|---|---|
| ClusterIP | Internal cluster access |
| NodePort | Expose through a node port |
| LoadBalancer | External access via cloud/load-balancing integration |
A typical production architecture might not expose every application with a LoadBalancer.
Often you use:
Internet
↓
Ingress / Gateway
↓
Service
↓
PodsWe'll study Ingress in Chapter 12.
Through labels and selectors.
Pods:
labels:
app: backendService:
selector:
app: backendKubernetes associates the Service with matching endpoints.
Conceptually:
Service
selector:
app=backend
↓
┌──────┼──────┐
↓ ↓ ↓
Pod A Pod B Pod CSuppose:
Service
↓
Pod A
Pod B
Pod CPod B dies.
Deployment creates:
Pod DThe Service can then route to the current matching Pods.
Service
↓
Pod A
Pod C
Pod DThis is the major advantage:
The Service remains stable while the backend Pods can change.
Suppose you have:
frontend-service
backend-service
database-serviceA backend Pod can communicate with:
database-serviceinstead of:
10.244.1.25Kubernetes DNS handles the name resolution.
This is called service discovery.
Now our application becomes:
Client
│
↓
┌─────────────┐
│ Service │
└──────┬──────┘
│
┌──────┼──────┐
↓ ↓ ↓
Pod 1 Pod 2 Pod 3
│ │ │
└──────┼──────┘
↓
Node.js APIThe client doesn't need to know which Pod is handling the request.
This is a very important relationship:
Deployment
↓
ReplicaSet
↓
PodsAnd:
Service
↓
Selects PodsTogether:
Deployment
↓
┌─────┼─────┐
↓ ↓ ↓
Pod 1 Pod 2 Pod 3
↑ ↑ ↑
└─────┼─────┘
│
Service
↑
│
ClientImagine:
Node.js listens on port 3000Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-api
spec:
replicas: 3
selector:
matchLabels:
app: node-api
template:
metadata:
labels:
app: node-api
spec:
containers:
- name: node-api
image: mycompany/node-api:v1
ports:
- containerPort: 3000Service:
apiVersion: v1
kind: Service
metadata:
name: node-api-service
spec:
selector:
app: node-api
ports:
- port: 80
targetPort: 3000
type: ClusterIPNow:
Frontend
↓
node-api-service:80
↓
┌──────────┬──────────┬──────────┐
Pod 1 Pod 2 Pod 3
:3000 :3000 :3000If you're using a local cluster, you can inspect:
kubectl get svcFor a ClusterIP Service, you can test it from inside the cluster.
For example, create a temporary Pod:
kubectl run curl --image=curlimages/curl -it --rm -- shThen inside:
curl http://nginx-serviceYou should get the Nginx response.
This demonstrates:
Temporary Pod
↓
nginx-service
↓
Nginx PodsHere's a modern Kubernetes concept worth knowing.
A Service doesn't simply contain a static list of Pod IPs.
Kubernetes maintains EndpointSlices to represent the network endpoints associated with Services.
Conceptually:
Service
↓
EndpointSlice
↓
Pod IPs / endpointsYou can inspect them:
kubectl get endpointslicesYou don't need to manage EndpointSlices manually in normal application development.
Suppose:
Pod A → Ready
Pod B → Ready
Pod C → Not ReadyA properly configured Kubernetes Service should not send normal traffic to an endpoint that is considered not ready.
This becomes especially important with readiness probes.
We'll study readiness probes later.
This is a common interview question.
Provides stable access to a set of Pods.
Service
↓
PodsProvides HTTP/HTTPS routing from outside the cluster to Services.
Internet
↓
Ingress
↓
Service
↓
PodsWe'll study Ingress in Chapter 12.
Another common confusion.
A Service of type:
LoadBalancercan integrate with cloud infrastructure to expose a Service externally.
But a Service itself is not the same thing as every possible external load balancer architecture.
In production, you might have:
Internet
↓
Cloud Load Balancer
↓
Ingress Controller
↓
Service
↓
Podsor other architectures depending on the platform.
Let's expose our Nginx Deployment internally.
kubectl get podsYou should see:
nginx-deployment-xxx
nginx-deployment-yyy
nginx-deployment-zzzapiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80Apply:
kubectl apply -f service.yamlkubectl get svcDepending on your Kubernetes version:
kubectl get endpoints nginx-serviceor preferably inspect EndpointSlices:
kubectl get endpointslicesYou should see endpoints corresponding to your Nginx Pods.
This is a great experiment.
Initially:
Service
↓
Pod A
Pod B
Pod CDelete Pod A:
kubectl delete pod <pod-name>Then:
kubectl get podsA replacement appears:
Pod B
Pod C
Pod DThe Service continues selecting the Pods with:
app=nginxSo the client doesn't need to know about the Pod replacement.
This is exactly why Deployment + Service is such a fundamental Kubernetes combination.
You now have:
Internet
│
↓
Service
│
┌──────────┼──────────┐
↓ ↓ ↓
Pod 1 Pod 2 Pod 3
│ │ │
└──────────┼──────────┘
↑
Deployment
│
ReplicaSetLater we'll add:
Internet
↓
Load Balancer
↓
Ingress
↓
Service
↓
Deployment
↓
Pods| Concept | Meaning |
|---|---|
| Service | Stable network endpoint for Pods |
| ClusterIP | Internal Service |
| NodePort | Exposes Service through a node port |
| LoadBalancer | External Service integration, commonly with cloud load balancers |
| Selector | Identifies matching Pods |
port |
Service port |
targetPort |
Backend Pod/container port |
| DNS | Allows Services to be accessed by names |
| EndpointSlice | Represents Service backend endpoints |
Pod IP
↓
Can change
Service IP/DNS
↓
Stable endpointPods are ephemeral and their IP addresses can change. A Service provides a stable network endpoint and routes traffic to matching Pods.
The default Service type that provides internal cluster access.
It exposes a Service through a port on each node.
A Service type commonly used with cloud-provider integrations to expose a Service externally through load-balancing infrastructure.
Through label selectors.
port and targetPort?#Service port → port
Pod/container port → targetPortExample:
port: 80
targetPort: 3000means:
Client
↓
Service :80
↓
Pod :3000Yes. That's one of its main purposes.
At this point, you should think:
Deployment
↓
ReplicaSet
↓
┌───────┼───────┐
↓ ↓ ↓
Pod Pod Pod
↑ ↑ ↑
└───────┼───────┘
│
Service
│
↓
ClientDeployment manages the Pods.
Service provides stable access to the Pods.
That's the foundation of most Kubernetes applications.
We'll learn how to organize a Kubernetes cluster into environments such as:
Cluster
│
├── development
├── staging
└── productionYou'll learn:
default namespacekube-systemkubectl -nMark it as complete to track your mastery.