StatefulSets: Managing Stateful Applications in Kubernetes
📅 Published: August 2026
⏱️ Estimated Reading Time: 14 minutes
🏷️ Tags: Kubernetes, StatefulSets, Stateful Applications, Databases, Storage
Introduction: What is a StatefulSet?
A StatefulSet is a Kubernetes workload API object used to manage stateful applications. It is designed for applications that require stable, unique network identifiers, persistent storage, and ordered, graceful deployment and scaling.
Think of a StatefulSet as a Deployment for stateful applications. While Deployments are ideal for stateless apps (where Pods are interchangeable), StatefulSets handle applications where each instance has a unique identity and persistent state, like databases, message queues, and distributed systems.
Key characteristics:
Stable, unique network identifiers: Each Pod gets a persistent hostname
Ordered, graceful deployment: Pods are created and scaled in order
Stable persistent storage: Each Pod can have its own PersistentVolume
Ordered, graceful deletion: Pods are terminated in reverse order
Ordered, automated rolling updates: Updates are applied in order
Part 1: Why Use StatefulSets?
The Problem with Deployments for Stateful Apps
Deployments are designed for stateless applications where Pods are interchangeable. They work well for web servers, APIs, and microservices. However, they have limitations for stateful applications:
| Aspect | Deployment | StatefulSet |
|---|---|---|
| Pod naming | Random suffixes | Ordered names (web-0, web-1, web-2) |
| Stable network ID | No (IP changes on restart) | Yes (stable hostname) |
| Persistent storage | Shared or ephemeral | Per-Pod persistent storage |
| Scaling | Any order, parallel | Ordered (0, 1, 2, ...) |
| Update strategy | Rolling update (any order) | Ordered rolling update |
| Use case | Stateless apps | Databases, message queues, stateful microservices |
StatefulSet Use Cases
Databases:
PostgreSQL, MySQL, MongoDB, Cassandra
Each instance needs its own data volume
Stable network identity for replication
Message Queues:
Kafka, RabbitMQ, ActiveMQ
Each broker needs persistent storage
Stable network identity for cluster membership
Distributed Systems:
Elasticsearch, Cassandra, ZooKeeper
Each node has a specific role
Stable identity for quorum and consensus
Part 2: StatefulSet Components
Core Components
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: database spec: serviceName: "postgres" # Headless Service name replicas: 3 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:15 env: - name: POSTGRES_PASSWORD value: "secret" ports: - containerPort: 5432 volumeMounts: - name: data mountPath: /var/lib/postgresql/data volumeClaimTemplates: # Per-Pod PVCs - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: "standard" resources: requests: storage: 10Gi
Key Components Explained
serviceName: A headless Service that manages the network identity of the Pods.
volumeClaimTemplates: A template for PersistentVolumeClaims. Each Pod gets its own PVC.
Pod Identity: Each Pod gets a stable hostname: <statefulset-name>-<ordinal>.
Part 3: Stable Network Identity
Headless Service
A headless Service is required for StatefulSets to provide stable network identities:
apiVersion: v1 kind: Service metadata: name: postgres namespace: database spec: clusterIP: None # Headless Service selector: app: postgres ports: - port: 5432 targetPort: 5432
DNS Naming
With a headless Service, each Pod gets a unique DNS name:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
Examples:
postgres-0.postgres.database.svc.cluster.local postgres-1.postgres.database.svc.cluster.local postgres-2.postgres.database.svc.cluster.local
Why Stable Network Identity Matters
Database Replication: Primary and replica nodes need to know each other's addresses
Cluster Membership: Distributed systems require stable addresses for quorum
Service Discovery: Other services need to find specific instances
Part 4: Ordered Deployment and Scaling
Creation Order
When you create a StatefulSet with 3 replicas:
1. postgres-0 is created and becomes Running 2. postgres-1 is created and becomes Running 3. postgres-2 is created and becomes Running
Scaling Up
When you scale from 3 to 5 replicas:
1. postgres-3 is created 2. postgres-4 is created
Scaling Down
When you scale from 5 to 3 replicas:
1. postgres-4 is terminated 2. postgres-3 is terminated
This ordered scaling ensures that you don't lose quorum or disrupt replication.
Scaling Commands
# Scale a StatefulSet kubectl scale statefulset postgres --replicas=5 # Edit the StatefulSet kubectl edit statefulset postgres # Apply changes from YAML kubectl apply -f statefulset.yaml
Part 5: Persistent Storage
PersistentVolumeClaims
Each Pod in a StatefulSet gets its own PersistentVolumeClaim (PVC) based on the volumeClaimTemplates:
volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: "standard" resources: requests: storage: 10Gi
PVC Naming
PVCs are named according to the template:
data-postgres-0 data-postgres-1 data-postgres-2
Why Per-Pod Storage Matters
Data Isolation: Each database instance has its own data
Data Persistence: Data survives Pod restarts and rescheduling
Independent Scaling: Each Pod can have its own storage size
StatefulSet with Storage
apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: mysql replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: "root" ports: - containerPort: 3306 volumeMounts: - name: data mountPath: /var/lib/mysql volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: standard resources: requests: storage: 20Gi
Part 6: Update Strategies
RollingUpdate (Default)
StatefulSets support rolling updates, updating Pods in reverse ordinal order:
1. postgres-2 is updated 2. postgres-1 is updated 3. postgres-0 is updated
Partitioned RollingUpdate
You can specify a partition to control which Pods are updated:
spec: updateStrategy: type: RollingUpdate rollingUpdate: partition: 2
With partition 2, only Pods with ordinal >= 2 are updated:
1. postgres-2 is updated (if it exists) 2. postgres-3 is updated (if it exists) ...
Pods below the partition (0, 1) are not updated.
OnDelete
The OnDelete strategy only updates Pods when they are manually deleted:
spec: updateStrategy: type: OnDelete
When a Pod is deleted, it is recreated with the new template.
Part 7: StatefulSet vs Deployment
| Aspect | StatefulSet | Deployment |
|---|---|---|
| Pod naming | Ordered (web-0, web-1) | Random (web-abc123) |
| Stable network ID | Yes (stable hostname) | No (IP changes) |
| Persistent storage | Per-Pod PVC | Shared or ephemeral |
| Scaling | Ordered, sequential | Parallel, any order |
| Deletion | Ordered, reverse | Any order |
| Updates | Ordered (reverse) | Any order |
| Use case | Stateful apps | Stateless apps |
Part 8: Real-World StatefulSet Examples
Example 1: PostgreSQL with Primary-Replica
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:15 env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: postgres-secret key: password - name: POSTGRES_PRIMARY value: "postgres-0.postgres.database.svc.cluster.local" ports: - containerPort: 5432 volumeMounts: - name: data mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: standard resources: requests: storage: 100Gi
Example 2: Elasticsearch Cluster
apiVersion: apps/v1 kind: StatefulSet metadata: name: elasticsearch spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:8.10.0 env: - name: discovery.type value: "zen" - name: cluster.name value: "es-cluster" - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: "elasticsearch-0.elasticsearch,elasticsearch-1.elasticsearch,elasticsearch-2.elasticsearch" ports: - containerPort: 9200 - containerPort: 9300 volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: standard resources: requests: storage: 200Gi
Part 9: Common Commands
# Create a StatefulSet kubectl apply -f statefulset.yaml # List StatefulSets kubectl get statefulsets kubectl get sts # Describe a StatefulSet kubectl describe statefulset postgres # Scale a StatefulSet kubectl scale statefulset postgres --replicas=5 # Delete a StatefulSet (PVCs remain) kubectl delete statefulset postgres # Delete a StatefulSet and PVCs kubectl delete statefulset postgres --cascade=foreground # View Pods in a StatefulSet kubectl get pods -l app=postgres # View PVCs kubectl get pvc -l app=postgres
Summary
| Aspect | StatefulSet |
|---|---|
| Purpose | Manage stateful applications |
| Pod naming | Ordered (web-0, web-1, web-2) |
| Network identity | Stable hostnames |
| Storage | Per-Pod PersistentVolumeClaims |
| Scaling | Ordered, sequential |
| Updates | Ordered rolling updates |
| Use cases | Databases, message queues, distributed systems |
StatefulSets are the foundation for running stateful applications in Kubernetes. They provide stable network identities, persistent storage, and ordered operations essential for databases and distributed systems.
Learn More
Practice StatefulSets with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com
Comments
Post a Comment