Lab 19 - StatefulSets
Lab 19 - StatefulSets
Goal: stable names, per-Pod storage, ordered start - and the clean-up surprise. Chapter: StatefulSets
This lab needs the three PVs from
Lab 18.
Check they are Available:
kubectl get pv
kubectl patch pv pv-storage1 -p '{"spec":{"claimRef":null}}' 2>/dev/null
kubectl get pv
Headless Service first
# 1 -
cd ~
cat > web-headless.yaml <<'YAML'
apiVersion: v1
kind: Service
metadata:
name: web-headless
spec:
clusterIP: None # headless - no virtual IP
selector:
app: web-sts
ports:
- port: 80
YAML
kubectl apply -f web-headless.yaml
kubectl get svc web-headless # CLUSTER-IP is "None"
A normal Service hides the Pods behind one IP. A headless Service does the opposite: DNS returns the individual Pod addresses, so each replica is addressable by name. A StatefulSet requires one.
The StatefulSet
# 2 -
cat > web-sts.yaml <<'YAML'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: web-headless
replicas: 3
selector:
matchLabels:
app: web-sts
template:
metadata:
labels:
app: web-sts
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
resources:
requests: { cpu: "20m", memory: "32Mi" }
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteMany"]
storageClassName: ""
resources:
requests:
storage: 500Mi
YAML
kubectl apply -f web-sts.yaml
kubectl get pods -l app=web-sts -w # Ctrl-C once all three are Running
Watch the order: web-0 becomes Ready before web-1 is created, and web-1
before web-2. A Deployment starts all three at once; a StatefulSet is
deliberately sequential, because a database replica cannot join a cluster that
is not up yet.
# 3 - names and storage
kubectl get pods -l app=web-sts -o wide
kubectl get pvc
kubectl get pv
Note the PVC names: data-web-0, data-web-1, data-web-2 - the template name
plus the Pod name. One PVC per replica, each bound to one of your three NFS PVs.
This is why three replicas needed three PVs.
# 4 - stable identity
kubectl exec web-0 -- sh -c 'echo "I am web-0" > /usr/share/nginx/html/index.html'
kubectl exec web-1 -- sh -c 'echo "I am web-1" > /usr/share/nginx/html/index.html'
kubectl delete pod web-0
kubectl get pods -l app=web-sts # web-0 comes back with the SAME name
kubectl exec web-0 -- cat /usr/share/nginx/html/index.html # and the same data
A Deployment would have created web-7d9f...-xk2p with an empty volume. The
StatefulSet recreated web-0 and reattached data-web-0.
# 5 - per-Pod DNS names
kubectl run tmp --image busybox --restart=Never -it --rm -- \
sh -c 'nslookup web-0.web-headless.default.svc.cluster.local; wget -qO- web-1.web-headless'
<pod>.<service>.<namespace>.svc.cluster.local reaches one specific replica.
That is how database members find each other.
# 6 - ordered, reverse scale-down
kubectl scale sts web --replicas=1
kubectl get pods -l app=web-sts # web-2 goes first, then web-1
kubectl get pvc # the PVCs are all still here
Scaling down does not delete the storage. Scale back up and web-1 finds
its old data.
# 7 -
kubectl scale sts web --replicas=2
kubectl exec web-1 -- cat /usr/share/nginx/html/index.html # "I am web-1"
Clean up - the surprise
# 8 -
kubectl delete -f web-sts.yaml
kubectl get pvc # still there!
kubectl delete pvc data-web-0 data-web-1 data-web-2
kubectl delete -f web-headless.yaml
for p in pv-storage1 pv-storage2 pv-storage3; do
kubectl patch pv $p -p '{"spec":{"claimRef":null}}'
done
kubectl get pv
Deleting a StatefulSet deletes the Pods and leaves the PVCs. That is deliberate - your data is the point of the object - and it means clean-up is always a manual second step.