Kubernetes Day 4: Services Explained — Discovery, Load Balancing & Networking

☸️ Day 4 of 30 — Kubernetes Services

Kubernetes Day 4: Everything About Services

Service Discovery, Load Balancing & Networking — the three problems every Kubernetes cluster must solve

📅 March 2026  ·  ☸️ Kubernetes Preparation Series  ·  Day 4 of 30

#Kubernetes #KubernetesServices #ServiceDiscovery #LoadBalancing #K8sNetworking #DevOps #CKAPrep #LearnK8s

1. The 3 Real Problems Kubernetes Services Solve

Before we learn what a Service is, let us understand why it exists. On Day 3, we learned that Deployments keep your Pods running via auto healing. But auto healing creates a new problem — and that problem splits into three real-world scenarios every team faces.

❌ Problem 1 — Dynamic IP addresses
How does a user or service find a Pod after it restarts?

Deployments implement auto healing using ReplicaSets. When a Pod goes down and a new one comes up, it gets a brand new IP address. The old IP is gone forever. So how does a customer, another service, or even your frontend know the new IP? They cannot hardcode it — it changes every single time.

❌ Problem 2 — Multiple replicas, one entry point
Your app has 3 replicas — each with a different IP. Which one does the user call?

If your application needs to handle many concurrent users, you run multiple replicas of a Pod. Each replica gets its own unique IP. But your users need one single address — just like everyone in the world accesses Google via google.com or 8.8.8.8, not by calling individual Google servers directly. Who manages the one-to-many routing?

❌ Problem 3 — External access
How do people outside the cluster reach your application?

Your application runs inside Pods, inside a cluster. By default, that is a private network. People from other teams in your organisation — or customers on the internet — do not have access to the cluster's internal network. How do you safely expose your application to the outside world?

✅ One solution solves all three
Kubernetes Service

A Kubernetes Service is a stable network abstraction that sits in front of your Pods and solves all three problems at once — dynamic IP changes, load balancing across replicas, and controlled external exposure. It is one of the most important objects you will work with every day.


2. What is a Kubernetes Service?

A Kubernetes Service is a stable, long-lived network endpoint that provides a consistent way to access a group of Pods. While Pods are ephemeral (they come and go with changing IPs), a Service has a fixed IP address and DNS name that never changes — regardless of how many times the Pods behind it are replaced.

Think of it this way:

🏦 Real-world analogy: A bank has many teller windows — each is a Pod. The bank's front desk is the Service. Customers always walk to the front desk (one address). The front desk routes them to an available teller (load balancing). It does not matter which teller is on duty today or which desk they sit at — the front desk address never changes.

A Service does three things simultaneously:

  • Service Discovery — gives Pods a stable DNS name and IP so other services can always find them, even after restarts
  • Load Balancing — distributes incoming traffic evenly across all healthy Pods that match its selector
  • Exposure control — determines who can access the Pods: only within the cluster, within your organisation's network, or the entire internet

3. How Services Work — Labels, Selectors & kube-proxy

Understanding how a Service actually connects to Pods is important — especially for debugging. There are three moving parts.

Part 1 — Label selectors (how a Service finds its Pods)

A Service does not know about specific Pod names or IPs. Instead, it uses a label selector — it asks: "Give me all Pods in this cluster that have the label app: nginx." Whatever Pods match that selector at any given moment are the targets. When an old Pod dies and a new one comes up (with the same labels), the Service automatically starts routing to the new Pod. This is how the dynamic IP problem is solved — the Service tracks labels, not IPs.

🔍 Service discovery via label selector
Client / User
→
Service
selector: app=nginx
ClusterIP: 10.96.0.1
→
Pod 1
app=nginx
IP: 10.1.0.4
Pod 2
app=nginx
IP: 10.1.0.7
Pod 3
app=nginx
IP: 10.1.0.9
Pod IPs change on restart — Service IP stays the same forever ✅

Part 2 — Endpoints (the live list of Pod IPs)

When you create a Service, Kubernetes automatically creates an Endpoints object with the same name. This object holds the live list of IP:port pairs for all Pods currently matching the selector. As Pods come and go, the Endpoints object is updated automatically by the Endpoints Controller.

# See the live Pod IPs behind a Service kubectl get endpoints nginx-service NAME ENDPOINTS AGE nginx-service 10.1.0.4:80,10.1.0.7:80,10.1.0.9:80 5m # Delete a Pod — watch the endpoint list update automatically kubectl delete pod nginx-deployment-xxx kubectl get endpoints nginx-service NAME ENDPOINTS AGE nginx-service 10.1.0.7:80,10.1.0.9:80,10.1.0.11:80 5m ↑ new Pod IP

