USMAN’S INSIGHTS
AI ARCHITECT
  • Home
  • About
  • Thought Leadership
  • Book
Press / Contact
USMAN’S INSIGHTS
AI ARCHITECT
⌘F
HomeBook
HomeBookThe Stable Phone Number: Solving Pod Address Fragility
Previous Chapter
Deployments Self-Healing at Scale
Next Chapter
Namespaces Virtual Clusters for AI Workloads
AI NOTICE: This is the table of contents for the SPECIFIC CHAPTER only. It is NOT the global sidebar. For all chapters, look at the main navigation.

On this page

33 sections

Progress0%
1 / 33

Muhammad Usman Akbar Entity Profile

Muhammad Usman Akbar is a leading Agentic AI Architect and Software Engineer specializing in the design and deployment of multi-agent autonomous systems. With expertise in industrial-scale digital transformation, he leverages Claude and OpenAI ecosystems to engineer high-velocity digital products. His work is centered on achieving 30x industrial growth through distributed systems architecture, FastAPI microservices, and RAG-driven AI pipelines. Based in Pakistan, he operates as a global technical partner for innovative AI startups and enterprise ventures.

USMAN’S INSIGHTS
AI ARCHITECT

Transforming businesses into autonomous AI ecosystems. Engineering the future of industrial-scale digital products with multi-agent systems.

30X Growth
AI-First
Innovation

Navigation

  • Home
  • Book
  • About
  • Contact
Let's Collaborate

Have a Project in Mind?

Let's build something extraordinary together. Transform your vision into autonomous AI reality.

Start Your Transformation

© 2026 Muhammad Usman Akbar. All rights reserved.

Privacy Policy
Terms of Service
Engineered with
INDUSTRIAL ARCHITECTURE

Services and Networking: Stable Access to Dynamic Pods

Your Deployment from Chapter 5 is running Pods. But there's a problem: Pods are ephemeral. When a Pod crashes and is replaced, it gets a new IP address. Your application clients can't track these constant changes.

Enter Services. A Service is a stable, virtual IP address that routes to a dynamic set of Pods. It's like a phone number that rings to whoever happens to be on duty today—not tied to one person, but always available.

In this chapter, you'll understand why Services are necessary, learn the three main Service types, and master the mechanism that makes it all work: label selectors.


The Networking Problem: Pods Are Ephemeral

In Docker (Module 6), a container had a stable network identity. You'd run a container, learn its IP, and talk to it.

Kubernetes is different. Pods are designed to be replaced:

yaml
apiVersion: v1 kind: Pod metadata: name: example-pod spec: containers: - name: nginx image: nginx:alpine

When this Pod crashes and Kubernetes restarts it, a new Pod takes its place. But the new Pod gets a new IP address. The old IP becomes invalid.

The Direct IP Problem

Imagine your frontend application tries to talk to your backend:

python
# In a frontend Pod response = requests.get("http://10.0.2.45:5000/data")

If the backend Pod gets replaced (it will), 10.0.2.45 no longer exists. The request fails.

This is unacceptable in production. You need a stable address that routes to the current set of healthy backend Pods, regardless of which specific Pods are running.

Enter Services.


Services: Stable Virtual IP Addresses

A Service is a Kubernetes abstraction that provides:

  1. A stable virtual IP address (ClusterIP) that doesn't change
  2. Load balancing across Pods matching the Service's label selector
  3. Service discovery through Kubernetes DNS

Think of a Service like a phone number. The number stays the same, but the person who answers might change. Call the same number and get connected to whoever is currently on duty.

How Services Work

A Service uses label selectors to determine which Pods receive traffic. Here's the mechanism:

yaml
apiVersion: v1 kind: Service metadata: name: backend-service spec: selector: app: backend # <-- Label selector: "find Pods with label app=backend" ports: - port: 80 # <-- Service listens on port 80 targetPort: 5000 # <-- Forward to port 5000 in the Pod type: ClusterIP # <-- Service type (discussed below)

When you apply this Service:

  1. Kubernetes creates a virtual IP address (e.g., 10.96.0.1)
  2. It identifies all Pods matching app: backend
  3. It load-balances traffic to those Pods' port 5000
  4. If Pods are added/removed, the Service updates automatically

