Services & Networking¶
Duration: 45 minutes (25 minutes theory + 20 minutes lab)
Learning Objectives¶
- Understand why Services are needed
- Learn the different Service types
- Configure Service discovery and DNS
- Map Docker Compose networking to Kubernetes Services
The Problem: Pods Are Ephemeral¶
Pods have dynamic IP addresses that change when they're recreated:
Initial state:
Pod A: 10.244.1.5 ──→ Pod B: 10.244.1.6
Pod B crashes and is recreated:
Pod A: 10.244.1.5 ──→ Pod B: 10.244.1.7 (new IP!)
Problem: How do clients find Pods when IPs change?
Solution: Services provide a stable endpoint.
What is a Service?¶
A Service is an abstraction that defines a logical set of Pods and a policy to access them.
Key Benefits¶
- Stable IP address: Services get a ClusterIP that doesn't change
- DNS name: Services are accessible via DNS
- Load balancing: Automatically distributes traffic across Pods
- Service discovery: Pods can find each other by name
Service Types¶
1. ClusterIP (Default)¶
Purpose: Internal cluster communication only
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP
selector:
app: backend
ports:
- port: 80 # Service port
targetPort: 8080 # Pod port
Access: http://backend-service:80 (from within cluster)
Use case: Internal microservices communication
2. NodePort¶
Purpose: Expose Service on each Node's IP at a static port
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Optional: 30000-32767 range
Access:
- Internal:
http://web-service:80 - External:
http://<NodeIP>:30080
Use case: Development, testing, or when no LoadBalancer available
3. LoadBalancer¶
Purpose: Expose Service externally using cloud provider's load balancer
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- port: 80
targetPort: 80
Access:
- Internal:
http://frontend-service:80 - External: Provided by cloud (AWS ELB, GCP LB, etc.)
Note: In kind/local clusters, LoadBalancer behaves like NodePort unless MetalLB is installed
Use case: Production external access
4. ExternalName¶
Purpose: Map Service to external DNS name
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: database.example.com
Access: external-db resolves to database.example.com
Use case: Accessing external services with internal names
Service Discovery¶
DNS-Based Discovery¶
Kubernetes automatically creates DNS records for Services:
Examples:
# Same namespace (short form)
curl http://api-service
# Different namespace
curl http://api-service.production
# Fully qualified domain name (FQDN)
curl http://api-service.production.svc.cluster.local
Environment Variables¶
Kubernetes injects environment variables for Services:
# Format: <SERVICE_NAME>_SERVICE_HOST and <SERVICE_NAME>_SERVICE_PORT
# Example for service named "database"
DATABASE_SERVICE_HOST=10.96.0.50
DATABASE_SERVICE_PORT=5432
Note: Environment variables are set only for Services that exist when the Pod starts.
Selectors and Labels¶
Services use label selectors to find Pods:
# Service
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web # Matches Pods with this label
tier: frontend
ports:
- port: 80
targetPort: 8080
# Deployment creates Pods with matching labels
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web
tier: frontend
template:
metadata:
labels:
app: web # Must match Service selector
tier: frontend
spec:
containers:
- name: web
image: nginx:latest
Endpoints¶
When a Service selects Pods, Kubernetes creates an Endpoints object:
# View Service endpoints
kubectl get endpoints web-service
# Output:
# NAME ENDPOINTS AGE
# web-service 10.244.1.5:8080,10.244.1.6:8080,10.244.1.7:8080 5m
Endpoints track the IP addresses of Pods backing the Service.
Port Configuration¶
Understanding Ports¶
spec:
ports:
- name: http # Port name (optional but recommended)
protocol: TCP # TCP or UDP
port: 80 # Port exposed by the Service
targetPort: 8080 # Port on the Pod
nodePort: 30080 # Node port (for NodePort/LoadBalancer types)
Port mapping:
Named Ports¶
You can reference container ports by name:
# Deployment
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: web
ports:
- name: web-port # Named port
containerPort: 8080
---
# Service
apiVersion: v1
kind: Service
spec:
ports:
- port: 80
targetPort: web-port # Reference by name
Load Balancing¶
Services automatically load balance across healthy Pods:
flowchart TB
Client["Client Request"]
--> Service["Service"]
Service --> Pod1["Pod1"]
Service --> Pod2["Pod2"]
Service --> Pod3["Pod3"]
Algorithm: Round-robin (default) or session affinity
Session Affinity¶
spec:
sessionAffinity: ClientIP # Stick to same Pod based on client IP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 hours
Headless Services¶
A Service without ClusterIP for direct Pod access:
apiVersion: v1
kind: Service
metadata:
name: database-headless
spec:
clusterIP: None # Makes it headless
selector:
app: database
ports:
- port: 5432
DNS returns Pod IPs directly instead of Service IP.
Use case: StatefulSets, databases where you need direct Pod access
Docker Compose to Kubernetes¶
Docker Compose Networking¶
version: '3'
services:
frontend:
image: nginx:latest
ports:
- "8080:80" # Host:Container
networks:
- webnet
backend:
image: api-server:latest
networks:
- webnet
- dbnet
# frontend can reach this via: http://backend:8080
database:
image: postgres:latest
networks:
- dbnet
networks:
webnet:
dbnet:
Kubernetes Services¶
# Frontend Service (external access)
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: LoadBalancer # or NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
# Backend Service (internal only)
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
type: ClusterIP # default, internal only
selector:
app: backend
ports:
- port: 8080
targetPort: 8080
---
# Database Service (internal only)
apiVersion: v1
kind: Service
metadata:
name: database
spec:
type: ClusterIP
selector:
app: database
ports:
- port: 5432
targetPort: 5432
Key Differences:
- No explicit network creation needed in Kubernetes
- Service types control accessibility (internal vs external)
- DNS-based service discovery is automatic
- All Pods can communicate by default (use NetworkPolicies to restrict)
Creating Services¶
Imperative¶
# Expose a Deployment
kubectl expose deployment web --port=80 --target-port=8080 --type=LoadBalancer
# Expose a Pod
kubectl expose pod my-pod --port=80 --name=my-service
Declarative (Recommended)¶
See examples/clusterip-service.yaml
Testing Services¶
# Create a test Pod
kubectl run test-pod --image=busybox:latest -it --rm -- sh
# Inside the Pod, test DNS resolution
nslookup my-service
# Test HTTP connectivity
wget -O- http://my-service:80
# Exit removes the Pod
exit
Best Practices¶
- Use ClusterIP for internal services - most common type
- Name your ports - helps with multi-port services
- Match selectors carefully - Service won't route if labels don't match
- Use meaningful Service names - they become DNS names
- Avoid NodePort in production - use LoadBalancer or Ingress instead
- Check Endpoints - if traffic doesn't reach Pods, check endpoints
Common Issues¶
No Endpoints¶
kubectl get endpoints my-service
# Shows: <none>
# Causes:
# 1. Selector doesn't match Pod labels
# 2. Pods aren't ready
# 3. Pods don't exist
# Fix: Check labels
kubectl get pods --show-labels
kubectl describe service my-service
Service Not Resolving¶
# Test DNS from within a Pod
kubectl run test --image=busybox -it --rm -- nslookup my-service
# If it fails:
# 1. Check CoreDNS is running: kubectl get pods -n kubesystem -l k8s-app=kube-dns
# 2. Verify Service exists: kubectl get svc my-service
Can't Access NodePort Externally¶
# In kind clusters, you need to map ports in cluster config
# See setup/kind/simple.yaml for port mapping examples
Examples¶
Check the examples/ directory for:
clusterip-service.yaml- Internal Servicenodeport-service.yaml- External access via NodePortloadbalancer-service.yaml- Cloud load balancercompose.yaml- Docker Compose comparison
Key takeaways¶
- Services provide stable endpoints for ephemeral Pods
- ClusterIP (default) for internal, NodePort for testing, LoadBalancer for production external access
- DNS-based service discovery is automatic
- Label selectors connect Services to Pods
- Endpoints track backing Pod IPs
Check your understanding¶
- What problem do Services solve?
- What's the difference between ClusterIP, NodePort, and LoadBalancer?
- How do Pods discover Services?
- What happens if a Service's selector doesn't match any Pods?
- How can you check which Pods a Service is routing to?
Solution
- Services provide stable IP addresses and DNS names for ephemeral Pods with changing IPs
- ClusterIP: internal only; NodePort: accessible via Node IP:port; LoadBalancer: external cloud load balancer
- Via DNS (service-name.namespace) or environment variables
- The Service exists but has no Endpoints - traffic won't be routed anywhere
kubectl get endpoints <service-name>orkubectl describe service <service-name>
Hands-on¶
Apply the concepts from this section in the lab exercises.
Next section¶
Once you've reviewed the content and completed the lab, proceed to the next section