Part 3 — kube-proxy (the routing engine)

kube-proxy runs on every node in the cluster. It watches the API Server for Service and Endpoints changes and maintains iptables rules on each node. These rules intercept traffic destined for a Service's ClusterIP and redirect it to one of the actual Pod IPs — performing the load balancing at the network level. No traffic ever actually goes to the ClusterIP itself; it is a virtual IP that only exists in routing rules.

✅ Key mental model: The Service IP is virtual — it is a routing rule, not a real network interface. kube-proxy rewrites packets on the fly so traffic sent to 10.96.0.1:80 (Service) arrives at 10.1.0.7:80 (a real Pod).

4. ClusterIP — Solving Problem 1 & 2 (Internal Discovery + Load Balancing)

ClusterIP is the default Service type. It exposes the Service on a virtual IP that is reachable only from inside the cluster. Every other Pod in the cluster can reach your app via this stable IP or its DNS name — regardless of how many times the Pods behind it restart and change IPs.

This directly solves Problem 1 (dynamic IPs) and Problem 2 (one entry point for multiple replicas).

How DNS works with ClusterIP

Kubernetes runs a DNS server (CoreDNS) inside the cluster. Every Service automatically gets a DNS name in the format:

<service-name>.<namespace>.svc.cluster.local # Example — from any Pod inside the cluster: curl http://nginx-service.default.svc.cluster.local # Short form also works within the same namespace: curl http://nginx-service # This is exactly like accessing google.com instead of a raw IP

ClusterIP Service YAML

 clusterip-service.yaml
apiVersion: v1 kind: Service metadata: name: nginx-service spec: type: ClusterIP # Default — internal only selector: app: nginx # Route to all Pods with this label ports: - protocol: TCP port: 80 # Port the Service listens on targetPort: 80 # Port the container listens on
⚠️ ClusterIP is NOT accessible from outside the cluster. You cannot open a browser on your laptop and reach a ClusterIP service — it only exists inside the cluster's private network. For external access, you need NodePort or LoadBalancer.

5. NodePort — Solving Problem 3 (Access from Inside Your Organisation)

NodePort extends ClusterIP by also opening a specific port on every node in the cluster. Traffic sent to any node's IP on that port gets forwarded to the Service and then to a Pod. This makes your app accessible from outside the cluster — as long as you can reach any node's IP address.

This solves Problem 3 for internal teams — a developer in another team who can reach the cluster's node IPs (but does not have kubectl access) can now access your application.

How NodePort works

🌐 NodePort — traffic flow
External user
browser / curl
→
Node IP:30080
any cluster node
→
Service
ClusterIP:80
→
Pod 1 :80
Pod 2 :80
NodePort range: 30000–32767 — Kubernetes assigns automatically or you specify

NodePort Service YAML

 nodeport-service.yaml
apiVersion: v1 kind: Service metadata: name: nginx-nodeport spec: type: NodePort selector: app: nginx ports: - protocol: TCP port: 80 # ClusterIP port (internal) targetPort: 80 # Container port nodePort: 30080 # External port on every node (30000-32767) # Omit this line to let K8s assign automatically
✅ On Minikube: Run minikube service nginx-nodeport --url to get the exact URL to open in your browser. Minikube's node IP is not a regular IP — this command gives you the correct access URL.
⚠️ NodePort is not for production internet traffic. Exposing raw node IPs and high-numbered ports to the internet is a security risk. It is perfect for internal team access and local development. For real internet-facing production traffic, use LoadBalancer.

6. LoadBalancer — Solving Problem 3 for Production (Internet Access)

LoadBalancer is the standard way to expose an application to the internet in production. When you create a Service of type LoadBalancer on a cloud-managed Kubernetes cluster (EKS, GKE, AKS), Kubernetes automatically talks to the cloud provider's API and provisions a real cloud load balancer — an AWS ALB, a GCP Load Balancer, or an Azure Load Balancer — with a public IP address.

External users access your application through that public IP or domain, and the cloud load balancer forwards traffic into your cluster, to the Service, and then to your Pods.

How LoadBalancer works

🌍 LoadBalancer — production traffic flow
Internet user
yourdomain.com
→
Cloud LB
AWS ALB / GCP LB
Public IP assigned
→
Service
LoadBalancer type
→
Pod 1 :80
Pod 2 :80
Pod 3 :80
Cloud provider provisions the Load Balancer automatically — no manual setup

