Skip to main content

Kubernetes Ep 9: StatefulSets & Stateful Database Deployments

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
kubernetes - This article is part of a series.
Part 9: This Article
While Deployments are designed for interchangeable, stateless application replicas, stateful workloads like PostgreSQL clusters, Redis Sentinels, Kafka brokers, and Elasticsearch nodes require stable network identities, dedicated persistent volumes per replica, and strict ordered deployment & scaling. This is where StatefulSets shine.

TL;DR (Quick Summary)
#

  • StatefulSet vs Deployment:
    • Deployment: Pods get random hashes (app-75b69bd654-2x9lq), share storage claims, and start/stop in parallel.
    • StatefulSet: Pods get persistent ordinal names (db-0, db-1, db-2), unique DNS hostnames, separate PVs via volumeClaimTemplates, and start/stop sequentially.
  • Headless Service (clusterIP: None): Disables load balancing VIP so DNS queries directly resolve individual Pod IPs for direct node-to-node replication.

1. StatefulSet Architecture & Ordinal Indexing
#


graph TD
    subgraph StatefulSet["StatefulSet: redis-cluster (replicas: 3)"]
        P0["Pod: redis-cluster-0
DNS: redis-cluster-0.redis-service"] P1["Pod: redis-cluster-1
DNS: redis-cluster-1.redis-service"] P2["Pod: redis-cluster-2
DNS: redis-cluster-2.redis-service"] end subgraph VolumeClaimTemplates["volumeClaimTemplates"] PVC0["PVC: data-redis-cluster-0"] -->|Binds| PV0["PV 0 (10Gi)"] PVC1["PVC: data-redis-cluster-1"] -->|Binds| PV1["PV 1 (10Gi)"] PVC2["PVC: data-redis-cluster-2"] -->|Binds| PV2["PV 2 (10Gi)"] end P0 -.-> PVC0 P1 -.-> PVC1 P2 -.-> PVC2

2. Headless Service Manifest (clusterIP: None)
#

A Headless Service does not assign a single Virtual IP. Instead, it creates A-records in CoreDNS for each individual Pod endpoint matching the selector.

Create headless-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: redis-headless
  namespace: default
  labels:
    app: redis
spec:
  clusterIP: None # Headless Service
  selector:
    app: redis
  ports:
  - port: 6379
    name: redis-port

3. Complete StatefulSet Manifest
#

Create statefulset.yaml:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: default
spec:
  serviceName: "redis-headless"
  replicas: 3
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7.0-alpine
        ports:
        - containerPort: 6379
          name: redis-port
        volumeMounts:
        - name: redis-data
          mountPath: /data
  # Dynamically creates a PVC for EACH ordinal Pod replica!
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

Apply both manifests:

kubectl apply -f headless-service.yaml
kubectl apply -f statefulset.yaml

4. Observing Sequential Deployment & DNS Names
#

Watch the Pod creation process in real-time:

kubectl get pods -l app=redis --watch

Sequential Output:

NAME              READY   STATUS              RESTARTS   AGE
redis-cluster-0   0/1     ContainerCreating   0          2s
redis-cluster-0   1/1     Running             0          8s
redis-cluster-1   0/1     ContainerCreating   0          1s
redis-cluster-1   1/1     Running             0          7s
redis-cluster-2   0/1     ContainerCreating   0          1s
redis-cluster-2   1/1     Running             0          6s

Notice how redis-cluster-1 was NOT started until redis-cluster-0 became fully Ready!

Checking Individual DNS Hostnames
#

Each Pod gets a deterministic, predictable network address:

  • redis-cluster-0.redis-headless.default.svc.cluster.local
  • redis-cluster-1.redis-headless.default.svc.cluster.local
  • redis-cluster-2.redis-headless.default.svc.cluster.local

List dynamically created PVCs:

kubectl get pvc -l app=redis
NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES
redis-data-redis-cluster-0   Bound    pvc-11111111-2222-3333-4444-555555555555   1Gi        RWO
redis-data-redis-cluster-1   Bound    pvc-66666666-7777-8888-9999-000000000000   1Gi        RWO
redis-data-redis-cluster-2   Bound    pvc-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee   1Gi        RWO

5. Summary & Next Steps
#

StatefulSets guarantee ordering, persistent identities, and dedicated storage volumes per replica for database systems.

In Episode 10: DaemonSets & Cluster Node Agents, we will shift gears to node-level background workloads, learning how DaemonSets deploy log collectors and metric agents automatically across every worker node in your cluster!

kubernetes - This article is part of a series.
Part 9: This Article