Kubernetes StatefulSets: Managing Stateful Applications
StatefulSets enable running stateful applications in Kubernetes. After deploying databases and stateful services, here’s how to use StatefulSets effectively.
StatefulSets vs Deployments
Deployments
- Stateless pods
- Random names
- No stable storage
- No ordering guarantees
StatefulSets
- Stateful pods
- Stable names
- Persistent storage
- Ordered deployment
Basic StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "web"
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.15
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "fast-ssd"
resources:
requests:
storage: 10Gi
Headless Service
apiVersion: v1
kind: Service
metadata:
name: web
spec:
clusterIP: None # Headless service
selector:
app: web
ports:
- port: 80
name: web
Stable Network Identity
Each pod gets:
- Stable hostname:
web-0,web-1,web-2 - Stable DNS:
web-0.web.default.svc.cluster.local
# Pods are accessible by name
ping web-0.web
ping web-1.web
ping web-2.web
Ordered Deployment
spec:
podManagementPolicy: OrderedReady # Default
# Pods created sequentially: 0, then 1, then 2
Parallel Pod Management
spec:
podManagementPolicy: Parallel
# All pods created simultaneously
Persistent Storage
Volume Claim Template
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "ssd"
resources:
requests:
storage: 100Gi
Each pod gets its own PVC:
data-web-0data-web-1data-web-2
Database Example: PostgreSQL
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
postgresql.conf: |
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 1GB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 4MB
min_wal_size = 1GB
max_wal_size = 4GB
---
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
data:
postgres-password: cG9zdGdyZXM= # base64 encoded
---
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:13
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
value: mydb
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: postgres-password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: config
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U postgres
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U postgres
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: config
configMap:
name: postgres-config
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "fast-ssd"
resources:
requests:
storage: 100Gi
Redis Cluster Example
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: "redis"
replicas: 6
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:6-alpine
ports:
- containerPort: 6379
name: redis
command:
- redis-server
- /etc/redis/redis.conf
- --cluster-enabled
- "yes"
- --cluster-config-file
- /data/nodes.conf
- --cluster-node-timeout
- "5000"
- --appendonly
- "yes"
volumeMounts:
- name: redis-data
mountPath: /data
- name: config
mountPath: /etc/redis
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: config
configMap:
name: redis-config
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "fast-ssd"
resources:
requests:
storage: 20Gi
Scaling StatefulSets
# Scale up
kubectl scale statefulset web --replicas=5
# Scale down (pods removed in reverse order)
kubectl scale statefulset web --replicas=2
Update Strategy
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # Update pods 2, 3, 4... but not 0, 1
Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: web
Init Containers
spec:
template:
spec:
initContainers:
- name: init-db
image: busybox
command:
- sh
- -c
- |
if [ ! -f /data/initialized ]; then
echo "Initializing database..."
touch /data/initialized
fi
volumeMounts:
- name: data
mountPath: /data
containers:
- name: app
# ...
StatefulSet Updates
Rolling Update
spec:
updateStrategy:
type: RollingUpdate
OnDelete Strategy
spec:
updateStrategy:
type: OnDelete
# Pods updated only when manually deleted
Best Practices
- Use headless services - For stable DNS
- Set resource limits - Prevent resource exhaustion
- Use init containers - For initialization
- Configure health checks - Liveness and readiness
- Set pod disruption budgets - Maintain availability
- Use appropriate storage - Fast storage for databases
- Monitor storage - Track disk usage
- Backup data - Regular backups of persistent volumes
Common Patterns
Master-Slave Pattern
# Master pod (index 0)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
replicas: 3
template:
spec:
containers:
- name: mysql
command:
- /bin/sh
- -c
- |
if [ "$(hostname)" = "mysql-0" ]; then
# Master configuration
mysqld --server-id=1 --log-bin
else
# Slave configuration
mysqld --server-id=$HOSTNAME_NUM --replicate-from=mysql-0
fi
Leader Election
# Use init container for leader election
initContainers:
- name: elect-leader
image: leader-elector:latest
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
Conclusion
StatefulSets enable:
- Stable pod identities
- Persistent storage
- Ordered deployment
- Stateful applications
Use StatefulSets for databases, caches, and any application requiring stable identity and storage. The patterns shown here handle production workloads.
Kubernetes StatefulSets from September 2018, covering Kubernetes 1.11+ features.