Intro
Running replicated stateful applications on Kubernetes is one of the hardest operational challenges in container orchestration. Unlike stateless workloads, stateful apps such as databases, message queues, and distributed caches require stable network identities, durable storage, and careful coordination between replicas. A single configuration mistake can lead to split-brain scenarios, silent data loss, or cascading failures that take down an entire service.
This guide covers the most common configuration mistakes in Kubernetes StatefulSets, how to validate changes safely, and what to do when things go wrong. You will learn how to inspect your current environment, apply rolling updates with canary partitions, verify replication health, and recover from common failure modes. By the end, you will have a repeatable process to deploy and operate replicated stateful applications with confidence.
Version and Environment Inventory
Before changing any configuration, document your current environment. Knowing your Kubernetes version, storage classes, and application topology prevents mismatches that cause hidden failures later. Run the following commands and record the output in a runbook or version-controlled document:
# Check Kubernetes client and server versions
kubectl version --short
# Check available storage classes
kubectl get storageclass
# List StatefulSets in the current namespace
kubectl get statefulsets
Example output:
Client Version: v1.28.0
Server Version: v1.28.0
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) kubernetes.io/gce-pd Delete Immediate false 1d
NAME READY AGE
my-statefulset 3/3 2h
Record details such as the StatefulSet's service name, pod management policy, and update strategy. These fields are set in the StatefulSet spec and directly affect rolling updates, pod replacement order, and network identity. Inspect them with:
kubectl get statefulset my-statefulset -o yaml | grep -A5 -E 'serviceName|podManagementPolicy|updateStrategy'
Example output:
serviceName: my-service
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
Prerequisites for safe changes include:
- A dedicated test namespace with the same storage class and network policies.
- Backup procedures for persistent volumes, tested regularly.
- Access to application logs and metrics (e.g., Prometheus, Loki).
- A rollback plan: store previous manifests in Git, or use Helm with release history.
Safe Configuration Path
Configuration mistakes often stem from misunderstanding how StatefulSets handle updates and identity. For replicated applications, common mistakes include:
- Using the wrong
serviceName, breaking stable DNS names likemyapp-0.my-service. - Misconfiguring
podManagementPolicy, causing unexpected parallel operations when ordered startup is required. - Using
RollingUpdatewith incorrectpartitionvalues, updating too many replicas at once and risking quorum loss. - Ignoring
volumeClaimTemplates, leading to shared storage or no persistence for stateful data. - Setting resource limits too low, causing
OOMKilledduring startup or normal operation.
To avoid these pitfalls, adopt a scoped implementation path. Instead of updating all replicas simultaneously, use a rolling update with a partition to create a canary. For example, with three replicas (myapp-0, myapp-1, myapp-2), set partition: 2. This means only pods with an ordinal greater than or equal to 2 (i.e., myapp-2) will be updated. The other two pods remain on the previous version, allowing you to validate the change on a single replica before rolling it out further.
Here is a complete StatefulSet manifest with safe defaults:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: my-statefulset
spec:
serviceName: "my-service"
replicas: 3
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # Only update pod with ordinal >= 2 (myapp-2)
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: standard
resources:
requests:
storage: 1Gi
template:
spec:
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
Apply changes incrementally and verify each step. Use kubectl apply with the modified manifest:
kubectl apply -f updated-statefulset.yaml
After applying, monitor the rollout status:
kubectl rollout status statefulset/my-statefulset
Expected output during a canary update:
Waiting for 1 pods to be ready...
Waiting for 1 pod to be ready...
statefulset rolling update complete 1 pods at revision my-statefulset-7d9f9d6f7
If the rollout stalls, inspect pod events and logs before proceeding. Do not force the rollout; investigate the cause first.
Verification and Diagnostics
After applying configuration changes, verify the health of each replica. For replicated stateful applications, consistency and data integrity are critical. Use the following commands and checks.
Check pod status and readiness:
kubectl get pods -l app=myapp -o wide
Expected output:
NAME READY STATUS RESTARTS AGE IP NODE
myapp-0 1/1 Running 0 10m 10.0.0.1 node1
myapp-1 1/1 Running 0 10m 10.0.0.2 node2
myapp-2 1/1 Running 0 10m 10.0.0.3 node3
Check PVC bound to each pod:
kubectl get pvc -l app=myapp
Expected output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-myapp-0 Bound pvc-0d1f7c42-1a5a-4f4d-9a5b-0f0a5e5f0a5a 1Gi RWO standard 10m
data-myapp-1 Bound pvc-5d7e3e1f-8b3c-4c1d-8d0a-2b1e2f1d2e1f 1Gi RWO standard 10m
data-myapp-2 Bound pvc-9f8e7c6d-2c3b-4d5e-6f7a-3c4d5e6f7a8b 1Gi RWO standard 10m
Check logs for replication health:
kubectl logs myapp-0
Example output for a database replica:
2024-01-01T12:00:00Z [Replication] INFO: Successfully connected to primary
2024-01-01T12:00:01Z [Replication] INFO: Replication lag: 0 ms
Diagnostic checks to perform:
- Ensure each pod has a unique persistent volume. Using
ReadWriteOnceprevents multiple pods from mounting the same volume. - Verify replication status via application metrics or logs. For databases, check
SHOW SLAVE STATUSor equivalent. - Run a write test on one replica and read from another, if the application supports it. For example, insert a row in
myapp-0and query it frommyapp-1to confirm replication. - Use
kubectl execto run application-specific health commands. For a PostgreSQL StatefulSet, you might run:
kubectl exec myapp-0 -- psql -c "SELECT 1"
If any check fails, pause further rollout and investigate.
Failure Modes and Recovery
Despite precautions, failures occur. Common failure modes include:
- Pod stuck in
Pendingdue to insufficient resources, storage class issues, or node affinity constraints. - Pod crash loop caused by misconfiguration, missing dependencies, or incompatible image version.
- Data inconsistency after an unclean shutdown, such as a node failure without proper fsync.
- Split-brain when network partitions isolate replicas and both sides attempt to become primary.
Recovery steps:
1. Diagnose the failure. If a rollout fails, check events and pod details:
kubectl describe pod myapp-1
Look for messages like Failed to attach volume, Insufficient cpu, or CrashLoopBackOff. Also check logs:
kubectl logs myapp-1 --previous
2. Rollback to previous version. If you have the previous manifest in Git or used Helm, execute:
kubectl rollout undo statefulset/my-statefulset
This reverts to the previous revision. Verify the rollback completes:
kubectl rollout status statefulset/my-statefulset
If using Helm, you can rollback a release:
helm rollback my-release 2
3. Restore from backup for data issues. Ensure backups are tested regularly. For cloud volumes, use snapshots; for databases, use logical dumps (e.g., pg_dump).
4. Replace a failed pod if necessary. If a pod is stuck or corrupted, you can delete it; the StatefulSet controller will recreate it with the same identity and PVC. Use this cautiously:
kubectl delete pod myapp-1
The pod will be recreated, but its PVC remains. Note: this does not roll back configuration; it only reschedules the pod.
Always document the incident and the recovery steps for future reference. Regularly test rollback procedures in a non-production environment to ensure they work when needed.
Operations Checklist
Use this checklist before and after making configuration changes to replicated stateful applications. It enforces consistency and reduces mistakes.
| Step | Description | Command / Action |
|---|---|---|
| 1 | Record baseline versions | kubectl version --short |
| 2 | List StatefulSet details | kubectl get statefulset -o yaml |
| 3 | Backup persistent volumes | Use storage provider snapshot or tool (e.g., Velero) |
| 4 | Apply change with partition for canary | kubectl apply -f updated-statefulset.yaml with partition: 2 |
| 5 | Monitor rollout status | kubectl rollout status statefulset/my-statefulset |
| 6 | Verify pod readiness | kubectl get pods -l app=myapp |
| 7 | Check application logs for replication health | kubectl logs myapp-0 |
| 8 | Run application-specific tests | For database, run query test (e.g., write to primary, read from replica) |
| 9 | If failure, rollback | kubectl rollout undo statefulset/my-statefulset |
| 10 | Document outcome and update runbook | Update internal wiki or Git repo |
Conclusion
Running replicated stateful applications on Kubernetes demands careful configuration and disciplined operations. By understanding common mistakes, using scoped updates with partitions, verifying with diagnostics, and having a rollback plan, you can maintain high availability and data integrity. Start with a narrow pilot, measure outcomes, and iterate. The commands and checklist provided give you a solid foundation for operating stateful workloads reliably. Remember that stateful applications require extra care: every change should be treated as potentially disruptive, and every failure should be analyzed to prevent recurrence.