Stateful Applications on Kubernetes
A comprehensive guide to running stateful applications on Kubernetes covering StatefulSets, headless services, ordered deployment, stable storage, and practical implementation strategies for databases and stateful workloads.
Stateful applications are applications that maintain state, store data persistently, and require stable identities. Unlike stateless applications, stateful applications cannot be easily replaced or scaled without considering data persistence and ordering.
Examples of stateful applications include databases (PostgreSQL, MySQL, MongoDB), message queues (Kafka, RabbitMQ), distributed systems (Zookeeper, etcd), and any application that stores user data or maintains session state.
Kubernetes provides StatefulSets specifically for managing stateful applications, offering features like stable network identities, ordered deployment, and persistent storage.
- Stable Identity: Each instance must have a unique, stable hostname
- Ordered Deployment: Instances must start and stop in a specific order
- Persistent Storage: Data must survive pod restarts and rescheduling
- Stable Storage: Each instance should have its own persistent storage
- Graceful Termination: Clean shutdown to maintain data integrity
A StatefulSet is a Kubernetes workload API object used to manage stateful applications. It provides guarantees about the ordering and uniqueness of pods, stable network identities, and persistent storage.
Stable Network Identity
<statefulset-name>-<ordinal> (e.g., mysql-0, mysql-1). These names persist across rescheduling.Ordered Deployment
Persistent Storage
Headless Service
# Headless Service for StatefulSet
apiVersion: v1
kind: Service
metadata:
name: postgres
labels:
app: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
name: postgres
---
# PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
labels:
app: postgres
spec:
serviceName: postgres
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
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_DB
value: mydb
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
timeoutSeconds: 5
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
timeoutSeconds: 2
terminationGracePeriodSeconds: 60
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
# Secret for database credentials
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
type: Opaque
data:
password: cG9zdGdyZXNwYXNz # base64 encoded
- Always use a headless service for StatefulSets
- Define terminationGracePeriodSeconds for graceful shutdown
- Use readiness and liveness probes for health checks
- Set resource requests and limits for predictable performance
- Use volumeClaimTemplates for automatic PVC creation
- Consider PodDisruptionBudget for availability
StatefulSets enforce strict ordering for pod creation, deletion, and updates. This is critical for applications that require leader election, master-slave replication, or ordered initialization.
# Ordered creation: 0, 1, 2 (sequential)
# Pods are created in ascending order
kubectl create -f statefulset.yaml
# Watches: postgres-0 created, Running → postgres-1 created, Running → postgres-2 created, Running
# Ordered deletion: 2, 1, 0 (reverse)
kubectl delete statefulset postgres
# postgres-2 deleted → postgres-1 deleted → postgres-0 deleted
# Scaling up: 0, 1, 2, 3
kubectl scale statefulset postgres --replicas=4
# postgres-3 created
# Scaling down: 3, 2, 1, 0
kubectl scale statefulset postgres --replicas=2
# postgres-3 deleted → postgres-2 deleted
# Rolling updates (ordered)
# Updated pods are terminated in reverse order (2, 1, 0)
# New pods are created in order (0, 1, 2)
# Partitioned rolling update (canary)
# Update only pods with index >= partition
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # Only updates pod-2 and above
A headless service is a service with clusterIP: None. Instead of load balancing to a cluster IP, DNS resolves to the individual pod IPs, enabling direct pod-to-pod communication.
# Headless service definition
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
# DNS resolution for StatefulSet pods
# Each pod gets a stable DNS entry:
# <pod-name>.<service-name>.<namespace>.svc.cluster.local
# Example:
# postgres-0.postgres.default.svc.cluster.local
# postgres-1.postgres.default.svc.cluster.local
# postgres-2.postgres.default.svc.cluster.local
# Use in application configuration
# PostgreSQL replication configuration
cat <> postgres.conf
primary_conninfo = 'host=postgres-0.postgres.default.svc.cluster.local user=replicator password=replicator'
EOF
# Testing DNS resolution from a pod
kubectl run test --image=busybox -it --rm --restart=Never -- nslookup postgres.default.svc.cluster.local
# For StatefulSets, DNS returns all pod IPs
# For individual pod access, use the pod-specific DNS name
- Direct pod-to-pod communication without load balancing
- Stable DNS names for each pod
- Essential for clustering and replication
- Enables service discovery for stateful applications
- Supports custom load balancing strategies
StatefulSets use volumeClaimTemplates to automatically create PersistentVolumeClaims for each pod. Each pod gets its own PVC, ensuring data is retained even if the pod is rescheduled.
# volumeClaimTemplates in StatefulSet
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
# Each pod gets its own PVC
# postgres-0 → pvc-data-postgres-0
# postgres-1 → pvc-data-postgres-1
# postgres-2 → pvc-data-postgres-2
# View PVCs
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES
# data-postgres-0 Bound pv-123 10Gi RWO
# data-postgres-1 Bound pv-456 10Gi RWO
# data-postgres-2 Bound pv-789 10Gi RWO
# Storage retention after pod deletion
# PVCs are NOT deleted when pods are deleted
# They persist until the StatefulSet or PVC is deleted
# Manual PVC cleanup
kubectl delete pvc data-postgres-0
# Important: When scaling down, PVCs are retained
# This prevents data loss if you scale back up
- Use volumeClaimTemplates for automatic provisioning
- PVCs persist even when pods are deleted (data safety)
- Manual cleanup required for removed PVCs
- Consider StorageClasses for different storage tiers
- Backup PVC data regularly
- Monitor PVC usage and capacity
PodDisruptionBudget (PDB) limits the number of pods that can be voluntarily evicted during cluster operations like node drains or cluster upgrades.
# PodDisruptionBudget for StatefulSet
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: postgres-pdb
labels:
app: postgres
spec:
minAvailable: 2 # At least 2 pods must be available
selector:
matchLabels:
app: postgres
# Alternative: maxUnavailable
# spec:
# maxUnavailable: 1 # Only 1 pod can be unavailable at a time
# Check PDB status
kubectl get pdb
kubectl describe pdb postgres-pdb
PostgreSQL
Apache Kafka
Elasticsearch
MySQL
MongoDB
etcd
| Feature | Deployment | StatefulSet |
|---|---|---|
| Pod Naming | Random names | Stable, ordered ( |
| Network Identity | Transient | Stable (headless service DNS) |
| Storage | Ephemeral (unless PVC shared) | Persistent (per-pod PVC) |
| Deployment Order | Parallel | Ordered (0, 1, 2...) |
| Scaling | Parallel | Ordered (create/delete in order) |
| Rolling Update | Parallel | Ordered (reverse order) |
| Use Case | Stateless applications | Stateful applications |
--cascade=foreground or --cascade=orphan flags to control behavior.kubectl scale statefulset <name> --replicas=<count>. Scaling up creates new pods in ascending order. Scaling down deletes pods in descending order. Scaling operations are sequential, not parallel.Stateful applications are a critical part of many Kubernetes deployments. Master StatefulSets, headless services, and persistent storage to run databases and stateful workloads reliably in production.