- Authors

- Name
- Phillip Pham
- @ddppham
Kubernetes Storage Performance: CSI-Treiber richtig konfigurieren
TL;DR
- CSI-Treiber entkoppeln die Storage-Logik von Kubernetes und ermoeglichen Zugriff auf NVMe, Cloud-SSDs oder verteilte Systeme wie Ceph
- Die StorageClass-Parameter
volumeBindingMode: WaitForFirstConsumerund der richtigereclaimPolicy-Wert sind entscheidend fuer Performance und Datensicherheit - PostgreSQL profitiert massiv von
ReadWriteOnce-Volumes auf lokalen NVMe-SSDs -- bis zu 3x hoehere TPS gegenueber Standard-Netzwerkspeicher - Benchmarkt euer Storage immer mit
fiovor dem Produktiv-Einsatz, nicht erst wenn die Datenbank langsam ist - Monitoring ueber Prometheus mit dem
csi-Exporter undnode_disk_*-Metriken ist Pflicht
Warum CSI-Treiber den Unterschied machen
Kubernetes bringt von Haus aus keine Speicherlogik mit. Es delegiert alles an CSI-Treiber (Container Storage Interface). Das ist Absicht: ein offener Standard, der es Storage-Anbietern erlaubt, ihre Systeme direkt an Kubernetes anzubinden, ohne dass Upstream-Code geaendert werden muss.
In der Praxis heisst das: Die Performance eurer Datenbanken haengt nicht von Kubernetes ab, sondern davon, welchen CSI-Treiber ihr einsetzt und wie ihr ihn konfiguriert. Ein falsch konfigurierter CSI-Treiber kann euer NVMe-Backend auf HDD-Niveau ausbremsen.
Die drei gaengigsten Varianten im Ueberblick:
| Storage-Typ | CSI-Treiber Beispiele | IOPS (typisch) | Latenz | Replikation | Einsatzbereich |
|---|---|---|---|---|---|
| Lokale NVMe-SSD | TopoLVM, local-path | 500k+ | < 0.1 ms | Keine (App-Level) | Hoechste I/O, Datenbanken |
| Cloud Block Storage | aws-ebs-csi, azuredisk-csi | 16k-64k | 0.5-2 ms | Provider-seitig | Standard-Workloads |
| Distributed Storage | Rook-Ceph, OpenEBS, Longhorn | 10k-100k | 1-5 ms | Integriert | HA-Anforderungen |
| Netzwerk-Dateisystem | nfs-subdir-provisioner | 1k-10k | 5-50 ms | Abhaengig | Shared Read, Logs |
Die Wahl haengt vom Workload-Profil ab. Ein OLTP-System wie PostgreSQL braucht niedrige Latenz und hohe Random-Read-IOPS. Ein Data Warehouse profitiert mehr von sequentiellem Durchsatz.
StorageClass: Die unterschaetzte Stellschraube
Die StorageClass ist das zentrale Konfigurationsobjekt. Hier definiert ihr nicht nur den Provisioner, sondern auch Performance-Tier, Caching-Verhalten und Volume-Binding.
Ein haeufiger Fehler: volumeBindingMode: Immediate in Multi-AZ-Setups. Das Volume wird sofort provisioniert, bevor der Pod gescheduled wird. Wenn der Pod dann in einer anderen Zone landet, muss er auf ein Remote-Volume zugreifen -- mit entsprechender Latenz.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: disk.csi.azure.com
parameters:
skuName: PremiumV2_LRS
cachingMode: None
DiskIOPSReadWrite: "50000"
DiskMBpsReadWrite: "250"
allowVolumeExpansion: true
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
mountOptions:
- noatime
- nodiratime
Drei Punkte, die oft uebersehen werden:
cachingMode: None -- Fuer Datenbanken mit eigenem Buffer-Pool (PostgreSQL shared_buffers, MySQL InnoDB Buffer Pool) ist Host-Caching kontraproduktiv. Es fuehrt zu Double-Buffering und verschwendet RAM.
reclaimPolicy: Retain -- Im Produktivbetrieb wollt ihr nicht, dass ein versehentlich geloeschter PVC euer Volume mitreisst. Retain ist sicherer als Delete, erfordert aber manuelle Bereinigung.
mountOptions: noatime -- Spart bei leseintensiven Workloads messbar I/O, weil der Kernel nicht bei jedem Lesezugriff den Access-Timestamp aktualisiert.
PostgreSQL auf Kubernetes: PVC und Tuning
Fuer PostgreSQL kombiniert ihr die StorageClass mit einem passenden PVC und einem StatefulSet. Hier ein produktionsnahes Setup:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 200Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
resources:
requests:
cpu: "2"
memory: "8Gi"
limits:
cpu: "4"
memory: "16Gi"
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
Auf Anwendungsebene solltet ihr die PostgreSQL-Konfiguration an das Storage anpassen. Die wichtigsten Parameter:
shared_buffers: 25% des verfuegbaren RAMs (bei 16 Gi also 4 Gi)effective_cache_size: 75% des RAMswal_buffers: 64 MB bei SSD-Storagerandom_page_cost: 1.1 fuer SSDs (Standard ist 4.0, optimiert fuer HDDs)effective_io_concurrency: 200 fuer NVMe, 2 fuer HDD
Der Unterschied zwischen random_page_cost: 4.0 (Default) und 1.1 ist enorm. Der Query Planner waehlt mit dem richtigen Wert deutlich haeufiger Index Scans statt Sequential Scans, was bei SSD-Storage schneller ist.
Storage Benchmarking mit fio
Bevor ihr eine Datenbank auf neuen Storage setzt, benchmarked ihr das Volume. Erstellt einen Pod mit fio:
kubectl run fio-bench --rm -it --restart=Never \
--image=nixery.dev/shell/fio \
--overrides='{
"spec": {
"containers": [{
"name": "fio-bench",
"image": "nixery.dev/shell/fio",
"command": ["fio"],
"args": [
"--name=rand-rw",
"--ioengine=libaio",
"--direct=1",
"--bs=8k",
"--size=4G",
"--numjobs=4",
"--iodepth=32",
"--rw=randrw",
"--rwmixread=70",
"--runtime=60",
"--time_based",
"--group_reporting",
"--filename=/data/testfile"
],
"volumeMounts": [{
"name": "test-vol",
"mountPath": "/data"
}]
}],
"volumes": [{
"name": "test-vol",
"persistentVolumeClaim": {
"claimName": "postgres-data"
}
}]
}
}'
Die Parameter simulieren ein typisches Datenbank-Workload-Profil: 8k Blockgroesse, 70% Reads, 30% Writes, Random-Zugriff mit 4 parallelen Jobs. Achtet auf drei Werte im Output:
- IOPS (read + write): Sollte bei NVMe ueber 100k liegen
- Latenz (clat, p99): Unter 1 ms fuer lokale SSDs, unter 5 ms fuer Cloud Block Storage
- Durchsatz (BW): Mindestens 500 MB/s fuer sequentielle Workloads
Wenn die Werte deutlich unter euren Erwartungen liegen, prueft zuerst die StorageClass-Parameter, dann den CSI-Treiber-Log (kubectl logs -n kube-system -l app=csi-driver).
Monitoring: Engpaesse frueh erkennen
Storage-Probleme aeussern sich oft schleichend. Die Datenbank wird langsamer, aber niemand schaut auf die I/O-Metriken. Richtet euch mindestens diese Prometheus-Alerts ein:
node_disk_io_time_weighted_seconds_totalsteigt ueber 1.0 (Volume ist saturiert)kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytesueber 85% (Volume laeuft voll)- PostgreSQL-spezifisch:
pg_stat_bgwriter_buffers_backendsteigt (Buffer Pool zu klein, direktes I/O)
Ergaenzt das mit Grafana-Dashboards, die IOPS, Latenz und Durchsatz pro PVC anzeigen. Das CSI-Driver-Monitoring variiert je nach Treiber, aber die meisten stellen Metriken ueber den Standard-/metrics-Endpoint bereit.
Distributed Storage: Wann Rook-Ceph Sinn ergibt
Nicht jeder Workload braucht lokale NVMe-Performance. Wenn euer primaeres Ziel Hochverfuegbarkeit ist und ihr keine Replikation auf Anwendungsebene betreiben wollt (oder koennt), ist Rook-Ceph eine solide Wahl.
Rook deployt Ceph als Kubernetes-Operator und nutzt die lokalen Disks der Worker Nodes als Storage-Backend. Das ergibt ein verteiltes, repliziertes Block-Storage-System, das aus Kubernetes-Sicht wie ein normaler CSI-Treiber funktioniert.
Die Trade-offs sind klar:
| Aspekt | Lokale NVMe | Rook-Ceph |
|---|---|---|
| Latenz | Minimal (< 0.1 ms) | Moderat (1-5 ms) |
| IOPS | Maximal | Gut, abhaengig von Disk-Anzahl |
| Replikation | Keine (App muss das loesen) | 2x oder 3x automatisch |
| Failover | Manuell oder Operator-gesteuert | Automatisch |
| Betriebsaufwand | Gering (keine eigene Logik) | Hoch (Ceph-Cluster managen) |
| Mindest-Nodes | 1 | 3 (fuer Replikation) |
Fuer kleine Teams mit weniger als drei Ops-Ingenieuren ist Longhorn oft die bessere Alternative. Einfacher zu betreiben, weniger Konfiguration, und fuer mittelgrosse Workloads ausreichend performant.
Haeufige Fehler und wie ihr sie vermeidet
Fehler 1: Volume und Pod in verschiedenen Availability Zones. Passiert bei volumeBindingMode: Immediate. Der Fix: Immer WaitForFirstConsumer verwenden, damit das Volume in der gleichen Zone wie der Pod provisioniert wird.
Fehler 2: Kein Resource Limit auf dem Datenbank-Pod. Ohne Memory Limit kann der OOM-Killer zuschlagen und den Pod mit offenen Transaktionen toeten. Das fuehrt zu langen Recovery-Zeiten. Setzt immer explizite Limits.
Fehler 3: ext4 statt XFS fuer grosse Volumes. Ab ca. 1 TB zeigt XFS bessere Performance bei parallelen I/O-Operationen. Die meisten CSI-Treiber unterstuetzen fsType: xfs als Parameter in der StorageClass.
Fehler 4: Keine Volume Snapshots. CSI-Treiber unterstuetzen VolumeSnapshots. Nutzt sie fuer konsistente Backups vor Migrationen oder Upgrades. Das ist wesentlich schneller als ein vollstaendiges pg_dump.
Weitergehende Themen
Dieses Setup ist ein solides Fundament. Je nach Anforderung gibt es weitere Bereiche, die sich lohnen:
- Fuer PostgreSQL-spezifische Operator-Konfiguration: PostgreSQL Kubernetes Operator
- Fuer Capacity Planning und Kostenoptimierung: Kubernetes Capacity Planning
- Fuer einen Vergleich der Hosting-Kosten verschiedener Plattformen: Kubernetes Hosting Kosten Vergleich
- Fuer Observability des gesamten Stacks: OpenTelemetry auf Kubernetes
- Fuer DSGVO-konforme Storage-Konfiguration: Kubernetes Storage und Compliance
Wenn ihr Unterstuetzung bei der Storage-Architektur oder PostgreSQL-Performance-Optimierung auf Kubernetes braucht, meldet euch gerne fuer ein technisches Gespraech unter /kontakt.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
Kubernetes Storage: CSI Driver und PV einrichten
Kubernetes Persistent Volumes und CSI Driver DSGVO-konform einrichten. StorageClasses konfigurieren, Verschlüsselung aktivieren und dynamisches Provisioning nutzen.
Kubernetes pgvector: PostgreSQL als performanten Vector Store implementieren
Erfahren Sie, wie Sie pgvector nutzen, um PostgreSQL auf Kubernetes als performanten und datenschutzkonformen Vector Store für KI-Anwendungen im deutschen Mittelstand zu etablieren. Profitieren Sie von einer zukunftssicheren hybriden Datenarchitektur, die lokalen Anforderungen gerecht wird.
Datenbanken auf Kubernetes: StatefulSets richtig nutzen
PostgreSQL mit StatefulSets auf Kubernetes betreiben: Stabile Netzwerk-Identitäten, PersistentVolumeClaims und automatisierte Backups per CronJob.
Kubernetes Storage: CSI-Treiber und PV richtig nutzen
Persistent Volumes, StorageClasses und CSI-Treiber in Kubernetes konfigurieren: Von dynamischem Provisioning bis Volume Expansion mit Praxisbeispielen.
Kubernetes PVC Pending lösen: StorageClass und Provisioner debuggen
PersistentVolumeClaim bleibt auf Pending? Die häufigsten Ursachen sind eine fehlende StorageClass, ein defekter Provisioner oder erreichte Quotas. So lösen Sie es.