Kubernetes StatefulSets: wanneer pod-identiteit en persistente opslag ertoe doen

Een Deployment behandelt elke pod als inwisselbaar. Een StatefulSet doet het tegenovergestelde: het kent elke pod een vaste naam, een vaste hostname en een eigen persistent volume toe. Dat onderscheid maakt het mogelijk om databases, message brokers en consensus-systemen op Kubernetes te draaien. Dit artikel legt uit welke garanties een StatefulSet biedt, wanneer je ze nodig hebt, en wanneer niet.

Wat een StatefulSet is

Een StatefulSet is een workload-controller (apiVersion: apps/v1, kind: StatefulSet) die pods beheert met een vaste identiteit. Elke pod krijgt een ordinaal getal (0, 1, 2, ...), een stabiele hostname afgeleid van dat getal, en optioneel een eigen PersistentVolumeClaim. Die identiteiten overleven herstarts, herscheduling en node-migraties.

De vier garanties:

  • Stabiele netwerkidentiteit. Pod postgres-0 is altijd postgres-0, ongeacht op welke node die draait.
  • Stabiele persistente opslag. Elke pod krijgt een eigen PVC, die automatisch opnieuw wordt gekoppeld na herscheduling.
  • Geordende deployment en schaling. Pods worden sequentieel aangemaakt (0, dan 1, dan 2) en in omgekeerde volgorde verwijderd.
  • Geordende rolling updates. Updates verlopen van het hoogste ordinaal naar beneden, zodat replica's voor de primary worden bijgewerkt.

Elke workload waarbij de identiteit van individuele instances ertoe doet (niet alleen het aantal) is een kandidaat voor een StatefulSet.

StatefulSet vs Deployment

Feature StatefulSet Deployment
Pod-naamgeving Ordinaal: kafka-0, kafka-1 Random hash: nginx-7df9f9cdd8-xkj2b
Opslag Per-pod PVC via volumeClaimTemplates Gedeelde PVC of geen
DNS Per-pod FQDN via headless Service Geen per-pod DNS
Schaalvolgorde Sequentieel; omgekeerde verwijdering Parallel, ongeordend
Pod-inwisselbaarheid Niet inwisselbaar; elke pod kan een eigen rol hebben Volledig inwisselbaar

Gebruik een Deployment als pods identiek zijn, geen individuele state dragen en elke replica elk verzoek kan afhandelen. REST API-servers, frontend-containers, queue consumers.

Gebruik een StatefulSet als pods een stabiele identiteit nodig hebben voor peer-communicatie, leader election of replicatie. PostgreSQL primary/replica-clusters, Kafka-brokers, ZooKeeper-ensembles, etcd-members, Elasticsearch-nodes, Redis Cluster.

Een StatefulSet inzetten voor een stateless workload voegt overhead toe (sequentieel schalen, verplichte headless Service) zonder voordeel. Als je pods inwisselbaar zijn, is een Deployment simpeler en schaalt die sneller.

Stabiele pod-identiteit

Voor een StatefulSet genaamd web met replicas: 3 maakt Kubernetes web-0, web-1 en web-2 aan. De hostname van elke pod komt overeen met zijn naam. Als web-1 naar een andere node wordt verplaatst, blijft die gewoon web-1.

Dat is belangrijk omdat gedistribueerde systemen afhankelijk zijn van bekende member-adressen. Kafka gebruikt het broker-ID voor partitie-toewijzing en replicatie; een broker kan niet anoniem zijn. etcd-members bootstrappen door alle peer-adressen op te sommen. PostgreSQL streaming replication verbindt met een bekende primary-hostname. In elk geval breekt een wisselende hostname de applicatie.

Vanaf Kubernetes v1.31 kun je een aangepast start-ordinaal instellen via .spec.ordinals.start, waardoor de reeks verschuift naar bijvoorbeeld 5, 6, 7 in plaats van 0, 1, 2.

De headless Service-vereiste

