Fatskills
Practice. Master. Repeat.
Study Guide: TECH **StatefulSets vs Deployments: The No-BS Guide to Stable Identity & Persistent Storage in Kubernetes**
Source: https://www.fatskills.com/kubernetes/chapter/tech-statefulsets-vs-deployments-the-no-bs-guide-to-stable-identity-persistent-storage-in-kubernetes

TECH **StatefulSets vs Deployments: The No-BS Guide to Stable Identity & Persistent Storage in Kubernetes**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~8 min read

StatefulSets vs Deployments: The No-BS Guide to Stable Identity & Persistent Storage in Kubernetes


1. What This Is & Why It Matters

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.

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

This guide will show you how to avoid this by using StatefulSets correctly.


2. Core Concepts & Components


? StatefulSet

  • Definition: A Kubernetes controller that manages stateful applications by providing stable, unique network identities and persistent storage for each pod.
  • Production insight: If you’re running databases, caches, or distributed systems, you must use StatefulSets—not Deployments.

? Deployment

  • Definition: A Kubernetes controller that manages stateless applications (e.g., web servers, APIs) by ensuring a desired number of identical pods are running.
  • Production insight: Deployments are not for stateful workloads. If you use them for databases, you will lose data.

? Pod

  • Definition: The smallest deployable unit in Kubernetes (one or more containers sharing storage and network).
  • Production insight: Pods are ephemeral—they can be killed and rescheduled at any time. Never store data in a pod.

? PersistentVolume (PV)

  • Definition: A piece of physical storage (e.g., AWS EBS, GCP Persistent Disk) provisioned in the cluster.
  • Production insight: PVs are independent of pods—they survive pod restarts. Always use them for stateful apps.

? PersistentVolumeClaim (PVC)

  • Definition: A request for storage by a pod. Binds to a PV.
  • Production insight: If a PVC is deleted, the PV may be reclaimed and wiped (depends on persistentVolumeReclaimPolicy).

? Headless Service

  • Definition: A Kubernetes Service without a ClusterIP, used to provide stable DNS names for StatefulSet pods.
  • Production insight: Without a headless service, StatefulSet pods won’t have predictable DNS names (e.g., mongo-0.mongo.default.svc.cluster.local).

? VolumeClaimTemplate

  • Definition: A template in a StatefulSet that automatically creates PVCs for each pod.
  • Production insight: If you don’t use this, you’ll have to manually create PVCs for each pod—not scalable.

? Pod Identity (Ordinal Index)

  • Definition: StatefulSet pods get stable names like app-0, app-1, app-2 (unlike Deployments, which use random hashes).
  • Production insight: If app-1 crashes, Kubernetes won’t create app-3 until app-1 is back (ordered scaling).


3. Step-by-Step Hands-On: Deploying PostgreSQL with a StatefulSet


Prerequisites

✅ A running Kubernetes cluster (Minikube, Kind, EKS, GKE, AKS) ✅ kubectl installed and configured ✅ Basic familiarity with kubectl commands

Step 1: Create a Headless Service for DNS Stability

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

Step 2: Create a StatefulSet with Persistent Storage

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

Apply it:


kubectl apply -f postgres-statefulset.yaml

Verify:


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

Step 3: Check Persistent Storage

Each pod gets its own PVC:


kubectl get pvc

Expected output:


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

Step 4: Test Stable Network Identity

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

Expected output:


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.

Step 5: Simulate a Pod Failure (and See What Happens)

Delete postgres-1:


kubectl delete pod postgres-1

Watch what happens:


kubectl get pods -w

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:


kubectl delete pod postgres-1

Reconnect and check data:


kubectl exec -it postgres-1 -- bash
psql -U postgres -c "SELECT * FROM test;"

Expected output:


 id
----
  1

✅ Data survived the pod restart!


4. ? Production-Ready Best Practices


? Security

  • Never store secrets in YAML: Use kubectl create secret or SealedSecrets.
    bash kubectl create secret generic postgres-secret --from-literal=POSTGRES_PASSWORD=mysecretpassword
  • Use NetworkPolicies to restrict pod-to-pod communication.
  • Enable PodSecurityPolicies (or Pod Security Admission in K8s 1.25+) to prevent privilege escalation.

