Table of Contents
- Understanding Stateful vs. Stateless Applications
- Core Kubernetes Resources for Stateful Applications
- 2.1 StatefulSet: The Backbone of Stateful Apps
- 2.2 PersistentVolumes (PV) and PersistentVolumeClaims (PVC)
- 2.3 StorageClasses: Dynamic Provisioning
- 2.4 Headless Services: Stable Network Identities
- Deploying Stateful Applications: Step-by-Step Examples
- 3.1 Simple Stateful App: Nginx with Persistent Storage
- 3.2 Advanced Example: PostgreSQL Cluster with Replication
- Scaling Stateful Applications
- Updating and Rolling Back Stateful Applications
- Backups and Disaster Recovery
- Monitoring Stateful Applications
- Best Practices for Stateful Application Management
- References
1. Understanding Stateful vs. Stateless Applications
Before diving into Kubernetes specifics, it’s critical to distinguish between stateful and stateless applications:
Stateless Applications
- No persistent data: They process requests and return responses without storing data locally (e.g., nginx, REST APIs).
- Scalability: Easily scaled horizontally—adding more pods doesn’t require coordination.
- Deployment: Managed with Kubernetes
DeploymentorReplicaSet.
Stateful Applications
- Persistent data: Rely on stored data (e.g., user records in a database, messages in a queue).
- Stable identities: Pods need fixed network names and storage (e.g., a database primary must always be reachable at a known address).
- Ordered operations: Deployment, scaling, and updates must follow a sequence (e.g., start the primary database before replicas).
- Challenges: Data consistency, storage provisioning, and replication across pods.
Examples of stateful applications: PostgreSQL, MongoDB, Kafka, Redis (with persistence), and Elasticsearch.
2. Core Kubernetes Resources for Stateful Applications
Kubernetes provides specialized resources to manage stateful workloads. Let’s explore the key ones.
2.1 StatefulSet: The Backbone of Stateful Apps
A StatefulSet is Kubernetes’ primary resource for deploying stateful applications. Unlike Deployment, it guarantees:
- Stable pod identities: Pods are named sequentially (e.g.,
web-0,web-1) and retain their names even after rescheduling. - Stable network identities: Each pod gets a DNS hostname (e.g.,
web-0.nginx-service.default.svc.cluster.local) via a Headless Service. - Ordered deployment/termination: Pods are deployed from
0toN-1and terminated fromN-1to0. - Persistent storage: Uses
volumeClaimTemplatesto dynamically provision unique PersistentVolumes (PVs) for each pod.
Example StatefulSet YAML:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx" # Must match Headless Service name
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # Dynamically creates a PVC for each pod
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard" # Use your storage class
resources:
requests:
storage: 1Gi
Key Fields:
serviceName: Links to a Headless Service (see Section 2.4) for DNS resolution.volumeClaimTemplates: Defines a template for PVCs. Each pod gets a unique PVC (e.g.,www-web-0,www-web-1).
2.2 PersistentVolumes (PV) and PersistentVolumeClaims (PVC)
Stateful applications require persistent storage to retain data across pod restarts. Kubernetes uses PVs and PVCs to abstract storage:
- PersistentVolume (PV): A cluster-scoped resource that represents physical storage (e.g., AWS EBS, GCE PD, or local disk). Admins pre-provision PVs or use dynamic provisioning via StorageClasses.
- PersistentVolumeClaim (PVC): A namespaced request for storage by a pod. PVCs “claim” PVs that match their storage requirements (size, access mode).
PV Example (Static Provisioning):
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-example
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce # Only one pod can write; many can read
persistentVolumeReclaimPolicy: Retain # Keep PV after PVC is deleted
storageClassName: standard
hostPath: # For local testing; use cloud storage in production
path: /data/pv-example
PVC Example:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-example
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard
Access Modes:
ReadWriteOnce (RWO): PV mounted by one node (most common for databases).ReadOnlyMany (ROX): PV mounted read-only by multiple nodes (e.g., shared configs).ReadWriteMany (RWX): PV mounted read-write by multiple nodes (e.g., NFS, Ceph).
2.3 StorageClasses: Dynamic Provisioning
Manually creating PVs for every stateful pod is impractical. StorageClasses enable dynamic provisioning: when a PVC is created, Kubernetes automatically provisions a PV using the StorageClass’s provisioner (e.g., AWS EBS, Azure Disk).
Example StorageClass (AWS EBS):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-sc
provisioner: ebs.csi.aws.com # AWS EBS CSI driver
parameters:
type: gp3 # General-purpose SSD
fsType: ext4
reclaimPolicy: Delete # Delete PV when PVC is deleted
allowVolumeExpansion: true # Allow resizing PVCs
Key Parameters:
provisioner: The storage driver (e.g.,gce-pd.csi.storage.gke.iofor GCP,localfor local storage).reclaimPolicy:Delete(PV deleted with PVC),Retain(PV kept for manual cleanup), orRecycle(deprecated).
2.4 Headless Services: Stable Network Identities
StatefulSets rely on Headless Services to provide stable DNS names for pods. Unlike regular Services (which have a cluster IP), Headless Services (clusterIP: None) return DNS A records for each pod, enabling direct pod-to-pod communication.
Headless Service Example:
apiVersion: v1
kind: Service
metadata:
name: nginx # Must match StatefulSet's serviceName
spec:
ports:
- port: 80
name: web
clusterIP: None # Headless Service
selector:
app: nginx # Matches StatefulSet pod labels
DNS Naming: For a StatefulSet named web with 3 replicas and Headless Service nginx in namespace default, pods get hostnames:
web-0.nginx.default.svc.cluster.localweb-1.nginx.default.svc.cluster.localweb-2.nginx.default.svc.cluster.local
3. Deploying Stateful Applications: Step-by-Step Examples
Let’s deploy two stateful applications: a simple Nginx with persistent storage and a production-grade PostgreSQL cluster.
3.1 Simple Example: Nginx with Persistent Storage
This example deploys an Nginx StatefulSet where each pod has its own persistent storage.
Step 1: Create a Headless Service
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
Apply: kubectl apply -f nginx-service.yaml
Step 2: Create a StatefulSet with PVC Templates
# nginx-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "gp3-sc" # Use your StorageClass
resources:
requests:
storage: 1Gi
Apply: kubectl apply -f nginx-statefulset.yaml
Verify Deployment:
- Check pods:
kubectl get pods -l app=nginx(should showweb-0andweb-1). - Check PVCs:
kubectl get pvc(should showwww-web-0andwww-web-1). - Test storage persistence: Write data to
web-0, delete the pod, and verify data is retained after it restarts.
3.2 Advanced Example: PostgreSQL Cluster with Replication
For a production database, we’ll deploy PostgreSQL with a primary-replica setup using a StatefulSet, Headless Service, and persistent storage.
Prerequisites:
- A StorageClass (e.g.,
gp3-scfor AWS). - A Secret to store database credentials.
Step 1: Create a Secret for Credentials
# postgres-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secrets
type: Opaque
data:
postgres-password: cGFzc3dvcmQxMjM= # Base64-encoded "password123"
replication-password: cmVwbGljYXRpb25wYXNzd29yZA== # "replicationpassword"
Apply: kubectl apply -f postgres-secret.yaml
Step 2: Create a Headless Service
# postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
ports:
- port: 5432
name: postgres
clusterIP: None
selector:
app: postgres
Apply: kubectl apply -f postgres-service.yaml
Step 3: Deploy PostgreSQL StatefulSet
This StatefulSet uses a custom Docker image with replication setup (simplified for example purposes).
# postgres-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: "postgres"
replicas: 2 # 1 primary, 1 replica
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: bitnami/postgresql-repmgr:14 # Preconfigured with replication
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: postgres-password
- name: POSTGRESQL_REPLICATION_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: replication-password
- name: POSTGRESQL_MASTER_HOST
value: "postgres-0.postgres.default.svc.cluster.local" # Primary hostname
volumeMounts:
- name: data
mountPath: /bitnami/postgresql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "gp3-sc"
resources:
requests:
storage: 10Gi
Apply: kubectl apply -f postgres-statefulset.yaml
How It Works:
postgres-0initializes as the primary database.postgres-1connects topostgres-0(via the Headless Service hostname) and sets up replication.- Each pod has its own PV (via
volumeClaimTemplates) to store data.
4. Scaling Stateful Applications
Scaling stateful apps requires careful planning due to storage and data consistency constraints.
Horizontal Scaling with StatefulSet
Use kubectl scale statefulset <name> --replicas=<n> to scale. For example:
kubectl scale statefulset postgres --replicas=3 # Add a second replica
Key Considerations:
- Storage: New pods get new PVCs/PVs. Ensure your StorageClass can provision additional storage.
- Data partitioning: For databases, scale read workloads by adding replicas; use sharding for write scaling.
- Replication setup: New replicas may need to sync data from the primary (automate with operators like the PostgreSQL Operator).
5. Updating and Rolling Back Stateful Applications
StatefulSet updates (e.g., image version, resource limits) are managed via its updateStrategy.
Update Strategies
- RollingUpdate (Default): Updates pods in reverse order (highest ordinal to lowest). Each pod is terminated, updated, and restarted before moving to the next.
- OnDelete: Updates only when you manually delete a pod (useful for apps that require manual intervention).
Example: Update PostgreSQL Image
# Edit the StatefulSet to use a new image
kubectl set image statefulset/postgres postgres=bitnami/postgresql-repmgr:14.1 --record
Check Update Status:
kubectl rollout status statefulset/postgres
Rollback an Update:
If an update fails, roll back to a previous revision:
kubectl rollout undo statefulset/postgres --to-revision=1
6. Backups and Disaster Recovery
Stateful apps require robust backup strategies to prevent data loss.
Backup Options
-
PV Snapshots: Use Kubernetes
VolumeSnapshot(via CSI drivers) to take point-in-time snapshots of PVs.
Example SnapshotClass:apiVersion: snapshot.storage.k8s.io/v1 kind: VolumeSnapshotClass metadata: name: csi-ebs-snapshot-class driver: ebs.csi.aws.com deletionPolicy: Delete -
Tools Like Velero: Open-source tool for backing up Kubernetes resources and PVs to cloud storage (AWS S3, GCS).
Install Velero and run:velero backup create postgres-backup --include-resources=statefulsets,pods,pv,pvc --namespace default -
Database-Specific Backups: For databases, use logical backups (e.g.,
pg_dumpfor PostgreSQL) alongside PV snapshots for flexibility.
7. Monitoring Stateful Applications
Monitor stateful apps to track storage usage, replication health, and performance.
Key Metrics
- Storage: PV usage, PVC capacity, and growth rate (via
kubeletmetrics). - Pod Health: Restart count, readiness/liveness probe status.
- Application-Specific: Replication lag (PostgreSQL), partition count (Kafka), or cache hit ratio (Redis).
Tools
- Prometheus + Grafana: Scrape metrics from pods and kube-state-metrics. Use dashboards like PostgreSQL Grafana Dashboard.
- kube-state-metrics: Exposes StatefulSet, PV, and PVC metrics (e.g.,
kube_statefulset_status_replicas_ready).
8. Best Practices for Stateful Application Management
- Use Operators: For complex apps (PostgreSQL, Kafka), use Kubernetes Operators (e.g., Zalando Postgres Operator) to automate deployment, scaling, and backups.
- Secure Storage: Use
storageClassName: encryptedfor sensitive data (e.g., AWS EBS encrypted volumes). - Resource Limits: Define CPU/memory limits to prevent resource starvation:
resources: requests: cpu: 100m memory: 256Mi limits: cpu: 1000m memory: 1Gi - Test Updates/Scaling: Validate changes in staging before production. Use
kubectl debugto troubleshoot pods.
9. References
- Kubernetes StatefulSet Documentation
- Persistent Volumes and Claims
- Velero Backup Tool
- PostgreSQL Operator by Crunchy Data
- Kubernetes Storage Classes
This tutorial covered the essentials of managing stateful applications in Kubernetes, from core resources like StatefulSet and StorageClass to real-world deployment examples. With these tools and best practices, you can run stateful workloads reliably at scale.