Kubernetes Notes - Statefulsets

Architecture Diagram Kubernetes has several controllers to manage workloads. While Deployments are ideal for stateless applications, many real‑world use cases require stateful behavior — applications that store data, maintain identity, and rely on stable storage.

This is where StatefulSets come in. They provide stable identity, stable storage, and ordered deployment for stateful applications.

Table of Contents

Why StatefulSets?

Stateless apps don’t care which instance serves a request. But stateful workloads often require:

  • Persistent storage
  • Stable network identity
  • Ordered startup and teardown
  • Consistent naming

Each Pod gets a sticky identity — persistent hostname and name. That’s why this is usually used for Database, you can set the pods into replication setup.

Pods identity survives restarts and rescheduling. Each Pod gets its own PersistentVolumeClaim (PVC) for storage. When a Pod is deleted or recreated, its associated storage remains intact.

Examples of Stateful Systems

  • Databases (PostgreSQL, MySQL)
  • Message brokers (Kafka, RabbitMQ)
  • Key‑value stores (Redis, Etcd)
  • Search Engines

Create Statefulset

statefulset.yaml

 1apiVersion: apps/v1
 2kind: StatefulSet
 3metadata:
 4  name: postgresql
 5spec:
 6  serviceName: "postgresql"
 7  replicas: 3
 8  selector:
 9    matchLabels:
10      app: postgresql
11  template:
12    metadata:
13      labels:
14        app: postgresql
15    spec:
16      containers:
17      - name: postgres
18        image: postgres:15
19        ports:
20        - containerPort: 5432
21          name: postgres
22        env:
23        - name: POSTGRES_PASSWORD
24          value: "mypassword"
25        volumeMounts:
26        - name: pgdata
27          mountPath: /var/lib/postgresql/data
28  volumeClaimTemplates:
29  - metadata:
30      name: pgdata
31    spec:
32      accessModes: ["ReadWriteOnce"]
33      resources:
34        requests:
35          storage: 1Gi

Check statefulset.

1kubectl get statefulset -n demo
2NAME         READY   AGE
3postgresql   3/3     31m

Check the pods created by statefulset.

1kubectl get pods -n demo
2NAME           READY   STATUS    RESTARTS   AGE
3postgresql-0   1/1     Running   0          31m
4postgresql-1   1/1     Running   0          31m
5postgresql-2   1/1     Running   0          30m

This verify that pod has controlled identity (pod names are stable), this allows applications to know exactly which instance they’re talking to.

Service

Headless service is used for statefulset. Kubernetes does not allocate a single IP address, and kube-proxy does not handle the traffic - direct pod access.

svc-statefulset.yaml

 1apiVersion: v1
 2kind: Service
 3metadata:
 4  name: postgresql
 5spec:
 6  clusterIP: None
 7  selector:
 8    app: postgresql
 9  ports:
10    - port: 5432
11      targetPort: 5432

Create the service.

1kubectl apply -f svc-statefulset.yaml -n demo

The pods will get stable DNS names like:

1postgresql-0.postgresql
2postgresql-1.postgresql
3postgresql-2.postgresql

Full DNS inside the cluster:

1postgresql-0.postgresql.demo.svc.cluster.local
2postgresql-1.postgresql.demo.svc.cluster.local
3postgresql-2.postgresql.demo.svc.cluster.local

Verify this using the busybox pod in the universe namespace.

 1kubectl exec pod/busybox -n universe -- nslookup postgresql-0.postgresql.demo.svc.cluster.local
 2Server:		10.43.0.10
 3Address:	10.43.0.10:53
 4
 5
 6Name:	postgresql-0.postgresql.demo.svc.cluster.local
 7Address: 172.16.235.46
 8
 9kubectl exec pod/busybox -n universe -- nslookup postgresql-1.postgresql.demo.svc.cluster.local
10Server:		10.43.0.10
11Address:	10.43.0.10:53
12
13
14Name:	postgresql-1.postgresql.demo.svc.cluster.local
15Address: 172.16.59.219
16
17kubectl exec pod/busybox -n universe -- nslookup postgresql-2.postgresql.demo.svc.cluster.local
18Server:		10.43.0.10
19Address:	10.43.0.10:53
20
21Name:	postgresql-2.postgresql.demo.svc.cluster.local
22Address: 172.16.241.92

Volume

volumeClaimTemplates allows each pod replica to automatically provision its own unique PersistentVolumeClaim (PVC).

1  volumeClaimTemplates:
2  - metadata:
3      name: pgdata
4    spec:
5      accessModes: ["ReadWriteOnce"]
6      resources:
7        requests:
8          storage: 1Gi

Verify.

1kubectl get pvc -n demo
2NAME                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
3pgdata-postgresql-0   Bound    pvc-e0ef97d6-0f66-496f-905b-7ad044c400a8   1Gi        RWO            local-path     <unset>                 3h37m
4pgdata-postgresql-1   Bound    pvc-109e4724-e3eb-40f7-8352-11077972ad39   1Gi        RWO            local-path     <unset>                 3h37m
5pgdata-postgresql-2   Bound    pvc-3b1664c1-e048-4458-bdf9-5d2be8eae5e8   1Gi        RWO            local-path     <unset>                 3h36m

Storage is persistent per pod. Even if a pod dies, its PVC remains.

Ordered Creation & Scaling

When applying statefulset, Kubernetes will create Pods in order.

1app-0 ⇒ app-1 ⇒ app-2

If you scale up.

1kubectl scale statefulset app --replicas=5

Pods are added one at a time from 3 to 4 to 5.

Ordered Deletion

Deleting or scaling down also happens one at a time in reverse order:

1app-4 ⇒ app-3 ⇒ app-2

This controlled sequence helps prevent data corruption in distributed systems.