? Cost Optimization

  • Use storageClass with volumeBindingMode: WaitForFirstConsumer to avoid provisioning unused PVs.
  • Set resource requests/limits to prevent over-provisioning: yaml resources:
    requests:
    cpu: "500m"
    memory: "1Gi"
    limits:
    cpu: "1"
    memory: "2Gi"

?️ Reliability & Maintainability

  • Use podManagementPolicy: OrderedReady (default) for databases to avoid split-brain.
  • Set persistentVolumeReclaimPolicy: Retain to prevent accidental data deletion.
  • Label everything for easy filtering: yaml metadata:
    labels:
    app: postgres
    tier: database
    env: prod

? Observability

  • Monitor PVC usage with Prometheus + Grafana.
  • Set up liveness/readiness probes: yaml livenessProbe:
    exec:
    command: ["pg_isready", "-U", "postgres"]
    initialDelaySeconds: 30
    periodSeconds: 10 readinessProbe:
    exec:
    command: ["pg_isready", "-U", "postgres"]
    initialDelaySeconds: 5
    periodSeconds: 5
  • Log to stdout/stderr (Kubernetes collects logs automatically).


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Using a Deployment for a database Data disappears after pod restart. Always use StatefulSets for stateful apps.
Not using a headless service Pods have unpredictable DNS names. Always create a headless service for StatefulSets.
Deleting a PVC manually Data is lost forever. Set persistentVolumeReclaimPolicy: Retain.
Scaling StatefulSets too quickly Replicas come up in random order, causing split-brain. Use podManagementPolicy: OrderedReady.
Not setting resource limits A single pod consumes all node resources, crashing the cluster. Always set requests and limits.


6. ? Exam/Certification Focus


Typical Question Patterns

  1. "Which Kubernetes resource provides stable network identity for stateful apps?"
  2. StatefulSet
  3. ❌ Deployment
  4. ❌ ReplicaSet

  5. "You need to deploy a MySQL cluster with persistent storage. What do you use?"

  6. StatefulSet + PVC
  7. ❌ Deployment + emptyDir
  8. ❌ DaemonSet

  9. "What happens if you delete a pod managed by a StatefulSet?"

  10. Kubernetes recreates it with the same name and PVC.
  11. Kubernetes creates a new pod with a random name.
  12. The pod is not recreated.

⚠️ Trap Distinctions

Concept StatefulSet Deployment
Pod Naming Stable (app-0, app-1) Random (app-abc123)
Scaling Ordered (one pod at a time) Parallel (all pods at once)
Storage Persistent (PVC per pod) Ephemeral (emptyDir)
Use Case Databases, caches, message brokers Web apps, APIs, stateless services


7. ? Hands-On Challenge (with Solution)


Challenge

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.

Solution

  1. Create a headless service:
    ```yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: redis
    spec:
    clusterIP: None
    ports:
    • port: 6379 selector:
      app: redis
      ```
  2. Create a StatefulSet:
    ```yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
    name: redis
    spec:
    serviceName: redis
    replicas: 3
    selector:
    matchLabels:
    app: redis
    template:
    metadata:
    labels:
    app: redis
    spec:
    containers:
    - name: redis
    image: redis:7
    ports:
    - containerPort: 6379
    volumeMounts:
    - name: redis-data
    mountPath: /data
    volumeClaimTemplates:
    • metadata:
      name: redis-data
      spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
      requests:
      storage: 2Gi
      ```
  3. Apply and verify:
    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.


8. ? Rapid-Reference Crib Sheet

Command/Concept Usage Notes
kubectl get statefulsets List StatefulSets Short name: kubectl get sts
kubectl describe sts <name> Debug a StatefulSet Check events for scaling issues
kubectl get pvc List PersistentVolumeClaims PVCs are pod-specific in StatefulSets
kubectl delete sts <name> --cascade=orphan Delete StatefulSet but keep pods Useful for debugging
Headless Service clusterIP: None Required for stable DNS
VolumeClaimTemplate Auto-creates PVCs No need to manually create PVCs
Pod Identity app-0, app-1 Never changes, even after restart
Ordered Scaling podManagementPolicy: OrderedReady Default, prevents split-brain
PersistentVolume Reclaim Policy Retain / Delete / Recycle Always use Retain for production
StorageClass kubectl get storageclass Controls PV provisioning (e.g., gp2 in AWS)


9. ? Where to Go Next



ADVERTISEMENT