Introduction#
Deployments are the go-to workload type for stateless applications. StatefulSets are designed for stateful workloads that require stable identities, ordered operations, and persistent storage. Choosing the wrong type causes operational problems.
Key Differences#
| Property | Deployment | StatefulSet |
|---|---|---|
| Pod names | Random: app-7d4b9c-xkz9p |
Ordered: app-0, app-1, app-2 |
| Pod identity | Interchangeable | Stable, persistent |
| Scaling order | Parallel (all at once) | Sequential (0, 1, 2…) |
| Rolling update order | Random | Reverse order (2, 1, 0) |
| Persistent volumes | Shared or none | Each pod gets its own PVC |
| DNS | Single service DNS | Individual DNS per pod |
| Deletion | Parallel | Reverse order |
StatefulSet Example: Redis Cluster#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: production
spec:
serviceName: redis-headless # required: headless service for stable DNS
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
command:
- redis-server
- --appendonly yes
- --cluster-enabled yes
- --cluster-config-file /data/nodes.conf
volumeClaimTemplates: # each pod gets its own PVC
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 10Gi
---
# Headless service: provides DNS for individual pods
apiVersion: v1
kind: Service
metadata:
name: redis-headless
namespace: production
spec:
clusterIP: None # headless: no virtual IP
selector:
app: redis
ports:
- port: 6379
With this setup, each pod gets stable DNS:
redis-0.redis-headless.production.svc.cluster.localredis-1.redis-headless.production.svc.cluster.localredis-2.redis-headless.production.svc.cluster.local
Stable Identity Matters for Consensus#
Distributed systems using consensus algorithms (Raft, Paxos) require stable identities. Pods must be able to refer to specific peers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Example: etcd StatefulSet init container bootstrapping
initContainers:
- name: init-etcd
image: busybox
command:
- sh
- -c
- |
# Determine this pod's index from hostname (e.g., etcd-2 → index 2)
IDX=${HOSTNAME##*-}
PEERS=""
for i in $(seq 0 2); do
PEERS="${PEERS}etcd-${i}=http://etcd-${i}.etcd-headless:2380,"
done
echo "--initial-cluster ${PEERS%,}" > /etc/etcd/cluster-config
Ordered Rolling Updates#
StatefulSets update pods in reverse order and wait for each pod to become Ready before proceeding.
1
2
3
4
5
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # only update pods with index >= 2 (canary)
1
2
3
4
5
6
# Update only pod-2 first (partition=2 means pods 0,1 keep old version)
kubectl patch statefulset redis -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
kubectl set image statefulset/redis redis=redis:7.2-alpine
# Verify pod-2 updated, then roll out to the rest
kubectl patch statefulset redis -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
PVC Lifecycle#
StatefulSet PVCs are not deleted when the StatefulSet is deleted. This protects data but requires manual cleanup.
1
2
3
4
5
6
7
8
# Delete StatefulSet without deleting pods (for maintenance)
kubectl delete statefulset redis --cascade=orphan
# Manually delete PVCs after confirming data is backed up
kubectl delete pvc -l app=redis -n production
# Or use the newer cascade policy
kubectl delete statefulset redis --cascade=foreground
When to Use Deployments vs StatefulSets#
Use StatefulSet for:
- Databases (PostgreSQL, MySQL, MongoDB, Cassandra)
- Distributed caches (Redis Cluster)
- Message brokers (Kafka, Pulsar, RabbitMQ in cluster mode)
- Consensus systems (etcd, ZooKeeper)
- Any workload where individual pod identity matters to the application
Use Deployment for:
- Stateless HTTP APIs
- Background workers consuming from queues
- Any workload where pods are interchangeable
A common mistake is running a database as a Deployment. This works until pods restart and get new identities — breaking cluster membership and data locality.
Conclusion#
StatefulSets give you stable pod names, ordered operations, and per-pod persistent storage. The trade-off is more complex lifecycle management. Deployments are simpler and sufficient for stateless workloads. The rule of thumb: if your application cares which pod it is or needs persistent local storage, use a StatefulSet.