Skip to main content

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:

ValueMeaning
Deployment (default)The platform builds a Kubernetes Deployment. All previous lessons assume this.
StatefulSetThe 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.

FieldRequiredNotes
nameyesPVC name prefix and volume mount name. PVC becomes <name>-<sts>-<ordinal> (e.g., data-demo-http-echo-0).
mountPathyesPath inside the container where the volume mounts.
sizeyesRequested storage (e.g., 10Gi).
storageClassNamenoProvisioner. Defaults to the cluster default StorageClass.
accessModesnoDefaults 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 volumeClaimTemplates for storage that should be per-pod (data dirs for replicated databases, per-shard storage for sharded systems).
  • Use volumeMounts[*] with source.claimName for shared storage across all pods (RWX volumes, ReadOnlyMany configuration mounts).
  • Use volumeMounts[*] with no source for emptyDir, 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:

ValueBehavior
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.
ParallelAll 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)