Learning objectives
By the end of this module, you should be able to:
- Deploy a multi-resource manifest safely.
- Verify rollout, Service discovery, health and persistent state.
- Recover the stateful component and prove that persistent data survives Pod replacement.
Capstones should prove behavior, not only object creation
A useful Kubernetes lab verifies the result from inside the cluster: Pods become ready, Services resolve, health checks pass and persistent data survives replacement.
Separate stateless and stateful concerns
The web tier is disposable and replicated. Redis uses a PVC so its append-only data can survive Pod replacement. This is a teaching example, not a production Redis architecture; production stateful systems need topology, backup, HA and failure-domain design.
Operational verification closes the loop
kubectl apply is only the start. rollout status, get/describe, in-cluster requests and persistence checks prove whether the declared state became a functioning system.
Worked examples
See the idea in practice.
Capstone verification sequence
kubectl -n gnu-k8s-fundamentals rollout status deployment/cap-web
kubectl -n gnu-k8s-fundamentals rollout status deployment/cap-redis
kubectl -n gnu-k8s-fundamentals get pods,svc,pvc
kubectl -n gnu-k8s-fundamentals get endpointslices
- Wait for controllers.
- Inspect service endpoints.
- Verify the claim is bound before testing persistence.
Hands-on lab
Lab — Multi-tier Kubernetes capstone
Goal: Deploy nginx from a ConfigMap, Redis with a PVC, health probes and Services; write a Redis key, replace the Redis Pod and prove the key survives.
Before you start
- kubectl is installed and can reach the lab cluster.
- Use the dedicated gnu-k8s-fundamentals namespace unless a step explicitly says otherwise.
STEP 1Apply the capstone
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: cap-web-content
namespace: gnu-k8s-fundamentals
data:
index.html: |
<h1>GNU Group Kubernetes Fundamentals</h1>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cap-redis-data
namespace: gnu-k8s-fundamentals
spec:
accessModes: [ReadWriteOnce]
resources:
requests: {storage: 100Mi}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cap-redis
namespace: gnu-k8s-fundamentals
spec:
replicas: 1
selector: {matchLabels: {app: cap-redis}}
template:
metadata: {labels: {app: cap-redis}}
spec:
containers:
- name: redis
image: redis:7-alpine
args: ["redis-server","--appendonly","yes"]
ports: [{containerPort: 6379}]
readinessProbe: {exec: {command: ["redis-cli","ping"]}, periodSeconds: 2}
resources:
requests: {cpu: 10m, memory: 32Mi}
limits: {cpu: 250m, memory: 128Mi}
volumeMounts: [{name: data, mountPath: /data}]
volumes: [{name: data, persistentVolumeClaim: {claimName: cap-redis-data}}]
---
apiVersion: v1
kind: Service
metadata:
name: cap-redis
namespace: gnu-k8s-fundamentals
spec:
selector: {app: cap-redis}
ports: [{port: 6379, targetPort: 6379}]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cap-web
namespace: gnu-k8s-fundamentals
spec:
replicas: 2
selector: {matchLabels: {app: cap-web}}
template:
metadata: {labels: {app: cap-web}}
spec:
containers:
- name: nginx
image: nginx:alpine
ports: [{containerPort: 80}]
readinessProbe: {httpGet: {path: /, port: 80}, periodSeconds: 2}
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {cpu: 100m, memory: 64Mi}
volumeMounts: [{name: content, mountPath: /usr/share/nginx/html, readOnly: true}]
volumes: [{name: content, configMap: {name: cap-web-content}}]
---
apiVersion: v1
kind: Service
metadata:
name: cap-web
namespace: gnu-k8s-fundamentals
spec:
selector: {app: cap-web}
ports: [{port: 80, targetPort: 80}]
EOF
STEP 2Wait for storage and rollouts
kubectl -n gnu-k8s-fundamentals wait --for=jsonpath='{.status.phase}'=Bound pvc/cap-redis-data --timeout=120s
kubectl -n gnu-k8s-fundamentals rollout status deployment/cap-redis --timeout=180s
kubectl -n gnu-k8s-fundamentals rollout status deployment/cap-web --timeout=180s
STEP 3Verify web Service DNS
kubectl -n gnu-k8s-fundamentals run cap-client --rm -i --restart=Never --image=busybox:1.36 -- wget -qO- http://cap-web
STEP 4Write Redis state through the Service
kubectl -n gnu-k8s-fundamentals run redis-client --rm -i --restart=Never --image=redis:7-alpine -- redis-cli -h cap-redis SET course kubernetes
STEP 5Replace the Redis Pod
kubectl -n gnu-k8s-fundamentals delete pod -l app=cap-redis --wait=true
kubectl -n gnu-k8s-fundamentals rollout status deployment/cap-redis --timeout=180s
STEP 6Read the persisted key
kubectl -n gnu-k8s-fundamentals run redis-check --rm -i --restart=Never --image=redis:7-alpine -- redis-cli -h cap-redis GET course
STEP 7Clean up all capstone objects
kubectl -n gnu-k8s-fundamentals delete service cap-web cap-redis
kubectl -n gnu-k8s-fundamentals delete deployment cap-web cap-redis
kubectl -n gnu-k8s-fundamentals delete configmap cap-web-content
kubectl -n gnu-k8s-fundamentals delete pvc cap-redis-data
Verify
- Both Deployments roll out successfully.
- The web Service returns GNU Group Kubernetes Fundamentals.
- Redis returns OK for SET and kubernetes for GET after its Pod is replaced.
Expected outcome
- The capstone demonstrates stateless replication, Service discovery, configuration injection, readiness and persistent storage.
If it fails
- If PVC is Pending, inspect the default StorageClass and events.
- If Redis GET fails after replacement, confirm the replacement mounted the same PVC and AOF completed its initial write.
- If Services have no endpoints, compare selectors and Pod readiness.
Real-world connectionThis pattern mirrors real platform work: declare multiple cooperating resources, verify control-plane convergence, then test behavior and recovery.
Avoid these traps
Common mistakes
- Calling apply success the same thing as application success.
- Treating a single-replica Redis Deployment as a production HA design.
- Deleting the PVC before verifying persistence.
Knowledge check
Can you explain it without looking back?
Open each answer only after you have tried to answer the question yourself.
1What proves the web tier is serving through Kubernetes discovery?
An in-cluster client successfully calls the Service DNS name.
2What proves the Redis data outlives a Pod?
The key remains readable after the Redis Pod is replaced while the PVC is preserved.
3Why wait for rollout status?
To verify controllers converged before functional testing.