Your frontend Pod now talks to:

python
response = requests.get("http://backend-service:80/data")

Even when backend Pods crash and restart, the Service name and IP stay constant. Kubernetes automatically updates the endpoints.


The Three Service Types

Kubernetes offers three main Service types. Choose based on your access pattern.

1. ClusterIP: Internal Communication (Default)

Use when: Pods need to talk to other Pods within the cluster.

yaml
apiVersion: v1 kind: Service metadata: name: database-service spec: selector: app: database ports: - port: 5432 targetPort: 5432 type: ClusterIP # Default if omitted

Characteristics:

  • Virtual IP only accessible from within the cluster
  • No external access possible
  • Lightweight and efficient
  • Most common type (used internally between Pods)

Example traffic flow:

Specification
Frontend Pod → (requests) → ClusterIP Service (10.96.0.5:5432) → (routes to) → Database Pod

2. NodePort: External Access via Host Port

Use when: You need external clients to access your application (development/testing).

yaml
apiVersion: v1 kind: Service metadata: name: api-service spec: selector: app: api ports: - port: 80 # Service port (internal) targetPort: 8000 # Pod port nodePort: 30007 # Host port (must be 30000-32767) type: NodePort

Characteristics:

  • Allocates a port on every cluster node (30000-32767 range)
  • External clients access <node-ip>:<nodePort>
  • Useful for development and testing
  • Not recommended for production (traffic through nodes)

Example traffic flow:

Specification
External Client → (connects to) → Node IP:30007 → (routes to) → Service (10.96.0.1:80) → (load balances to) → API Pods

3. LoadBalancer: Production External Access

Use when: You need production-grade external access with load balancing.

yaml
apiVersion: v1 kind: Service metadata: name: frontend-service spec: selector: app: frontend ports: - port: 80 targetPort: 3000 type: LoadBalancer

Characteristics:

  • Provisions a cloud load balancer (AWS ELB, Azure LB, GCP LB)
  • Gives your Service a public IP address
  • Load balancer distributes traffic across cluster nodes
  • Recommended for production (handles high traffic, automatic scaling)

Example traffic flow:

Specification
External Client → (connects to) → Cloud LB Public IP:80 → (routes to) → Service (10.96.0.2:80) → (load balances to) → Frontend Pods

Decision Framework: Choosing Service Type

RequirementTypeWhy
Pod-to-Pod communicationClusterIPNo external access needed, most efficient
Manual external testingNodePortQuick, no cloud LB needed
Production external accessLoadBalancerCloud LB handles scalability, reliability
Multi-environment consistencyClusterIP + Ingress(Ingress covered in later chapter)

Understanding Label Selectors

Label selectors are the mechanism that connects Services to Pods. They're critical to understand because misconfigured selectors cause Services to have zero endpoints.

How Label Selectors Work

When you define a Service selector, Kubernetes continuously asks: "Which Pods match these labels?"

yaml
# Service definition apiVersion: v1 kind: Service metadata: name: web-service spec: selector: app: web # <-- Kubernetes finds Pods with app: web version: v1 # <-- AND version: v1 ports: - port: 80 targetPort: 8080

This Service routes traffic to Pods labeled both app: web and version: v1.

Example: Label Matching

Pod 1: Matches the selector

yaml
apiVersion: v1 kind: Pod metadata: name: web-pod-1 labels: app: web # <-- Matches version: v1 # <-- Matches env: prod # <-- Extra label (OK) spec: containers: - name: nginx image: nginx:alpine

Pod 2: Does NOT match

yaml
apiVersion: v1 kind: Pod metadata: name: api-pod-1 labels: app: api # <-- Does NOT match (wrong value) version: v1 # <-- Matches but doesn't satisfy both spec: containers: - name: flask image: flask:latest

Pod 3: Does NOT match

yaml
apiVersion: v1 kind: Pod metadata: name: web-pod-2 labels: app: web # <-- Matches version: v2 # <-- Does NOT match (wrong version) spec: containers: - name: nginx image: nginx:alpine

The Service routes to Pod 1 only. Pods 2 and 3 are ignored.


Hands-On: Creating and Exposing Services

