Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions k8s/STATEFULSET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Lab 15

## StatefulSet Overview

- Purpose: manage stateful applications that need stable network IDs and persistent per-pod storage.

### Key differences vs Deployment

- Identity
- StatefulSet: each pod gets a stable, unique hostname (ordinal) and predictable DNS name (pod-0, pod-1...).
- Deployment: pods are interchangeable; no stable per-pod identity.

- Storage
- StatefulSet: supports volumeClaimTemplates so each pod gets its own persistent volume (mapped by ordinal).
- Deployment: PVCs must be managed manually; pods don’t get automatic per-pod PVs.

- Ordering & lifecycle
- StatefulSet: ordered, graceful pod creation, scaling, rolling updates, and termination (respecting ordinals).
- Deployment: parallel, unordered pod creation/termination for speed.

- Network & discovery
- StatefulSet: designed to work with a headless Service for stable DNS-based discovery.
- Deployment: typically fronted by a ClusterIP/Service load balancer; no per-pod DNS identity.

- Use cases
- StatefulSet: databases, distributed systems, apps needing sticky identity.
- Deployment: stateless web services, APIs, workers that can be scaled/replaced freely.


## Resource Verification

Output of kubectl get po,sts,svc,pvc

```bash
kubectl get svc

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17m
myapp-mychart-headless ClusterIP None <none> 80/TCP 3m9s

kubectl get statefulset

NAME READY AGE
myapp-mychart 3/3 96s

kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-mychart-0 1/1 Running 0 96s
myapp-mychart-1 1/1 Running 0 96s
myapp-mychart-2 1/1 Running 0 96s

kubectl get pvc

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
data-myapp-mychart-0 Bound pvc-24939609-c8ee-4b3d-b75f-c00101172b1f 100Mi RWO standard <unset> 96s
data-myapp-mychart-1 Bound pvc-a5e89bec-01fd-487d-b9a4-9cbc609f3606 100Mi RWO standard <unset> 83s
data-myapp-mychart-2 Bound pvc-d2b4f3b8-9062-4a78-9ca0-20827a61e3f6 100Mi RWO standard <unset> 70s

```

Network Identity - DNS resolution outputs

```bash
# Inside pod
nonroot@myapp-mychart-0:/app$ getent hosts myapp-mychart-1.myapp-mychart-headless.default.svc.cluster.local
10.244.0.13 myapp-mychart-1.myapp-mychart-headless.default.svc.cluster.local
```

Per-Pod Storage Evidence - Different visit counts per pod

```bash
curl localhost:8081/visits
{"visits":169,"timestamp":"2026-05-07T19:02:56.771901+00:00"}

curl localhost:8082/visits
{"visits":179,"timestamp":"2026-05-07T19:02:54.913873+00:00"}
```

Persistence Test - Data survives pod deletion

```bash
$ kubectl exec myapp-mychart-0 -- cat /data/visits.json
{
"count": 204
}
$ kubectl delete pod myapp-mychart-0
pod "myapp-mychart-0" deleted from default namespace

$ kubectl exec myapp-mychart-0 -- cat /data/visits.json
{
"count": 205
}
```
6 changes: 6 additions & 0 deletions k8s/mychart/.helmignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@
.idea/
*.tmproj
.vscode/


# I want to disable rollouts for this lab

templates/rollout.yaml
templates/service-preview.yaml
10 changes: 10 additions & 0 deletions k8s/mychart/templates/service-headless.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mychart.fullname" . }}-headless
spec:
clusterIP: None
selector:
{{- include "mychart.selectorLabels" . | nindent 4 }}
ports:
- port: {{ .Values.service.port }}
94 changes: 94 additions & 0 deletions k8s/mychart/templates/statefulset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "mychart.fullname" . }}
spec:
serviceName: {{ include "mychart.fullname" . }}-headless
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mychart.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
{{- with .Values.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- secretRef:
name: {{ include "mychart.fullname" . }}-credentials
- configMapRef:
name: {{ include "mychart.fullname" . }}-env-config
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
- name: config-volume
mountPath: /config
- name: data
mountPath: /data
{{- with .Values.volumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: config-volume
configMap:
name: {{ include "mychart.fullname" . }}-config
{{- with .Values.volumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: {{ .Values.persistence.size }}
Loading