Een standaard Service met een ClusterIP load-balancet over alle achterliggende pods via een enkel VIP. Voor een database replica set klopt dat niet: je kunt writes niet naar "een willekeurige replica" sturen. Je moet de primary op naam kunnen bereiken.

Een headless Service (clusterIP: None) slaat het VIP over en maakt in plaats daarvan individuele DNS-records per pod aan. Voor een StatefulSet genaamd postgres met een headless Service genaamd postgres in namespace default resolvet CoreDNS:

  • postgres-0.postgres.default.svc.cluster.local
  • postgres-1.postgres.default.svc.cluster.local
  • postgres-2.postgres.default.svc.cluster.local

Het patroon is $(pod-name).$(service-name).$(namespace).svc.cluster.local, zoals gedocumenteerd in DNS for Services and Pods.

Een veelgemaakte fout: de StatefulSet-controller maakt de headless Service niet automatisch aan. Die moet bestaan voor je de StatefulSet aanmaakt, en .spec.serviceName moet ernaar verwijzen. Ontbreekt de Service, dan draaien de pods wel maar krijgen ze geen stabiele DNS-entries, en faalt peer-to-peer communicatie zonder duidelijke foutmelding.

volumeClaimTemplates: opslag per pod

Bij een Deployment die een PVC deelt tussen alle pod-replica's, lezen en schrijven alle pods naar dezelfde data. Twee PostgreSQL-instances die naar dezelfde data directory schrijven, zouden die direct corrumperen.

De volumeClaimTemplates van een StatefulSet provisioneren een individuele PersistentVolumeClaim per pod. Voor een StatefulSet genaamd postgres met een template genaamd data en 3 replica's maakt Kubernetes PVC's data-postgres-0, data-postgres-1 en data-postgres-2 aan. Elke pod mount alleen zijn eigen PVC.

Wordt een pod verwijderd en opnieuw aangemaakt (node-failure, rolling update), dan maakt de controller die opnieuw aan met hetzelfde ordinaal en koppelt dezelfde PVC. Data blijft behouden.

Downscalen verwijdert PVC's standaard niet. Dat is opzettelijk: dataveiligheid gaat voor automatische opruiming. Terugschalen koppelt de bestaande PVC opnieuw. Vanaf Kubernetes v1.27 (beta), en stable in v1.32, regelt een persistentVolumeClaimRetentionPolicy-veld dit gedrag:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete   # verwijder PVC's bij het verwijderen van de StatefulSet
    whenScaled: Retain    # behoud PVC's bij downscalen (standaard)

Voor access modes raadt de officiele documentatie ReadWriteOncePod aan boven ReadWriteOnce. De oudere modus staat toe dat een volume door meerdere pods op dezelfde node wordt gemount, wat bij databases een datacorruptie-risico is.

Geordende deployment en schaling

Met de standaard podManagementPolicy: OrderedReady maakt de controller pods sequentieel aan: postgres-1 start pas als postgres-0 Running en Ready is. Downscalen verwijdert in omgekeerde volgorde: eerst postgres-2, dan postgres-1, dan postgres-0.

Dat beschermt initialisatie-afhankelijke systemen. Een ZooKeeper-ensemblelid heeft zijn leader nodig voordat het kan toetreden. Een database-replica heeft de primary nodig voor replicatie.

De keerzijde: als een pod in een crash loop terechtkomt of zijn readiness probe faalt, blokkeert de controller volledig. Er worden geen volgende pods aangemaakt, verwijderd of bijgewerkt totdat de vastzittende pod is gefixt of handmatig verwijderd. Dit is het meest voorkomende operationele struikelblok bij StatefulSets in productie.

Voor applicaties die hun eigen initialisatie afhandelen (via init containers of leader election), stel je podManagementPolicy: Parallel in om alle pods tegelijk aan te maken en te verwijderen. De identiteits- en opslaggaranties blijven overeind; alleen de volgorde vervalt.

Update-strategieen