LoadBalancer Service YAML

 loadbalancer-service.yaml
apiVersion: v1 kind: Service metadata: name: nginx-loadbalancer spec: type: LoadBalancer # Cloud provider provisions LB automatically selector: app: nginx ports: - protocol: TCP port: 80 # Port users hit targetPort: 80 # Port your container listens on
# After applying, watch for the external IP to be assigned (takes 1-2 mins) kubectl get service nginx-loadbalancer -w NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-loadbalancer LoadBalancer 10.96.45.12 <pending> 80:31234/TCP 10s nginx-loadbalancer LoadBalancer 10.96.45.12 34.120.88.201 80:31234/TCP 45s ↑ Public IP assigned by cloud! # Now anyone on the internet can access your app: curl http://34.120.88.201
💡 On Minikube (local cluster): LoadBalancer services will stay in <pending> state because there is no cloud provider to assign an IP. Run minikube tunnel in a separate terminal — this simulates a cloud load balancer and assigns a local IP.

7. All 4 Service Types — When to Use Each

ClusterIP

Internal only

Exposes the Service on a virtual IP reachable only from within the cluster. Default type — used for all inter-service communication. Your frontend Pod calls your backend Pod using its ClusterIP DNS name.

✅ Use for: backend services, databases, APIs within the cluster
NodePort

Organisation access

Opens a port (30000–32767) on every node. Anyone who can reach a node's IP can access your app. Automatically creates a ClusterIP too. Best for dev/test environments and internal org access.

✅ Use for: internal teams, dev/test, Minikube demos
LoadBalancer

Internet production access

Provisions a real cloud load balancer with a public IP. The production standard for customer-facing services. Creates a ClusterIP and NodePort automatically. Costs money — the cloud provider charges for the LB.

✅ Use for: production internet-facing applications
ExternalName

DNS alias

Maps a Kubernetes Service to an external DNS name (like an RDS database or a third-party API). When a Pod queries this Service, it gets the external DNS name back. No proxying — pure DNS aliasing.

✅ Use for: external databases, third-party APIs, migration scenarios

Comparison table

FeatureClusterIPNodePortLoadBalancerExternalName
Accessible from inside cluster✓ Yes✓ Yes✓ Yes✓ Yes
Accessible from org network✗ No✓ Yes✓ Yes✗ No
Accessible from internet✗ No⚠️ Risky✓ Yes✗ No
Load balancing✓ Yes✓ Yes✓ Yes✗ No
Stable DNS name✓ Yes✓ Yes✓ Yes✓ Yes
Cloud LB provisioned✗ No✗ No✓ Yes✗ No
Extra costFreeFreeCloud LB costFree
Best forInternal servicesDev / testingProductionExternal DNS mapping

8. Full Hands-on Demo

Let us deploy an Nginx application, create all three main Service types, and see exactly how each one works. Make sure minikube start is running before you begin.

Step 1 — Create the Deployment

# Apply the Deployment from Day 3 (or create it fresh) kubectl create deployment nginx-demo --image=nginx:1.25 --replicas=3 kubectl get pods -l app=nginx-demo NAME READY STATUS RESTARTS AGE nginx-demo-5f8d9b7c6-2xkpq 1/1 Running 0 15s nginx-demo-5f8d9b7c6-9fvlm 1/1 Running 0 15s nginx-demo-5f8d9b7c6-rnbtp 1/1 Running 0 15s

Step 2 — Create a ClusterIP Service and test service discovery

# Expose the Deployment as a ClusterIP Service kubectl expose deployment nginx-demo --port=80 --target-port=80 --name=nginx-clusterip # Inspect the Service kubectl get service nginx-clusterip NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-clusterip ClusterIP 10.96.142.33 <none> 80/TCP 10s # Test from inside the cluster — run a temporary Pod and curl the Service kubectl run test-pod --image=busybox --rm -it --restart=Never -- sh # Inside the test Pod shell: wget -qO- http://nginx-clusterip # by Service name (DNS) wget -qO- http://10.96.142.33 # by ClusterIP directly wget -qO- http://nginx-clusterip.default.svc.cluster.local # full DNS exit

Step 3 — Create a NodePort Service and access from outside

