StatefulSets and volumeClaimTemplates
How a StatefulSet gives each Pod its own name, DNS name and claim, and what happens to the claims on scale-down or delete.
Key ideas in this lesson · tap one for a simple explanation
What you will be able to do
- Write a StatefulSet with a headless Service and a volumeClaimTemplate, and predict the names of its Pods and claims.
- Scale a StatefulSet and say which claims are created, kept or deleted under each retention policy.
- Choose between a Deployment and a StatefulSet for an app and say why.
Some apps run several copies, and each copy needs its own data. A database with three replicas is the classic example. If db-1 restarts, it must get db-1's disk back, not db-0's. A does this. Each gets a fixed name, its own DNS name and its own claim, and keeps all three when it restarts.
This lesson ties the module together. Each Pod's claim comes from a volumeClaimTemplate, and a provisions it. The exam asks you to create a StatefulSet with storage, or to scale one and check its volumes. The grader checks names, so learn the naming rules.
StatefulSet: blog
2 replicas, claim template: content
Pods
each with its own name, DNS name and claim
blog-0
DNS: blog-0.blog
claim: content-blog-0
blog-1
DNS: blog-1.blog
claim: content-blog-1
PersistentVolumes
one per claim, made by the standard class
Delete blog-1 and it comes back with the same name and the same claim, so it finds its data again. Its full DNS name is blog-1.blog.st-sts.svc.cluster.local.
StatefulSet or Deployment?
Stable names: the headless Service
A StatefulSet names a Service in spec.serviceName. That Service is headless (clusterIP: None): it has no virtual IP, and DNS answers with the Pod IPs directly. Each Pod then gets its own DNS name, <pod>.<service>.<namespace>.svc.cluster.local. Example: blog-0.blog.st-sts.svc.cluster.local.
- Like any Service, its must match the Pod .
- A client that needs one exact copy, such as the primary database, uses that Pod's name:
blog-0.blog. - A client that wants any copy uses the Service name,
blog.
apiVersion: v1
kind: Service
metadata:
name: blog
namespace: st-sts
spec:
clusterIP: None
selector:
app: blog
ports:
- name: http
port: 80apiVersion: apps/v1
kind: StatefulSet
metadata:
name: blog
namespace: st-sts
spec:
serviceName: blog
replicas: 2
selector:
matchLabels:
app: blog
template:
metadata:
labels:
app: blog
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
name: http
volumeMounts:
- name: content
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: content
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 100MiPods start in order
By default (podManagementPolicy: OrderedReady) the Pods start one at a time. blog-0 must be Running and Ready before blog-1 starts. Scaling down removes the highest number first. A database that elects a leader needs this order. With Parallel, all Pods start and stop at once instead.
Either way, a deleted Pod comes back with the same name, the same claim and the same DNS name. That is the whole promise of a StatefulSet.
volumeClaimTemplates and claim names
Each entry in volumeClaimTemplates is a claim spec. For every Pod, the StatefulSet creates one claim from it, named <template>-<statefulset>-<number>. Example: template content on StatefulSet blog gives content-blog-0 and content-blog-1.
- The 's
volumeMountsuses the template name,content, as if it were a volume. There is novolumes:entry for it. - The claims are ordinary . The StorageClass provisions them, and
kubectl get pvclists them. - Each claim stays tied to the Pod with the same number.
- You cannot change
volumeClaimTemplateson an existing StatefulSet. To change it, delete the StatefulSet and create it again.
kubectl apply -f blog.yaml
kubectl -n st-sts rollout status sts/blog --timeout=180s
kubectl -n st-sts get pods -l app=blog -o wide # blog-0, blog-1
kubectl -n st-sts get pvc # content-blog-0, content-blog-1, both Bound
kubectl -n st-sts scale sts blog --replicas=3 # creates content-blog-2 then blog-2
kubectl -n st-sts get sts blog -o jsonpath='{.spec.persistentVolumeClaimRetentionPolicy}'What happens to the claims when you scale down or delete
By default, nothing: the claims stay. Scale from 3 to 2, and content-blog-2 stays Bound with its data. Scale back to 3, and blog-2 gets its old disk back. Delete the StatefulSet, and every claim survives too. That is why old claims pile up after test runs.
Scale to 2
--replicas=2
blog-2 is removed
the highest number goes first
Claim content-blog-2
kept, unless whenScaled is Delete
spec.persistentVolumeClaimRetentionPolicy changes this. It has two settings, and each one is Retain (the default) or Delete:
What happens to the data after a claim is deleted is still up to the PV's reclaim policy, which the StorageClass set. You can add the field to a running StatefulSet with a patch:
kubectl -n st-sts patch sts blog -p '{"spec":{"persistentVolumeClaimRetentionPolicy":{"whenScaled":"Delete","whenDeleted":"Retain"}}}'
kubectl -n st-sts scale sts blog --replicas=2
kubectl -n st-sts get pvc # content-blog-2 is gone after blog-2 terminates
# Cleaning up by hand when the policy is Retain:
kubectl -n st-sts delete sts blog
kubectl -n st-sts delete pvc -l app=blog # claims inherit the selector labelsUpdates never touch the claims either. When you change the image, the StatefulSet replaces its Pods one at a time, from the highest number down. Each Pod comes back with its own claim and its data. The docs explain the details under Rolling Updates.
In the official docs· open on exam day too
- StatefulSetssearch: statefulset
- StatefulSet Basicssearch: statefulset basics
- Storage Classessearch: storage classes
A StatefulSet with per-replica storage
st-sts create a headless Service named blog on port 80 with selector app=blog, and a StatefulSet named blog with 2 replicas of image nginx:1.27 that uses that Service for its network identity. Every replica must get its own 100Mi ReadWriteOnce volume from the StorageClass standard through a volumeClaimTemplate named content, mounted at /usr/share/nginx/html. Both Pods must be Running and both claims Bound.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-1/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-1/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-1/cleanup | bash
Scale a StatefulSet and control what happens to its claims
blog in namespace st-sts runs 2 replicas, each with its own volume. Scale it to 3 replicas and make sure the new replica gets its own claim. Also configure the StatefulSet so that from now on the claim of a replica is deleted when the StatefulSet is scaled down, but all claims are kept if the StatefulSet itself is deleted. All 3 Pods must be Running when you finish.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-2/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-2/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/st-sts-2/cleanup | bash
Finished this lesson?
0 of 2 tasks passed. Your readiness counts tasks, so run them before you move on.