edu@dorner-it
WerkzeugeFortgeschritten30 min

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: 10Gi

Ergebnis: 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: 20Gi

Access 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 requests schedulet k8s blind — Nodes überlaufen.
  • Ohne memory limit killt der Kernel im Zweifel andere Pods.
  • cpu limit ist umstritten (Throttling); cpu request ist 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: 70

Voraussetzung: metrics-server lä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: 80

Ohne 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=3

Strategien im Deployment:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0
  • maxUnavailable: 0 ist 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/app

ConfigMap-Ä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 1

Ein Chart besteht aus:

mychart/
├── Chart.yaml          # Metadaten
├── values.yaml         # Default-Werte
└── templates/          # YAML-Templates mit Go-Template-Syntax
    └── deployment.yaml

Alternative: 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 storageclass

Hä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:

  1. Ergänze sinnvolle requests/limits und Probes.
  2. 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.
  3. Installiere einen Ingress Controller (z. B. ingress-nginx per Helm) und stelle den Service per Ingress bereit.
  4. Erstelle eine StatefulSet mit Postgres (oder per Helm Chart) und prüfe, dass das Volume Neustarts überlebt (kubectl delete pod pg-0).
  5. Spiele Rollouts durch: Image-Tag ändern, rollout undo.

Weiterführendes