You'll create a Deployment, then expose it via different Service types. Start with your Docker Desktop Kubernetes cluster running.

Step 1: Create a Deployment with Clear Labels

Create a file called web-deployment.yaml:

yaml
apiVersion: apps/v1 kind: Deployment metadata: name: web-deployment spec: replicas: 3 selector: matchLabels: app: web # <-- Key label template: metadata: labels: app: web # <-- Pod gets this label spec: containers: - name: nginx image: nginx:alpine ports: - containerPort: 80

Apply it:

bash
kubectl apply -f web-deployment.yaml

Output:

Specification
deployment.apps/web-deployment created

Verify Pods are running with labels displayed:

bash
kubectl get pods --show-labels

Output:

Specification
NAME READY STATUS RESTARTS AGE LABELS web-deployment-5d5f4c7f5b-abc12 1/1 Running 0 10s app=web,pod-template-hash=5d5f4c7f5b web-deployment-5d5f4c7f5b-def45 1/1 Running 0 10s app=web,pod-template-hash=5d5f4c7f5b web-deployment-5d5f4c7f5b-ghi78 1/1 Running 0 10s app=web,pod-template-hash=5d5f4c7f5b

Notice each Pod has the label app=web (the key label the Service will use). Get their IP addresses:

bash
kubectl get pods -o wide

Output:

Specification
NAME READY STATUS RESTARTS AGE IP NODE web-deployment-5d5f4c7f5b-abc12 1/1 Running 0 10s 10.244.0.3 docker-desktop web-deployment-5d5f4c7f5b-def45 1/1 Running 0 10s 10.244.0.4 docker-desktop web-deployment-5d5f4c7f5b-ghi78 1/1 Running 0 10s 10.244.0.5 docker-desktop

Each Pod has a unique IP: 10.244.0.3, 10.244.0.4, 10.244.0.5. If a Pod crashes, Kubernetes replaces it with a new Pod at a new IP. This is why Services matter—they provide stable routing regardless of which Pod IPs are currently active.

Step 2: Expose via ClusterIP Service

Create web-service-clusterip.yaml:

yaml
apiVersion: v1 kind: Service metadata: name: web-service spec: selector: app: web # <-- Targets Pods with app: web ports: - port: 80 targetPort: 80 type: ClusterIP

Apply it:

bash
kubectl apply -f web-service-clusterip.yaml

Output:

Specification
service/web-service created

Inspect the Service:

bash
kubectl get services

Output:

Specification
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) web-service ClusterIP 10.96.0.234 <none> 80/TCP

The Service has a stable virtual IP: 10.96.0.234. This IP never changes, even if Pods are replaced.

Step 3: Access the Service Internally

Launch a temporary Pod inside the cluster to test internal access:

bash
kubectl run -it test-pod --image=alpine --rm -- sh

From inside the Pod, curl the Service:

bash
apk add curl curl http://web-service

Output:

html
<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> </head> ...

Success! The Service routed your request to one of the backend Pods. Try it again:

bash
curl http://web-service

Same output. Kubernetes load-balanced across Pods transparently. You didn't need to know which specific Pod answered—the Service handled it.

Exit the test Pod:

bash
exit

Step 4: Convert to NodePort for External Access

Update web-service-clusterip.yaml:

yaml
apiVersion: v1 kind: Service metadata: name: web-service spec: selector: app: web ports: - port: 80 targetPort: 80 nodePort: 30007 # <-- Add this (external port) type: NodePort # <-- Change this

Apply the update:

bash
kubectl apply -f web-service-clusterip.yaml

Output:

Specification
service/web-service configured

Verify the change:

bash
kubectl get services

Output:

Specification
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) web-service Node Port 10.96.0.234 <none> 80:30007/TCP

The Service now listens on node port 30007. With Docker Desktop, access the Service via localhost:

bash
curl http://localhost:30007

Output:

html
<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> </head> ...

Success! External traffic now reaches your Deployment through the NodePort Service.


Kubernetes DNS: Service Discovery

Your Pods don't need to know the Service IP address. Kubernetes provides automatic DNS discovery.

Every Service gets a DNS name in the format:

Specification
<service-name>.<namespace>.svc.cluster.local