RollingUpdate (standaard): wanneer je .spec.template wijzigt, werkt de controller pods bij in omgekeerde ordinaalvolgorde. postgres-2 wordt als eerste bijgewerkt; postgres-0 (meestal de primary) als laatste, wat downtime minimaliseert in leader-based systemen.

Het partition-veld maakt gefaseerde rollouts mogelijk. Met partition: 2 en 3 replica's krijgt alleen postgres-2 de nieuwe template. Pods 0 en 1 blijven op de oude versie. Verlaag de partition-waarde om het bereik uit te breiden: een canary deployment ingebouwd in de controller. maxUnavailable (beschikbaar sinds v1.24) bepaalt hoeveel pods tegelijk down mogen zijn tijdens de update.

OnDelete: de controller raakt pods niet automatisch aan. Updates gebeuren alleen als je handmatig een pod verwijdert. De vervanging pikt dan de nieuwe template op. Dit zie je vaak bij operator-beheerde workloads waar de operator zijn eigen update-volgorde orkestreert.

Databases in Kubernetes: afwegingen

Dit is de vraag achter de meeste interesse in StatefulSets. Het korte antwoord: het kan, maar het vraagt meer operationele volwassenheid dan stateless workloads draaien.

Vóór: een uniform beheerplatform (dezelfde RBAC, monitoring, GitOps-pipelines), kostenbesparing ten opzichte van cloud DBaaS, en multi-cloud portabiliteit. Het ecosysteem is volwassen geworden: CloudNativePG is een CNCF-project, Strimzi beheert productie-Kafka op schaal, en CrunchyData PGO is bewezen in productie sinds 2017.

Tegen: Kubernetes is ontworpen rond vluchtige workloads, en pods kunnen op elk moment worden ge-evict. Network-attached storage voegt latency toe vergeleken met lokale SSD's. Troubleshooting vereist dubbele expertise in zowel Kubernetes-internals als database-internals. Volume-snapshots zijn niet crash-consistent voor databases, dus database-native backup tools blijven noodzakelijk.

Voor productie zijn rauwe StatefulSets meestal niet genoeg. Ze bieden de bouwstenen (identiteit, opslag, volgorde) maar niet de domein-specifieke logica (automatische failover, backup-scheduling, replica-promotie). Dat is wat een Kubernetes operator toevoegt. Voor PostgreSQL is CloudNativePG de meest gebruikte operator anno 2025. Sommige operators, zoals CloudNativePG, vermijden bewust rauwe StatefulSets en implementeren eigen pod-management om beperkingen als inflexibele volume-resize te omzeilen.

Een praktische verdeling: gebruik rauwe StatefulSets (via Helm-charts zoals Bitnami) voor development en staging. Gebruik een mature operator voor productie. Overweeg managed cloud databases als je team geen diepe expertise heeft in zowel Kubernetes als de specifieke database.

Wat StatefulSets NIET zijn

  • Geen database-operator. Een StatefulSet geeft je stabiele identiteit en opslag. Het begrijpt niks van replicatie-lag, quorum, backup-schema's of failover. Die logica leeft in de operatorlaag erboven.
  • Niet nodig voor alle persistente opslag. Een Deployment met een ReadWriteMany PVC werkt prima als alle pods dezelfde data delen (een CMS media-directory, een gedeeld configuratiebestand). StatefulSets zijn voor workloads die per-pod opslaginsolatie nodig hebben.
  • Geen DaemonSet. Een DaemonSet plaatst precies een pod per node. Een StatefulSet plaatst een vast aantal pods over het cluster. Verward omdat beide "een per ding" opleveren? Het "ding" verschilt: DaemonSet is per-node, StatefulSet is per-ordinaal.
  • Geen vervanging voor applicatie-niveau HA. Kubernetes kan een gefaalde pod herstarten. Het kan geen database-replica promoveren tot primary, Kafka-partities herbalanceren, of een etcd-member opnieuw aan zijn cluster toevoegen. Applicatie-niveau health management is iets aparts.

Verder lezen

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.