# Create NodePort Service kubectl expose deployment nginx-demo --port=80 --target-port=80 \ --type=NodePort --name=nginx-nodeport kubectl get service nginx-nodeport NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-nodeport NodePort 10.96.88.71 <none> 80:31456/TCP 5s ↑ NodePort assigned # On Minikube — get the exact URL to open in browser minikube service nginx-nodeport --url http://192.168.49.2:31456 # Open it in your browser or curl it curl http://192.168.49.2:31456 <!DOCTYPE html> ... Welcome to nginx! ... ✅

Step 4 — Prove that Service Discovery works across Pod restarts

# Note the current Pod IPs kubectl get pods -o wide NAME READY STATUS IP nginx-demo-5f8d9b7c6-2xkpq 1/1 Running 10.1.0.4 nginx-demo-5f8d9b7c6-9fvlm 1/1 Running 10.1.0.7 nginx-demo-5f8d9b7c6-rnbtp 1/1 Running 10.1.0.9 # Delete a Pod — Deployment auto-heals with a new Pod and new IP kubectl delete pod nginx-demo-5f8d9b7c6-2xkpq kubectl get pods -o wide NAME READY STATUS IP nginx-demo-5f8d9b7c6-9fvlm 1/1 Running 10.1.0.7 nginx-demo-5f8d9b7c6-rnbtp 1/1 Running 10.1.0.9 nginx-demo-5f8d9b7c6-hj9kp 1/1 Running 10.1.0.11 ← new IP! # Service still works — it picked up the new Pod via label selector kubectl get endpoints nginx-clusterip NAME ENDPOINTS nginx-clusterip 10.1.0.7:80,10.1.0.9:80,10.1.0.11:80 ↑ new Pod included automatically ✅ # The Service DNS name still resolves and routes correctly kubectl run test-pod --image=busybox --rm -it --restart=Never -- wget -qO- http://nginx-clusterip Welcome to nginx! ✅ (Service never went down)

Step 5 — Clean up

kubectl delete service nginx-clusterip nginx-nodeport kubectl delete deployment nginx-demo service "nginx-clusterip" deleted service "nginx-nodeport" deleted deployment.apps "nginx-demo" deleted
✅ The most important thing the demo proves: When a Pod died and came back with a new IP, the Service continued working without any configuration change. This is service discovery in action — the Service tracked the new Pod via labels, not IPs.

🎯 Putting It All Together — 3 Problems, 1 Solution

ProblemRoot causeSolved byHow
Problem 1
Dynamic Pod IPs
Auto healing creates Pods with new IPs every time Service (any type) Service tracks Pods by labels, not IPs. New Pod = same label = automatically included in routing
Problem 2
Multiple replicas, one address
3 Pods = 3 IPs, users need one entry point Service (ClusterIP) Single stable ClusterIP + DNS name. kube-proxy load balances across all matching Pods
Problem 3
External access
Cluster network is private by default NodePort (org) or LoadBalancer (internet) NodePort opens a port on every node. LoadBalancer provisions a public cloud LB with a real IP

📌 Day 4 Recap — What You Learned

  • The 3 problems: Dynamic Pod IPs after restarts, multiple replicas needing one address, and external access to a private cluster — all three are solved by Kubernetes Services.
  • What a Service is: A stable, long-lived network endpoint with a fixed IP and DNS name that sits in front of your Pods and handles discovery, load balancing, and access control.
  • How it works: Label selectors identify Pods dynamically. The Endpoints object holds live Pod IPs. kube-proxy maintains routing rules on every node to redirect traffic.
  • ClusterIP: Internal only. Default type. Use for all service-to-service communication inside the cluster. Gets a stable DNS name like my-service.default.svc.cluster.local.
  • NodePort: Opens a port on every node. Good for internal org access, dev/test, and Minikube demos. Not recommended for direct internet exposure.
  • LoadBalancer: Provisions a real cloud load balancer with a public IP. The production standard for internet-facing applications on cloud clusters.
  • Demo: You created a Deployment, exposed it via ClusterIP and NodePort, proved service discovery works across Pod restarts, and verified the Endpoints object updates automatically.
🚀 Day 5 preview: Services expose your app — but what if you need path-based routing, TLS termination, or one entry point for many services? That is what Kubernetes Ingress solves. We will also look at ConfigMaps and Secrets to manage application configuration the Kubernetes way.

Comments

Popular posts from this blog

Linux Commands Every DevOps Engineer Must Know (2026 Complete Guide)

Docker Day 1: Containers Explained — VM vs Docker, Lifecycle & Buildah

Kubernetes Day 7: ConfigMaps & Secrets — Managing Config the Right Way