🎓 LEVEL 34 DEBRIEF: StatefulSet Volume Templates¶
Congratulations! You've mastered StatefulSet volumeClaimTemplates - the key to providing persistent, per-pod storage for stateful applications!
📊 What You Fixed¶
The Problem:
# All pods sharing ONE PVC
volumes:
- name: database-storage
persistentVolumeClaim:
claimName: database-storage # ❌ All pods use same PVC!
Result: Database corruption - all 3 postgres pods writing to same files!
The Solution:
# Each pod gets its OWN PVC
volumeClaimTemplates:
- metadata:
name: database-storage
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
Result: 3 separate PVCs created automatically:
- database-storage-postgres-cluster-0
- database-storage-postgres-cluster-1
- database-storage-postgres-cluster-2
🔍 Understanding StatefulSet Storage¶
Why StatefulSets Need Special Storage¶
Stateful Applications: - Databases (PostgreSQL, MySQL, MongoDB) - Message queues (Kafka, RabbitMQ) - Distributed systems (etcd, ZooKeeper) - Each instance needs its own persistent identity and data
Wrong Approach (Deployment with single PVC):
kind: Deployment # ❌ For stateful apps
replicas: 3
volumes:
- persistentVolumeClaim:
claimName: shared-data
Right Approach (StatefulSet with volumeClaimTemplates):
kind: StatefulSet # ✅ For stateful apps
replicas: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
Benefits: - Each pod gets dedicated storage - Stable pod names (pod-0, pod-1, pod-2) - Ordered deployment and scaling - Persistent storage survives pod restarts
🎯 volumeClaimTemplates Deep Dive¶
How It Works¶
-
StatefulSet Created:
-
Kubernetes Creates PVCs Automatically:
www-web-0(for pod web-0)www-web-1(for pod web-1)-
www-web-2(for pod web-2) -
Each Pod Gets Its PVC:
- Pod
web-0→ PVCwww-web-0 - Pod
web-1→ PVCwww-web-1 - Pod
web-2→ PVCwww-web-2
PVC Naming Pattern¶
Examples:
- data-mysql-0
- data-mysql-1
- logs-app-0
- cache-redis-2
Lifecycle Management¶
When Pod Deleted: - Pod is removed - PVC remains (not deleted!) - Data preserved
When Pod Recreated: - New pod gets SAME PVC - Data from previous pod available - Maintains state across restarts
When StatefulSet Scaled Down:
- Podweb-2 deleted
- PVC www-web-2 retained
- Data safe for future scale-up
When Scaled Back Up:
- New podweb-2 created
- Automatically bound to existing www-web-2
- Previous data intact!
💥 Common Mistakes¶
Mistake 1: Using Static PVC with StatefulSet¶
# ❌ Wrong
kind: StatefulSet
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc # All pods share this!
Problem: Data conflicts, corruption, or "volume already mounted" errors
Fix: Use volumeClaimTemplates
Mistake 2: Forgetting volumeClaimTemplates¶
# ❌ Wrong
kind: StatefulSet
spec:
replicas: 3
template:
spec:
containers:
- volumeMounts:
- name: data
mountPath: /data
# Missing volumeClaimTemplates!
Problem: Pods have no storage or use emptyDir
Fix: Add volumeClaimTemplates
Mistake 3: Wrong Access Mode¶
Problem: More expensive storage, not needed for per-pod PVCs
Fix: Use ReadWriteOnce (each PVC mounted by single pod)
Mistake 4: Deleting PVCs Manually¶
Problem: Pod cannot start, data lost
Fix: Let StatefulSet manage PVCs, delete StatefulSet if needed
Mistake 5: Not Planning for Storage Growth¶
# ❌ Small initial size
volumeClaimTemplates:
- spec:
resources:
requests:
storage: 1Gi # Too small for database!
Problem: Running out of space, PVC resizing complicated
Fix: Plan capacity, use appropriate initial size
🏗️ Real-World Patterns¶
Pattern 1: PostgreSQL Cluster¶
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:14
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
Pattern 2: Multiple Volumes Per Pod¶
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: app
spec:
volumeClaimTemplates:
- metadata:
name: data # Application data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 50Gi
- metadata:
name: logs # Separate volume for logs
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 20Gi
Result:
- data-app-0 + logs-app-0
- data-app-1 + logs-app-1
- Each pod gets 2 PVCs
Pattern 3: StatefulSet with Shared ConfigMap¶
apiVersion: apps/v1
kind: StatefulSet
spec:
template:
spec:
containers:
- volumeMounts:
- name: data
mountPath: /data # Per-pod storage
- name: config
mountPath: /config # Shared config
volumes:
- name: config
configMap:
name: app-config # Shared by all pods
volumeClaimTemplates: # Per-pod storage
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
🚨 REAL-WORLD HORROR STORY: The Shared Database Volume¶
The Incident: $3.2M Data Corruption Disaster¶
Company: Healthcare SaaS platform Date: September 2021 Impact: 11 hours downtime, 3,200 customers affected, $3.2M revenue loss, HIPAA investigation
What Happened¶
DevOps team deployed PostgreSQL cluster:
# Deployment (should have been StatefulSet!)
kind: Deployment # ❌ Wrong for database!
metadata:
name: postgres
spec:
replicas: 3
template:
spec:
containers:
- name: postgres
volumeMounts:
- name: db-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: db-storage
persistentVolumeClaim:
claimName: postgres-data # ❌ All pods share this PVC!
The Timeline¶
09:00 - Deployment updated to 3 replicas for "high availability" 09:05 - All 3 pods started, all mounting same PVC 09:06 - Database files corrupted (multiple postgres processes writing) 09:10 - Customer reports: "Cannot access patient records" 09:15 - Database completely unusable 09:30 - Emergency declared, all 3,200 customers affected 10:00 - Attempted rollback failed (data already corrupted) 12:00 - Restore from backup started 14:00 - Backup restoration failed (backup also corrupted) 16:00 - Switched to older backup (4 hours of data loss) 20:00 - Service restored, 4 hours of patient data permanently lost
Root Causes¶
- Wrong workload type: Used Deployment instead of StatefulSet
- Shared storage: Single PVC shared by all database pods
- No validation: Didn't test with multiple replicas
- Poor monitoring: No alert on database file conflicts
- Backup issues: Backup taken while database was corrupted
- Insufficient testing: HA setup never tested in staging
The Aftermath¶
- $3.2M in lost revenue and SLA penalties
- 4 hours of patient data permanently lost
- HIPAA investigation and fines
- 15% customer churn
- Complete infrastructure audit mandated
- DevOps team restructured
The Correct Solution¶
apiVersion: apps/v1
kind: StatefulSet # ✅ Correct for database
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 3
template:
spec:
containers:
- name: postgres
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # ✅ Each pod gets own storage
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 100Gi
Lessons Learned¶
- Use StatefulSet for stateful apps: Never use Deployment for databases
- Each instance needs own storage: volumeClaimTemplates, not shared PVC
- Test HA configurations: Verify replicas work before production
- Monitor file locking: Alert on database file conflicts
- Validate backups: Test restoration regularly
- Staging mirrors production: Test exact production config
🛡️ Best Practices¶
1. Choose Right Workload Type¶
# Stateless apps → Deployment
kind: Deployment
# All pods identical, no persistent identity needed
# Stateful apps → StatefulSet
kind: StatefulSet
# Each pod unique, needs own storage and identity
2. Use Appropriate Storage Class¶
volumeClaimTemplates:
- spec:
storageClassName: fast-ssd # For databases
# fast-ssd: High performance, expensive
# standard: Normal performance, cheaper
3. Plan Storage Capacity¶
4. Set Resource Limits¶
containers:
- name: postgres
resources:
requests:
memory: "4Gi"
cpu: "2"
limits:
memory: "8Gi"
cpu: "4"
5. Use Headless Service¶
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None # Headless service for StatefulSet
selector:
app: postgres
ports:
- port: 5432
6. Implement Proper Backup Strategy¶
# CronJob for backups
apiVersion: batch/v1
kind: CronJob
metadata:
name: postgres-backup
spec:
schedule: "0 */6 * * *" # Every 6 hours
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:14
command: ["/backup.sh"]
volumeMounts:
- name: data
mountPath: /data
readOnly: true
🎯 Key Takeaways¶
- StatefulSets for stateful apps - Databases, queues need stable identity
- volumeClaimTemplates for per-pod storage - Each instance gets own PVC
- PVCs persist across pod restarts - Data survives pod lifecycle
- Never share single PVC for databases - Causes corruption and conflicts
- Plan storage capacity upfront - Resizing is complex
- Use headless services - Enable stable network identity
- Test HA before production - Verify multiple replicas work correctly
- Monitor and backup - Protect against data loss
🚀 Next Steps¶
Now that you understand StatefulSet storage, you're ready for:
- Level 35: StorageClass configuration and dynamic provisioning
- Level 36: ConfigMap volumes and key management
- Level 37: Secrets and base64 encoding
Well done! You've mastered StatefulSet volumeClaimTemplates. Remember: stateful apps need stateful storage! 🎉