Kubernetes Fortgeschritten: StatefulSets, Storage & Skalierung
Wenn die fünf Basis-Ressourcen nicht mehr reichen: StatefulSets, PersistentVolumes, Probes, Resource-Management, Autoscaling, Ingress und ein erster Blick auf Helm.
Zuletzt aktualisiert: 12. Mai 2026
kubernetes-basics hat dir Pods, Deployments und Services gezeigt — die
zustandslosen Bausteine. In der Realität sind Datenbanken zustands
behaftet, Apps müssen skalieren, Traffic muss von außen rein, und
hunderte YAMLs verlangen nach Templating. Hier sind die Werkzeuge.
Lernziele
- Du wählst bewusst zwischen Deployment und StatefulSet
- Du verstehst PersistentVolumes, PVCs und StorageClasses
- Du konfigurierst Probes und sinnvolle Resource-Requests/Limits
- Du skalierst horizontal mit HPA und vertikal mit Limits
- Du machst Services per Ingress von außen erreichbar
- Du kennst Helm im Überblick
StatefulSet vs Deployment
Ein Deployment behandelt seine Pods als austauschbar — gleicher Bauplan, beliebige Reihenfolge, beliebige Namen. Für Datenbanken, Queues, verteilte Systeme reicht das nicht.
| Eigenschaft | Deployment | StatefulSet |
|---|---|---|
| Pod-Namen | zufällig (web-abc123) |
stabil (db-0, db-1) |
| Reihenfolge | parallel | sequenziell, geordnet |
| Identität | austauschbar | jeder Pod hat eine Rolle |
| Storage | gemeinsam / ephemeral | pro Pod ein eigener PVC |
| Headless Service | optional | praktisch immer |
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db-headless
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10GiErgebnis: db-0, db-1, db-2 mit jeweils eigenem 10-GB-Volume. Wird
db-1 neu gescheduled, bekommt der neue Pod das gleiche Volume.
Faustregel: Stateless → Deployment. Stateful (DB, Kafka, Elastic) → StatefulSet, idealerweise via Operator (siehe nächster Kurs).
Persistent Storage
Ein Pod ist vergänglich, ein Volume nicht. Drei Begriffe klar zu trennen ist der halbe Weg:
flowchart LR
SC["StorageClass<br/><small>wie soll Storage aussehen</small>"]
PVC["PersistentVolumeClaim<br/><small>ich brauche 10Gi RWO</small>"]
PV["PersistentVolume<br/><small>hier liegen 10Gi auf EBS-xyz</small>"]
Pod[Pod] -- mount --> PVC -- bound to --> PV
SC -. dynamic provisioning .-> PV- StorageClass beschreibt einen Storage-Typ (SSD, gp3, NFS).
- Ein PVC ist ein Antrag auf Storage mit Größe und Access-Mode.
- Ein PV ist das konkrete Stück Storage — meist dynamisch bereitgestellt durch die StorageClass.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 20GiAccess Modes:
| Mode | Bedeutung |
|---|---|
ReadWriteOnce (RWO) |
Genau ein Node schreibt — Standard für Block-Storage |
ReadOnlyMany (ROX) |
Mehrere Nodes lesen |
ReadWriteMany (RWX) |
Mehrere Nodes schreiben — z. B. NFS, CephFS |
ReadWriteOncePod (RWOP) |
Genau ein Pod, härter als RWO |
Probes — Liveness, Readiness, Startup
Ohne Probes weiß k8s nicht, ob deine App gesund ist — nur, ob der Prozess läuft.
livenessProbe:
httpGet:
path: /healthz
port: 3000
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 3000
periodSeconds: 5
startupProbe:
httpGet:
path: /healthz
port: 3000
failureThreshold: 30
periodSeconds: 2| Probe | Was passiert bei Fehlschlag |
|---|---|
| Liveness | Pod wird neugestartet |
| Readiness | Pod wird aus Service-Endpoints entfernt |
| Startup | Liveness/Readiness pausieren, bis Startup erstmals grün ist |
Klassischer Fehler: Liveness an die DB hängen. Wenn die DB hängt, killst du alle App-Pods. Lieber: Liveness prüft nur die App selbst. Readiness darf an Abhängigkeiten dranhängen.
Resource Requests & Limits
resources:
requests: # Reservierung — Scheduler-Garantie
cpu: "250m"
memory: "256Mi"
limits: # Hartes Limit
cpu: "1"
memory: "512Mi"- Ohne
requestsschedulet k8s blind — Nodes überlaufen. - Ohne
memory limitkillt der Kernel im Zweifel andere Pods. cpu limitist umstritten (Throttling);cpu requestist Pflicht.- Faustregel: erst messen (Prometheus,
kubectl top), dann setzen.
QoS-Klassen
| Klasse | Wann | Verhalten |
|---|---|---|
Guaranteed |
requests == limits für CPU + Memory | Wird zuletzt evicted |
Burstable |
requests < limits | Mittel |
BestEffort |
nichts gesetzt | Wird zuerst evicted |
Horizontal Pod Autoscaler (HPA)
Skaliert Replikas anhand von Metriken — meistens CPU oder Memory, mit Custom Metrics auch alles andere.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Voraussetzung:
metrics-serverläuft im Cluster. Ohne den sieht HPA keine Metriken und macht nichts.
Mit dem Vertical Pod Autoscaler (VPA) lassen sich Requests/Limits automatisch nachziehen — gut zum Lernen über den richtigen Wert, in Prod oft im „recommend only"-Modus.
Ingress — Traffic von außen
Services vom Typ LoadBalancer sind teuer (jeweils eine Cloud-LB). Für
HTTP/HTTPS-Verkehr ist Ingress der Standard: ein einziger Einstieg
mit Routing.
flowchart LR
User[Internet] --> LB[Cloud-LB / NodePort]
LB --> IC[Ingress Controller<br/>nginx / Traefik]
IC -- "host: app.example.com" --> S1[Service: web]
IC -- "host: api.example.com" --> S2[Service: api]
IC -- "path: /admin" --> S3[Service: admin]apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts: [app.example.com]
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80Ohne Ingress Controller (NGINX, Traefik, HAProxy, …) tut die Ingress-Ressource gar nichts. In Cloud-Clustern oft separat zu installieren.
Neuer im Ökosystem: Gateway API löst Ingress in vielen Setups langfristig ab — flexibler, klarere Trennung von Plattform und App. Für neue Projekte einen Blick wert.
Rollouts steuern
kubectl rollout status deploy/web
kubectl rollout history deploy/web
kubectl rollout undo deploy/web --to-revision=3Strategien im Deployment:
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0maxUnavailable: 0ist die sicherere Default für Prod.- Mit Blue/Green oder Canary (z. B. via Argo Rollouts oder Flagger) lassen sich Rollouts noch feiner steuern — siehe nächster Kurs.
ConfigMaps & Secrets — fortgeschritten
Als Dateien mounten
volumes:
- name: config
configMap:
name: app-config
containers:
- name: app
volumeMounts:
- name: config
mountPath: /etc/appConfigMap-Änderungen erscheinen automatisch als neue Datei-Inhalte im Pod — die App muss sie selbst neu einlesen.
Automatischer Neustart bei Config-Änderung
k8s startet Pods nicht automatisch neu, wenn die ConfigMap sich ändert. Tricks:
- Annotation mit Hash der ConfigMap im Pod-Template (Helm/Kustomize-
Pattern:
checksum/config: {{ include … | sha256sum }}). - Tools wie Reloader (stakater) tun das automatisch.
Secrets in echt
In Prod nie unverschlüsselte Secrets ins Git. Optionen:
- SOPS + age-Keys (verschlüsselt im Repo)
- External Secrets Operator (zieht aus Vault, AWS Secrets Manager …)
- Sealed Secrets (Bitnami, verschlüsselt für einen Cluster)
Helm im Überblick
Hunderte YAMLs werden schnell unübersichtlich. Helm ist der Paket- Manager für k8s — Templates plus Versionierung.
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install pg bitnami/postgresql \
--namespace data --create-namespace \
--set auth.postgresPassword=secret
helm list -A
helm upgrade pg bitnami/postgresql --set auth.postgresPassword=neu
helm rollback pg 1Ein Chart besteht aus:
mychart/
├── Chart.yaml # Metadaten
├── values.yaml # Default-Werte
└── templates/ # YAML-Templates mit Go-Template-Syntax
└── deployment.yamlAlternative:
kustomize— keine Templates, sondern Overlays. Beide haben Daseinsberechtigung; viele Projekte mischen.
Häufige Stolperfallen
„Pod hängt in Pending mit Storage-Fehler"
PVC bekommt keinen passenden PV. Prüfen:
kubectl describe pvc <name>
kubectl get storageclassHäufig: falsche storageClassName, oder kein Provisioner installiert.
„HPA macht nichts, obwohl Last da ist"
kubectl get hpa
# TARGETS MINPODS MAXPODS
# <unknown>/70% 2 20<unknown> heißt: metrics-server fehlt oder Container haben keine
requests gesetzt. Beides fixen.
„Service ist da, aber von außen nicht erreichbar"
Service ist ClusterIP — nur intern. Für externen Zugriff: Ingress
(empfohlen) oder LoadBalancer-Service. kubectl get svc checken.
„Rolling Update bleibt stecken"
kubectl rollout status deploy/web
# Waiting for deployment "web" rollout to finish: 1 of 3 updated replicas are available...Meist: Readiness-Probe fehlschlägt, oder maxUnavailable: 0 plus
nicht genug Cluster-Kapazität. kubectl describe zeigt Details.
Praxis-Übung
Bau auf dem hello-Beispiel aus dem Einsteiger-Kurs auf:
- Ergänze sinnvolle
requests/limitsund Probes. - Installiere
metrics-server(für kind:kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml) und füge einen HPA hinzu. - Installiere einen Ingress Controller (z. B.
ingress-nginxper Helm) und stelle den Service per Ingress bereit. - Erstelle eine StatefulSet mit Postgres (oder per Helm Chart) und
prüfe, dass das Volume Neustarts überlebt (
kubectl delete pod pg-0). - Spiele Rollouts durch: Image-Tag ändern,
rollout undo.
Weiterführendes
- Probes erklärt: https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/
- Resource Management: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
- Helm Docs: https://helm.sh/docs/
- Gateway API: https://gateway-api.sigs.k8s.io/
- Argo Rollouts: https://argoproj.github.io/argo-rollouts/