StatefulSet Workloads - Details
← Back to StatefulSet Workloads Tutorial
This document provides comprehensive information about spec.deployment.kind: StatefulSet, including the immutability constraint, volumeClaimTemplates provisioning, podManagementPolicy semantics, and the deferred headless-Service support.
kind Field
spec.deployment.kind selects the workload controller:
| Value | Meaning |
|---|---|
Deployment (default) | The platform builds a Kubernetes Deployment. All previous lessons assume this. |
StatefulSet | The platform builds a Kubernetes StatefulSet. Unlocks volumeClaimTemplates and podManagementPolicy. |
The field is immutable after creation. A CEL rule on the CRD (self.kind == oldSelf.kind) rejects any attempt to change kind on an existing PlatformApplication. To switch workload types, delete the PlatformApplication and re-create with the new kind. PVCs created by the platform have no owner reference to the application, so they survive the deletion and are re-adopted on the next apply if names match.
Existing manifests with no kind field continue to deserialize as Deployment — the field default makes the change zero-migration for any application created before this feature shipped.
volumeClaimTemplates
spec.deployment.volumeClaimTemplates[] is a list of per-pod PVC templates. Kubernetes provisions one PVC per pod ordinal per template, so a replicas: 3 StatefulSet with one template yields three PVCs.
| Field | Required | Notes |
|---|---|---|
name | yes | PVC name prefix and volume mount name. PVC becomes <name>-<sts>-<ordinal> (e.g., data-demo-http-echo-0). |
mountPath | yes | Path inside the container where the volume mounts. |
size | yes | Requested storage (e.g., 10Gi). |
storageClassName | no | Provisioner. Defaults to the cluster default StorageClass. |
accessModes | no | Defaults to [ReadWriteOnce]. Per-pod PVCs are almost always RWO; multiple pods don't share a per-pod template. |
Every entry in volumeClaimTemplates automatically gets a matching volumeMount on the container — you don't add a volumeMounts[*] entry separately for the same name.
When to use volumeClaimTemplates vs volumeMounts
Both fields can coexist on a StatefulSet:
- Use
volumeClaimTemplatesfor storage that should be per-pod (data dirs for replicated databases, per-shard storage for sharded systems). - Use
volumeMounts[*]withsource.claimNamefor shared storage across all pods (RWX volumes,ReadOnlyManyconfiguration mounts). - Use
volumeMounts[*]with no source foremptyDir, ConfigMap, or Secret file mounts — same as on a Deployment.
volumeMounts[*].source.size (provisioning a new shared PVC) is unusual on a StatefulSet — if you need shared storage, prefer referencing a pre-created RWX PVC by claimName.
podManagementPolicy
Kubernetes StatefulSet.spec.podManagementPolicy controls pod creation/deletion ordering:
| Value | Behavior |
|---|---|
OrderedReady (Kubernetes default) | Pods are created in order (-0 before -1 before -2), each waiting for the previous to be Ready before starting. Termination is reverse order. |
Parallel | All pods are created and deleted in parallel. The StatefulSet still gives each pod its stable identity and PVC, but doesn't gate on readiness. |
Use OrderedReady for replicated systems with quorum requirements (consensus protocols, leader-elected databases) where bringing up multiple peers simultaneously could cause split-brain. Use Parallel for sharded systems where pods are independent and you want faster rollouts.
The CRD enforces the enum at admission time via a CEL rule:
!has(self.podManagementPolicy) || self.podManagementPolicy == 'OrderedReady' || self.podManagementPolicy == 'Parallel'
A typo (podManagementPolicy: ordered) gets rejected immediately, not silently dropped.
Field Restrictions
CEL rules enforce that volumeClaimTemplates and podManagementPolicy are StatefulSet-only:
self.kind == 'StatefulSet' || (!has(self.volumeClaimTemplates) || self.volumeClaimTemplates.size() == 0)
self.kind == 'StatefulSet' || !has(self.podManagementPolicy)
Setting either field on a Deployment (or omitting kind, which defaults to Deployment) is rejected at admission.
Singleton-Mode Override Skipped
The singleton-mode override from lesson 5 is explicitly gated on kind: Deployment. StatefulSets don't trigger it, even with RWO volumeClaimTemplates and replicas: 3 — that's the whole point of the workload kind.
The matching admission rejection (autoscaling + RWO PVC) is also gated on kind: Deployment. A StatefulSet with autoscaling and RWO volumeClaimTemplates passes admission. The platform does still emit a ScaledObject (KEDA) when autoscaling is configured.
Headless Service (Deferred)
StatefulSet.spec.serviceName is required by the Kubernetes API. The platform points it at the existing primary Service so the StatefulSet is well-formed. This does not give you per-pod DNS.
Per-pod DNS records (pod-N.<svc>.<ns>.svc.cluster.local) require the named Service to be headless (spec.clusterIP: None). The platform's primary Service is not headless — it's a normal ClusterIP Service used by ingress and cluster-internal traffic.
If you need stable per-pod DNS today (peer discovery for Cassandra, etcd, ZooKeeper, Postgres replicas with built-in DNS-based discovery), create a headless Service manually alongside the PlatformApplication:
apiVersion: v1
kind: Service
metadata:
name: demo-http-echo-headless
namespace: demo-http-echo
spec:
clusterIP: None
selector:
p6m.dev/app: demo-http-echo
ports:
- name: http
port: 8080
Then your application can resolve peers at demo-http-echo-0.demo-http-echo-headless.demo-http-echo.svc.cluster.local. Operator-managed headless Service support is tracked as a follow-up; until then, hand-rolled is the workaround.
PVC Retention
When the StatefulSet is scaled down or deleted, the platform does not delete the per-pod PVCs. This matches the Kubernetes default StatefulSet PVC retention policy and prevents accidental data loss.
Re-applying the PlatformApplication (or scaling back up) re-binds to the existing PVCs by name, so data persists across rollouts and re-creations.
To free the storage explicitly:
kubectl delete pvc -n <namespace> -l app.kubernetes.io/instance=<app-name>
If you need PVC deletion tied to StatefulSet lifecycle, edit the StatefulSet directly to set spec.persistentVolumeClaimRetentionPolicy — the platform doesn't surface this field on PlatformApplication yet.
Verification Procedures
Confirm the StatefulSet was created
kubectl get statefulset -n <namespace>
# Should show one StatefulSet with the application name
kubectl get deployment -n <namespace>
# Should show no Deployment for the same name
Confirm one PVC per pod ordinal
kubectl get pvc -n <namespace>
# data-<app>-0 Bound ...
# data-<app>-1 Bound ...
# data-<app>-2 Bound ...
Verify per-pod identity
for i in 0 1 2; do
kubectl exec -n <namespace> <app>-$i -- hostname
done
# <app>-0
# <app>-1
# <app>-2
Inspect podManagementPolicy
kubectl get statefulset -n <namespace> <app> -o jsonpath='{.spec.podManagementPolicy}'
# OrderedReady (or Parallel)
Related Documentation
- Kubernetes StatefulSets - StatefulSet concepts
- Pod Management Policies - OrderedReady vs Parallel
- Headless Services - Per-pod DNS
- StatefulSet PVC Retention - Lifecycle of per-pod PVCs
- Persistent Volumes - PV/PVC concepts