100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Kubernetes Orchestration
55 minintermediate

Networking Practice — Expose a Multi-Service App

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.

Analogy🏏Cricket
🏏 Think of it like cricket: This exercise is a complete match simulation — not just batting practice. You'll face the full sequence: opening innings (initial deployment), the change of tactics mid-game (rolling update with canary check), an unexpected top-order collapse (bad deployment), and the team's recovery (rollback). Each scenario tests a different aspect of your Kubernetes deployment skills. Just as a batsman who's only practiced in nets needs match experience to handle the unexpected pressure of a live game, you need to experience deployment, update, failure, and recovery in sequence to build the operational instincts for real production management.

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.

Analogy🏏Cricket
🏏 Think of it like cricket: Simulating different CricketPulse API versions with a ConfigMap and stock nginx is like rehearsing a batting-order change using the same players wearing different bib numbers, instead of recruiting brand-new specialists for every drill. Just as swapping a player's instructions on a laminated card lets the coach test how the side responds to a new plan without signing anyone, injecting different response text through a ConfigMap lets nginx serve 'v1' or 'v2' without building a custom Docker image. Just as a good academy sets out a clear practice plan — where each drill lives, which card belongs to which scenario — you lay out a tidy manifest directory before starting. The payoff: you can rehearse realistic multi-version rollout behaviour quickly and cheaply, learning the mechanics without the overhead of building and pushing real container images.
bash
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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 1 is setting the batting lineup before the first ball. You're establishing the production baseline — 3 batsmen at the crease (3 replicas), with the team's fitness protocol in place (health probes), clear performance expectations (resource requests and limits). Everything that follows depends on this solid initial setup.
bash
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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 2 is the mid-series tactical review. The captain introduces a new player (v2 configuration) for one over to evaluate their performance before committing to a longer spell. The pause is the strategic timeout — the captain reviews the scoreboard for the new player's first over before deciding whether to continue the new approach or revert to the proven lineup.
bash
# 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 JSON

Step 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 3 is the batting collapse scenario — a rapid loss of wickets that requires the captain to call the team together, diagnose what went wrong, and switch back to a more conservative approach to save the innings. The bad deployment is the top-order collapse; the rollback is the captain's tactical reset. Every production engineer has this experience; the exercise ensures you've handled it in a controlled environment before it happens in production.
bash
# 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 again

Step 4 — Testing & Verification

Verify the complete multi-service networking stack and clean up.

Analogy🏏Cricket
🏏 Think of it like cricket: Manually deleting a Pod and watching the Deployment replace it is like a captain testing his team's bench depth by pulling a fielder off mid-session and seeing the twelfth man walk on instantly, unprompted. Just as a well-run squad has a standing rule — 'the field always has eleven' — the Deployment holds a declared replica count and the controller reconciles reality back to it the moment a Pod vanishes. Just as changing the declared squad size (say to a five-a-side drill) makes the coach add or bench players to match, `kubectl scale` changes the desired count and the controller adds or removes Pods accordingly. The payoff: you see firsthand that Kubernetes is declarative — you state the number you want and the system continuously enforces it, healing deletions and honouring scale changes without manual babysitting.
bash
# 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.
Lesson 12 of 24
0% complete