Stateful workloads in Kubernetes need Pod identity that survives replacement more predictably than a Deployment can provide. A StatefulSet creates Pods with ordinal names such as web-0 and web-1, which fits applications that depend on stable peer names or one storage claim per replica.
A StatefulSet needs a matching Pod selector and a Service name that points at a headless Service. The Service has no cluster IP; it gives the controller a DNS domain for the StatefulSet Pods while each Pod keeps its own ordinal hostname.
The lab manifest uses a separate namespace, two busybox Pods, and a volumeClaimTemplates entry that creates one 1 GiB claim per Pod. Replace standard with a StorageClass from your cluster, and keep the cleanup step for test namespaces only because deleting the namespace removes the claims and dynamically provisioned volumes.
$ kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 1s
The manifest uses standard. If your cluster uses another class name, change storageClassName before applying the manifest.
$ kubectl create namespace app-stateful namespace/app-stateful created
Use the application namespace instead of app-stateful when creating a real StatefulSet.
Related: How to create a Kubernetes namespace
apiVersion: v1 kind: Service metadata: name: web spec: clusterIP: None selector: app: web ports: - name: http port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: web replicas: 2 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: busybox:1.36 command: - sh - -c - | if [ ! -f /data/identity.txt ]; then hostname > /data/identity.txt; fi sleep 3600 ports: - containerPort: 80 name: http volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data spec: accessModes: - ReadWriteOnce storageClassName: standard resources: requests: storage: 1Gi
serviceName must match the headless Service name, and selector.matchLabels must match the Pod template labels.
$ kubectl apply -f web-statefulset.yaml --namespace app-stateful service/web created statefulset.apps/web created
$ kubectl rollout status statefulset/web --namespace app-stateful --timeout=180s Waiting for statefulset spec update to be observed... Waiting for 2 pods to be ready... Waiting for 1 pods to be ready... partitioned roll out complete: 2 new pods have been updated...
$ kubectl get pods --namespace app-stateful -l app=web NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 25s web-1 1/1 Running 0 5s
The ordinal suffixes identify each replica. web-0 and web-1 keep those names when Kubernetes replaces the Pods.
$ kubectl get pvc --namespace app-stateful NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE data-web-0 Bound pvc-5cb05dd1-a6b9-485f-a29e-69a0b741ba2e 1Gi RWO standard <unset> 25s data-web-1 Bound pvc-09ee7941-e182-49e6-8b1b-1a7d2936c02a 1Gi RWO standard <unset> 5s
The claim name combines the volumeClaimTemplates name and the Pod ordinal.
$ kubectl exec web-0 --namespace app-stateful -- cat /data/identity.txt web-0
$ kubectl delete pod web-0 --namespace app-stateful pod "web-0" deleted from app-stateful namespace
Do this only in a test namespace or during a controlled maintenance window. Stateful applications may need quorum, leader, or replica-health checks before one Pod is removed.
$ kubectl wait --for=condition=Ready pod/web-0 --namespace app-stateful --timeout=120s pod/web-0 condition met
$ kubectl exec web-0 --namespace app-stateful -- cat /data/identity.txt web-0
The recreated Pod has the same ordinal name and remounts the claim associated with data-web-0.
$ kubectl delete namespace app-stateful namespace "app-stateful" deleted
Skip this command for a real application namespace. Deleting the namespace removes the StatefulSet, Pods, Services, claims, and dynamically provisioned volumes inside it.