What You'll Build
You will deploy a two-service CricketPulse application — an API backend and a frontend — connect them via ClusterIP Services, expose the frontend publicly through a NodePort Service (for local kind cluster access), and create an Ingress resource to route HTTP traffic based on path. You will use DNS debugging to verify service discovery, test that the readiness probe correctly removes unhealthy Pods from Service endpoints, and observe the relationship between Endpoints and Pods. By the end, you will have a complete understanding of how Services, DNS, and Ingress work together in a realistic multi-service application.
Prerequisites
- A running kind cluster — re-create from Lesson 4: `kind create cluster --config kind-cluster.yaml --name cricketpulse-cluster`.
- For Ingress support, the kind cluster needs the nginx Ingress controller installed.
- kubectl configured and pointing to the kind cluster.
- Lessons 9–11 completed — understanding of Service types, Ingress, and DNS.
- curl or a browser for testing HTTP endpoints.
Setup & Project Structure
Create the networking project directory and install the nginx Ingress controller on the kind cluster. kind requires a specific ingress controller configuration that maps host ports to the cluster.
mkdir -p cricketpulse-k8s/networking
cd cricketpulse-k8s
# For kind clusters: install nginx ingress with kind-specific patches
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# Wait for ingress controller to be ready
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=90s
# Create the app namespace
kubectl create namespace cricketpulse
echo 'Ingress controller installed, namespace created'Step 1 — Foundation
Deploy the API backend and frontend as separate Deployments, each with a ClusterIP Service. Verify both services have populated Endpoints (Pods passing readiness probes) before adding Ingress routing.
cat > networking/api-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: cricketpulse-api
namespace: cricketpulse
spec:
replicas: 2
selector:
matchLabels: { app: cricketpulse-api }
template:
metadata:
labels: { app: cricketpulse-api }
spec:
containers:
- name: api
image: hashicorp/http-echo:alpine
args: ['-text={"service":"cricketpulse-api","status":"ok"}']
ports: [{ name: http, containerPort: 5678 }]
readinessProbe:
httpGet: { path: /, port: 5678 }
initialDelaySeconds: 2
EOF
cat > networking/api-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: cricketpulse-api
namespace: cricketpulse
spec:
type: ClusterIP
selector: { app: cricketpulse-api }
ports: [{ name: http, port: 80, targetPort: 5678 }]
EOF
cat > networking/frontend-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: cricketpulse-frontend
namespace: cricketpulse
spec:
replicas: 2
selector:
matchLabels: { app: cricketpulse-frontend }
template:
metadata:
labels: { app: cricketpulse-frontend }
spec:
containers:
- name: frontend
image: hashicorp/http-echo:alpine
args: ['-text=<html><h1>CricketPulse Live Scores</h1></html>']
ports: [{ name: http, containerPort: 5678 }]
readinessProbe:
httpGet: { path: /, port: 5678 }
initialDelaySeconds: 2
EOF
cat > networking/frontend-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: cricketpulse-frontend
namespace: cricketpulse
spec:
type: ClusterIP
selector: { app: cricketpulse-frontend }
ports: [{ name: http, port: 80, targetPort: 5678 }]
EOF
kubectl apply -f networking/
# Verify Endpoints are populated (Pods are healthy)
kubectl get endpoints -n cricketpulse
# Both services should show Pod IPs, not <none>Step 2 — Core Logic
Test DNS service discovery from inside the cluster and create the Ingress resource for path-based routing. Verify that DNS short names resolve correctly within the same namespace and that Ingress routes requests to the correct backend.
# Test DNS from inside the cluster
kubectl run dns-test -n cricketpulse --image=busybox --rm -it -- /bin/sh
# Inside: verify DNS resolution for both services
# nslookup cricketpulse-api
# nslookup cricketpulse-frontend
# wget -qO- http://cricketpulse-api/
# wget -qO- http://cricketpulse-frontend/
# Create the Ingress for path-based routing
cat > networking/ingress.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cricketpulse-ingress
namespace: cricketpulse
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: cricketpulse-api
port: { name: http }
- path: /
pathType: Prefix
backend:
service:
name: cricketpulse-frontend
port: { name: http }
EOF
kubectl apply -f networking/ingress.yaml
# kind cluster uses localhost:80 for ingress
curl http://localhost/ # Should return frontend HTML
curl http://localhost/api # Should return API JSONStep 3 — Integration & Enhancement
Test the readiness probe's effect on Service Endpoints by scaling the API deployment to 0 replicas and observing how the Endpoints object updates and traffic behaviour changes.
# Scale API to 0 and observe Endpoints change
kubectl scale deployment cricketpulse-api -n cricketpulse --replicas=0
# Watch Endpoints empty out
watch kubectl get endpoints -n cricketpulse
# cricketpulse-api <none> ← No healthy Pods
# cricketpulse-frontend 10.x.x.x:5678,10.x.x.x:5678
# Test that Ingress correctly returns 503 for /api
curl -v http://localhost/api
# < HTTP/1.1 503 Service Temporarily Unavailable
# Frontend still works
curl http://localhost/
# Restore the API
kubectl scale deployment cricketpulse-api -n cricketpulse --replicas=2
kubectl rollout status deployment/cricketpulse-api -n cricketpulse
curl http://localhost/api # Should work againStep 4 — Testing & Verification
Verify the complete multi-service networking stack and clean up.
# Final verification of the complete networking stack
kubectl get all -n cricketpulse
kubectl describe ingress cricketpulse-ingress -n cricketpulse
kubectl get endpoints -n cricketpulse
# Verify both paths route correctly
curl http://localhost/ # Frontend
curl http://localhost/api # API
# Check Ingress controller logs for routing confirmation
kubectl logs -n ingress-nginx \
-l app.kubernetes.io/component=controller --tail=20
# Cleanup
kubectl delete namespace cricketpulse
echo 'Networking exercise complete!'Warning: If `curl http://localhost/api` returns 404, check that the `nginx.ingress.kubernetes.io/rewrite-target: /` annotation is present on the Ingress. Without rewrite-target, nginx forwards `/api` to the backend as the path `/api`, and the backend service (http-echo) doesn't have a `/api` handler — it only responds to `/`. The rewrite-target strips the path prefix so the backend receives `/` regardless of which Ingress path matched. Also verify the Ingress was created in the correct namespace (`cricketpulse`) not `default`.
Extension Challenge: Add a third service — `cricketpulse-scores` — that returns live score JSON. Add it to the Ingress at path `/scores`. Then create a ConfigMap with a mock scores response and mount it as an nginx index.html. Finally, add a readiness probe that checks a specific health endpoint and deliberately fail it by deploying a broken ConfigMap — observe the Endpoints object update in real time as Pods fail their readiness probes and are removed from the Service. Re-apply the correct ConfigMap and verify Pods are automatically re-added to Endpoints when readiness probes pass again.
- Services + DNS + Ingress form a three-layer networking stack: Services provide stable internal addresses, DNS makes those addresses discoverable by name, Ingress provides external HTTP routing to internal Services.
- Endpoints are automatically updated by the Endpoints controller as Pods start (passing readiness probe) and stop — verify Endpoints before debugging routing issues.
- Scaling a Deployment to 0 replicas empties its Service's Endpoints — Ingress correctly returns 503 when no backend Pods are available.
- DNS short-name resolution works within a namespace — `cricketpulse-api` resolves to the Service IP without specifying namespace, because the namespace is appended from DNS search domains.
- The `nginx.ingress.kubernetes.io/rewrite-target` annotation is required when backend services don't handle the full Ingress path — it strips path prefixes before forwarding to backends.
- Always create Ingress objects in the same namespace as the Services they route to — Ingress and Services are namespace-scoped objects.