By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
You’re deploying a PostgreSQL cluster, a Redis cache, or a Kafka message broker in Kubernetes. These aren’t just "stateless" web apps—they remember things: database records, cache keys, message offsets. If a pod restarts, crashes, or gets rescheduled, you cannot lose that data. Worse, if the pod comes back with a new name or IP, other services won’t recognize it, breaking replication, leader election, or client connections.
This is where StatefulSets come in. They give you: ✅ Stable network identity (pods keep the same name and DNS even after rescheduling) ✅ Persistent storage (data survives pod restarts) ✅ Ordered, graceful scaling (unlike Deployments, which scale chaotically)
What breaks if you ignore this?- Data loss: If you use a Deployment with emptyDir storage, a pod restart wipes your database.- Split-brain: If a database pod comes back with a new name, replicas won’t recognize it, causing corruption.- Client disconnects: If a Redis pod restarts with a new IP, clients lose their connection mid-transaction.
emptyDir
Real-world scenario:You inherit a Kubernetes cluster running MongoDB as a Deployment. One day, a node fails. The pod reschedules to another node, but: - The new pod gets a new name (mongo-abc123 → mongo-def456).- The PersistentVolumeClaim (PVC) is tied to the old pod, so the new pod starts with empty storage.- Replica sets break because they can’t find the old pod.- Your database is now corrupted.
mongo-abc123
mongo-def456
This guide will show you how to avoid this by using StatefulSets correctly.
persistentVolumeReclaimPolicy
mongo-0.mongo.default.svc.cluster.local
app-0
app-1
app-2
app-3
✅ A running Kubernetes cluster (Minikube, Kind, EKS, GKE, AKS) ✅ kubectl installed and configured ✅ Basic familiarity with kubectl commands
kubectl
StatefulSets require a headless service to provide stable DNS names.
# postgres-service.yaml apiVersion: v1 kind: Service metadata: name: postgres labels: app: postgres spec: ports: - port: 5432 name: postgres clusterIP: None # ⚠️ This makes it headless selector: app: postgres
Apply it:
kubectl apply -f postgres-service.yaml
Verify:
kubectl get svc postgres
Expected output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE postgres ClusterIP None <none> 5432/TCP 5s
This StatefulSet: - Runs PostgreSQL (a stateful app).- Uses VolumeClaimTemplates to auto-create PVCs for each pod.- Ensures ordered scaling (pods come up one by one).
# postgres-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: "postgres" # Must match the headless service replicas: 3 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:15 ports: - containerPort: 5432 name: postgres env: - name: POSTGRES_PASSWORD value: "mysecretpassword" volumeMounts: - name: postgres-persistent-storage mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: postgres-persistent-storage spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 5Gi
kubectl apply -f postgres-statefulset.yaml
kubectl get pods -w
Expected output (pods come up one by one):
NAME READY STATUS RESTARTS AGE postgres-0 1/1 Running 0 30s postgres-1 0/1 Pending 0 0s postgres-1 0/1 ContainerCreating 0 0s postgres-1 1/1 Running 0 10s postgres-2 0/1 Pending 0 0s
Each pod gets its own PVC:
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE postgres-persistent-storage-postgres-0 Bound pvc-1234abcd-5678-90ef-ghij-klmnopqrstuv 5Gi RWO standard 2m postgres-persistent-storage-postgres-1 Bound pvc-2345bcde-6789-01fg-hijk-lmnopqrstuvw 5Gi RWO standard 1m postgres-persistent-storage-postgres-2 Bound pvc-3456cdef-7890-12gh-ijkl-mnopqrstuvwx 5Gi RWO standard 30s
Each pod has a stable DNS name:
kubectl exec -it postgres-0 -- bash
Inside the pod:
apt update && apt install -y dnsutils nslookup postgres-1.postgres.default.svc.cluster.local
Server: 10.96.0.10 Address: 10.96.0.10#53 Name: postgres-1.postgres.default.svc.cluster.local Address: 10.244.1.5 # Same IP even if pod restarts
Why this matters:- If postgres-1 crashes, Kubernetes won’t create postgres-3 until postgres-1 is back.- The DNS name never changes, so other pods (e.g., a web app) can always find it.
postgres-1
postgres-3
Delete postgres-1:
kubectl delete pod postgres-1
Watch what happens:
Expected behavior: 1. postgres-1 terminates.2. Kubernetes waits (StatefulSets scale one pod at a time).3. A new postgres-1 is created with the same PVC and DNS name.
Verify storage persists:
kubectl exec -it postgres-1 -- bash psql -U postgres -c "CREATE TABLE test (id SERIAL PRIMARY KEY); INSERT INTO test DEFAULT VALUES;"
Delete the pod again:
Reconnect and check data:
kubectl exec -it postgres-1 -- bash psql -U postgres -c "SELECT * FROM test;"
id ---- 1
✅ Data survived the pod restart!
kubectl create secret
bash kubectl create secret generic postgres-secret --from-literal=POSTGRES_PASSWORD=mysecretpassword
storageClass
volumeBindingMode: WaitForFirstConsumer
yaml resources: requests: cpu: "500m" memory: "1Gi" limits: cpu: "1" memory: "2Gi"
podManagementPolicy: OrderedReady
persistentVolumeReclaimPolicy: Retain
yaml metadata: labels: app: postgres tier: database env: prod
yaml livenessProbe: exec: command: ["pg_isready", "-U", "postgres"] initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: exec: command: ["pg_isready", "-U", "postgres"] initialDelaySeconds: 5 periodSeconds: 5
requests
limits
❌ ReplicaSet
"You need to deploy a MySQL cluster with persistent storage. What do you use?"
❌ DaemonSet
"What happens if you delete a pod managed by a StatefulSet?"
app-abc123
Deploy a Redis cluster with 3 replicas using a StatefulSet. Ensure: - Each pod has persistent storage.- Pods have stable DNS names.- If a pod crashes, data survives.
bash kubectl apply -f redis-service.yaml -f redis-statefulset.yaml kubectl get pods -w kubectl get pvc
Why it works:- Headless service → Stable DNS (redis-0.redis.default.svc.cluster.local).- VolumeClaimTemplate → Each pod gets its own PVC.- StatefulSet → Pods restart with the same name and storage.
redis-0.redis.default.svc.cluster.local
kubectl get statefulsets
kubectl get sts
kubectl describe sts <name>
kubectl delete sts <name> --cascade=orphan
clusterIP: None
Retain
Delete
Recycle
kubectl get storageclass
gp2
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.