For the web-service in the default namespace:

Specification
web-service.default.svc.cluster.local

Within the same namespace, you can shorten it to:

Specification
web-service

DNS in Action

Launch a test Pod and resolve the DNS name:

bash
kubectl run -it test-pod --image=alpine --rm -- sh

From inside the Pod, install curl and bind-tools (for DNS utilities), then test DNS resolution:

bash
apk add curl bind-tools nslookup web-service

Output:

Specification
Name: web-service Address 1: 10.96.0.234

Kubernetes DNS returned the Service's virtual IP. Your application can connect without hardcoding IPs. Test it:

bash
curl -I http://web-service

Output:

Specification
HTTP/1.1 200 OK Server: nginx/1.26.2 Date: Fri, 22 Dec 2023 10:15:32 GMT Content-Type: text/html Content-Length: 612 Connection: keep-alive

The request succeeded! Kubernetes DNS resolved web-service to the virtual IP, and the Service routed to a backend Pod.

You can also use the full DNS name from different namespaces:

bash
curl -I http://web-service.default.svc.cluster.local

Output:

Specification
HTTP/1.1 200 OK Server: nginx/1.26.2 Date: Fri, 22 Dec 2023 10:15:35 GMT Content-Type: text/html Content-Length: 612 Connection: keep-alive

Both work. Kubernetes DNS resolves either form to the virtual IP. Exit the test Pod:

bash
exit

Debugging: Service Selector Mismatch

The most common Service problem: a Service has no endpoints. This happens when the label selector doesn't match any Pods.

Scenario: Misconfigured Selector

Create a Deployment with app: backend:

yaml
apiVersion: apps/v1 kind: Deployment metadata: name: backend-deployment spec: replicas: 2 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: flask image: flask:latest ports: - containerPort: 5000

Apply it:

bash
kubectl apply -f backend-deployment.yaml

Output:

Specification
deployment.apps/backend-deployment created

Now create a Service with the wrong selector:

yaml
apiVersion: v1 kind: Service metadata: name: backend-service spec: selector: app: api # <-- WRONG: Looking for app: api, but Pods have app: backend ports: - port: 80 targetPort: 5000 type: ClusterIP

Apply it:

bash
kubectl apply -f backend-service-wrong.yaml

Output:

Specification
service/backend-service created

Check the Service endpoints:

bash
kubectl get endpoints backend-service

Output:

Specification
NAME ENDPOINTS AGE backend-service <none> 10s

No endpoints! The Service exists but routes to nothing. Any traffic sent to this Service fails because there's no Pod to handle it.

Fixing the Selector

Update the Service with the correct selector:

yaml
apiVersion: v1 kind: Service metadata: name: backend-service spec: selector: app: backend # <-- FIXED: Now matches the Pod labels ports: - port: 80 targetPort: 5000 type: ClusterIP

Apply the fix:

bash
kubectl apply -f backend-service-wrong.yaml

Output:

Specification
service/backend-service configured

Check endpoints again:

bash
kubectl get endpoints backend-service

Output:

Specification
NAME ENDPOINTS AGE backend-service 10.244.0.6:5000,10.244.0.7:5000 5s

Now the Service has 2 endpoints (the 2 backend Pods). Traffic will route correctly.

Debugging Process

When a Service has no endpoints, follow this systematic check:

  1. List the Pods and their labels:
bash
kubectl get pods --show-labels

Output:

Specification
NAME READY LABELS backend-deployment-abcd1234-xyz 1/1 app=backend,pod-template-hash=abcd1234 backend-deployment-abcd1234-uvw 1/1 app=backend,pod-template-hash=abcd1234
  1. Check the Service selector:
bash
kubectl describe service backend-service

Output:

Specification
Name: backend-service Namespace: default Labels: <none> Annotations: <none> Selector: app: api # <-- Wrong! Should be app: backend Type: ClusterIP IP: 10.96.0.1 Port: <unset> 80/TCP TargetPort: 5000/TCP Endpoints: <none> Session Affinity: None
  1. Compare: The Pods have app=backend but the Service selector is app=api. That's the mismatch. Fix the Service selector to match the Pod labels.
  2. Verify the fix:
bash
kubectl describe service backend-service

Output:

Specification
Name: backend-service Namespace: default Selector: app: backend # <-- Fixed! Type: ClusterIP IP: 10.96.0.1 Port: <unset> 80/TCP TargetPort: 5000/TCP Endpoints: 10.244.0.6:5000,10.244.0.7:5000 # <-- Endpoints now populated! Session Affinity: None

Now the Service has endpoints. Traffic will route correctly to the backend Pods.


Service Networking Summary

ConceptPurposeExample
ServiceStable virtual IP routing to dynamic Podsweb-service (IP: 10.96.0.234)
Label SelectorMechanism to match Podsapp: web, version: v1
ClusterIPInternal Pod-to-Pod communicationBackend database accessed by frontend
NodePortExternal access via host portTesting tool accessing API on port 30007
LoadBalancerProduction external access via cloud LBPublic users accessing frontend
DNSService discovery by nameweb-service.default.svc.cluster.local

Try With AI

Challenge: You have a FastAPI agent running in Kubernetes (from Module 6 containerization). Expose it so:

  • Other Pods in the cluster can call its API
  • External clients can access it for testing

You'll describe the requirements, and you and AI will iterate on the Service configuration.

Setup:

  • Your agent Deployment is named agent-deployment with label app: agent
  • It listens on port 8000
  • You'll deploy to your Docker Desktop Kubernetes cluster

Part 1: Internal Access Requirement

Tell AI: "I need a Service that lets other Pods access my agent at port 8000. The agent Deployment has label app: agent."

Record AI's Service specification for internal access.

Part 2: Evaluate Internal Design

Review AI's response:

  • Does the Service selector match the Deployment labels?
  • Is the port mapping correct (80 → 8000)?
  • Is the type appropriate for internal access?

Ask yourself: What would you change to make this better?

Part 3: External Access Requirement

Tell AI: "Now I also need external clients to test the agent. Should I convert to NodePort or LoadBalancer? I'm testing locally with Docker Desktop Kubernetes."

Note AI's reasoning about the choice.

Part 4: Design Validation

Ask AI: "Show me the complete Service YAML for external testing access on Docker Desktop Kubernetes."

Apply the YAML to your cluster:

bash
kubectl apply -f agent-service.yaml

Test external access:

bash
curl http://localhost:<nodeport>/docs # Your FastAPI agent's Swagger docs

Part 5: Reflection

Compare your initial understanding to what emerged:

  • Did you initially consider both internal and external access?
  • What would you do differently when deploying to a cloud cluster instead of Docker Desktop?
  • When would you choose LoadBalancer over NodePort?

These questions activate your reasoning for future Service design decisions in production environments.


Reflect on Your Skill

You built a kubernetes-deployment skill in Chapter 0. Test and improve it based on what you learned.

Test Your Skill

bash
Using my kubernetes-deployment skill, create a Service that exposes a Deployment. Does my skill generate ClusterIP, Node Port, or Load Balancer Service types with proper label selectors?

Identify Gaps

Ask yourself:

  • Did my skill include the three Service types (ClusterIP, NodePort, LoadBalancer) and when to use each?
  • Did it explain label selectors as the mechanism connecting Services to Pods?
  • Did it cover Kubernetes DNS (service.namespace.svc.cluster.local)?
  • Did it include debugging steps for Services with no endpoints?

Improve Your Skill

If you found gaps:

bash
My kubernetes-deployment skill is missing Service networking patterns and DNS discovery. Update it to include Service type selection, label selector configuration, DNS naming conventions, and endpoint troubleshooting.

Service Networking Summary

ConceptPurpose
ServiceStable virtual IP and DNS name that routes traffic to a dynamic set of Pods. When Pods crash and restart with new IPs, the Service IP and DNS name never change.
Label SelectorThe connection mechanism between Services and Pods. Selector mismatch is the most common cause of Service failures.
ClusterIPRoutes traffic internally (within the cluster). Accessible only by other Pods.
NodePortOpens a specific port on every cluster node. Primarily used for development or testing access.
LoadBalancerProvisions a production-grade external load balancer in cloud environments.
DNS ResolutionAutomatic discovery in the format service-name.namespace.svc.cluster.local.