Ein Kubernetes Persistent Volume ist eine Abstraktion über echten Speicher, der den Lebenszyklus eines Pods überdauert. Ein Pod bekommt dieses Volume nie direkt, sondern beansprucht es über ein Persistent Volume Claim, kurz PVC. Zusammen bilden PV und PVC das Storage-Fundament, auf dem Datenbanken, Message-Broker und alles andere aufbaut, das sich Daten über einen Neustart hinaus merken muss.
Ich betreibe produktionsreife Kubernetes-Cluster mit k3s auf Hetzner Cloud und beschreibe Storage im sechsten Kapitel meines Kubernetes-Praxisbuchs bei Rheinwerk ausführlich. Die meisten Storage-Probleme, die ich in Projekten sehe, entstehen nicht am PV selbst, sondern an der falschen Wahl von Access Mode oder Reclaim Policy. Alle meine Beiträge zu Kubernetes findest du gesammelt auf der Seite Kubernetes.
Kubernetes Persistent Volume und Persistent Volume Claim: wie sie zusammenspielen
Stell dir ein Persistent Volume wie eine externe Festplatte vor, die irgendwo im Rechenzentrum steckt. Ob dahinter ein Netzwerkspeicher, ein Cloud-Blockspeicher oder eine lokale Festplatte liegt, ist deiner Anwendung egal, denn der Zugriff über das PV sieht immer gleich aus. Das PVC ist die Anforderungsliste eines Pods an genau dieses Volume: wie viel Speicher, mit welchem Access Mode, aus welcher StorageClass.
Zwischen PV und PVC entsteht eine exklusive Verbindung, sobald beide zueinander passen. Ein Beispiel: Ein Grafana-Pod soll seine Dashboards und Einstellungen dauerhaft speichern. Dazu legst du erst ein PV an, das 5 Gigabyte auf einem Netzwerkspeicher bereitstellt.
apiVersion: v1
kind: PersistentVolume
metadata:
name: grafana-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path: /export/grafana
server: 10.0.1.20
Das passende PVC fragt diese Eigenschaften an. Access Mode und StorageClass müssen übereinstimmen, die angeforderte Größe darf die Kapazität des PVs nicht übersteigen. Passt eines davon nicht, bleibt das PVC im Status Pending hängen und wartet auf ein Volume, das nie kommt.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana-pvc
spec:
storageClassName: ""
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Ein PV durchläuft dabei sichtbare Zustände. Direkt nach dem Anlegen steht es auf Available. Sobald ein passendes PVC es beansprucht, wechselt es zu Bound. Löschst du das PVC wieder, springt das PV auf Released, behält aber seine Daten, bis du manuell eingreifst. Das ist bewusst so gebaut: Kubernetes löscht nie stillschweigend Daten, nur weil ein PVC verschwunden ist.
Wie Persistent Volumes konkret in einem StatefulSet zum Einsatz kommen, etwa über volumeClaimTemplates, zeige ich im Beitrag Kubernetes StatefulSet vs Deployment.
Access Modes: wer darf wie auf ein Volume zugreifen
Nicht jeder Storage-Typ erlaubt jeden Zugriffsmodus, denn das hängt vom darunterliegenden Speicher ab. Trotzdem solltest du die vier Modi kennen, bevor du ein PVC schreibst, weil eine falsche Wahl später zu Pods führt, die einfach nicht starten.
| Access Mode |
Bedeutung |
| ReadWriteOnce |
Lesen und Schreiben von genau einer Node aus. Mehrere Pods auf derselben Node dürfen trotzdem gleichzeitig zugreifen. |
| ReadOnlyMany |
Mehrere Nodes dürfen gleichzeitig lesend zugreifen, schreiben darf keine. |
| ReadWriteMany |
Mehrere Nodes dürfen gleichzeitig lesen und schreiben, etwa für einen gemeinsamen Upload-Ordner. |
| ReadWriteOncePod |
Stellt sicher, dass im gesamten Cluster nur ein einziger Pod auf das Volume zugreift, nicht nur eine Node. |
In der Praxis triffst du ReadWriteOnce am häufigsten, weil die meisten Block-Storage-Lösungen nur diesen Modus unterstützen. ReadWriteMany brauchst du dagegen fast nur, wenn mehrere Pods dieselben Dateien lesen und schreiben müssen, etwa bei hochgeladenen Bildern auf einer Website. Wichtig dabei: Der Access Mode ist eine Anforderung, keine Zusage. Dass du ReadWriteMany in ein PVC schreibst, heißt nicht, dass dein CSI-Treiber Schreibzugriff von mehreren Nodes wirklich kann. Unterstützt er ihn nicht, bleibt der Claim ungebunden oder der zweite Pod hängt beim Mounten. Dateisysteme wie NFS oder CephFS können es, klassischer Block-Storage in der Regel nicht. Welcher Storage-Typ welchen Access Mode überhaupt anbietet, listet die Kubernetes-Dokumentation zu Access Modes vollständig auf.
Statisch oder dynamisch: StorageClass und Reclaim Policy
Die beiden Beispiele oben zeigen ein statisches PV: Du legst es selbst an, bevor ein PVC es beansprucht. In der Praxis läuft das anders, denn kaum jemand will für jede Anwendung von Hand ein PV schreiben. Über eine StorageClass kann Kubernetes das Volume passend zum PVC selbst erzeugen.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: hetzner-volumes
provisioner: csi.hetzner.cloud
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
csi.storage.k8s.io/fstype: ext4
Zwei Einstellungen verdienen hier besondere Aufmerksamkeit. allowVolumeExpansion erlaubt dir, den Speicher eines bestehenden PVCs später zu vergrößern, ohne die Anwendung neu aufsetzen zu müssen. Der volumeBindingMode entscheidet, wann genau das Volume entsteht.
| volumeBindingMode |
Verhalten |
| Immediate |
Das PV entsteht sofort beim Anlegen des PVCs, unabhängig davon, welcher Pod es später nutzt. |
| WaitForFirstConsumer |
Kubernetes wartet, bis ein Pod das PVC tatsächlich braucht, und erzeugt das PV dann in der richtigen Zone. |
WaitForFirstConsumer ist bei node-gebundenem Storage fast immer die richtige Wahl, weil sonst ein Volume in einer Zone entstehen kann, in der später kein passender Node für den Pod frei ist.
Ebenso wichtig ist die Reclaim Policy, die festlegt, was mit dem PV passiert, wenn sein PVC verschwindet. Retain behält das PV samt Daten und verlangt manuelles Aufräumen. Delete ist bei dynamisch erzeugten PVs der Standard und löscht das Volume zusammen mit dem PVC, endgültig. Eine dritte Option, Recycle, gilt als veraltet und sollte durch dynamische PVs ersetzt werden. Wer produktive Daten auf einer StorageClass mit Delete-Policy laufen lässt, sollte sich bewusst sein, dass ein versehentlich gelöschtes PVC die Daten unwiderruflich mitnimmt.
Storage auf Hetzner: CSI-Treiber, Longhorn und local-path
Für meine eigenen Cluster auf Hetzner Cloud stehen mir grundsätzlich drei Wege offen, Storage bereitzustellen, und ich wähle je nach Anwendung einen anderen. Der offizielle Hetzner-CSI-Treiber bindet Hetzner Cloud Volumes als Netzwerkspeicher ein, so wie im StorageClass-Beispiel oben. Das Volume ist an eine Zone gebunden, folgt dem Pod aber node-übergreifend innerhalb dieser Zone, was für die meisten Datenbanken und Anwendungen die unkomplizierteste Lösung ist.
Longhorn geht einen anderen Weg: Es baut aus dem lokalen Storage jeder Node ein verteiltes, repliziertes Blockspeicher-System und übernimmt selbst die Replikation über mehrere Nodes hinweg. Das ist interessant, wenn du keinen Cloud-Storage nutzen willst oder zusätzliche Redundanz über die des Cloud-Anbieters hinaus brauchst, kostet dich aber zusätzliche Ressourcen und Betriebsaufwand für das Longhorn-System selbst.
Für alles, was nicht produktionskritisch ist, etwa Caches oder Testumgebungen, reicht oft der local-path-Provisioner, der PVs direkt auf dem lokalen Dateisystem einer Node anlegt. Er ist einfach und schnell, bindet den Pod aber fest an diese eine Node, denn das Volume kann nicht mit umziehen. Ob sich für eine Anwendung eher selbst betriebener Storage oder ein Wechsel auf eine Managed-Cloud-Lösung lohnt, wäge ich grundsätzlich im Beitrag Kubernetes on-premise oder Cloud? ab.
Temporäre Volumes: emptyDir, Ephemeral und Projected Volumes
Nicht jede Anwendung braucht Daten, die einen Pod überdauern. Für einen Cache oder eine Zwischenablage reicht ein Ephemeral Volume, das mit dem Pod entsteht und mit ihm auch wieder verschwindet. Der einfachste Fall ist emptyDir, ein leerer Ordner, den das Kubelet beim Start anlegt.
volumes:
- name: cache
emptyDir:
sizeLimit: 500Mi
medium: Memory
Mit medium: Memory legst du das Volume direkt im Arbeitsspeicher an, was deutlich schneller ist als eine Festplatte, aber auf das Ressourcenlimit des Pods einzahlt. Für anspruchsvollere Fälle gibt es CSI-basierte Ephemeral Volumes und Generic Ephemeral Volumes, die über einen Treiber sogar Snapshots oder vorbefüllte Daten unterstützen können, allerdings abhängig davon, was der jeweilige Treiber anbietet.
Ein verwandtes, aber eigenständiges Feature sind Projected Volumes. Sie bündeln mehrere Quellen wie ConfigMaps, Secrets oder die Downward API unter einem einzigen Mount-Pfad, statt für jede Quelle einen eigenen Mount zu brauchen. Deine Anwendung findet dann alle Konfigurationsdateien an einem Ort, was besonders bei vielen kleinen Konfigurationswerten die YAML-Manifeste deutlich aufräumt.
Volumes sichern: Snapshots als Backup-Baustein
Ein Volume Snapshot ist eine Momentaufnahme deines PVs zu einem bestimmten Zeitpunkt, vergleichbar mit einem Snapshot von Cloud-Blockspeicher, wie du ihn vielleicht schon von anderen Anbietern kennst. Kubernetes bildet das über drei zusammenhängende Objekte ab: Ein VolumeSnapshot ist deine Anforderung, ähnlich einem PVC, ein VolumeSnapshotContent die tatsächliche Datenkopie, und eine VolumeSnapshotClass legt fest, welcher Treiber den Snapshot ausführt.
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: grafana-pvc-snapshot
spec:
volumeSnapshotClassName: hetzner-snapshot
source:
persistentVolumeClaimName: grafana-pvc
Snapshots eignen sich vor allem als schneller Baustein vor riskanten Eingriffen, etwa einem Datenbank-Update, oder um eine Kopie für eine Fehleranalyse zu ziehen, ohne das Livesystem anzufassen. Sie ersetzen aber keine vollständige Backup-Strategie: Ein Snapshot liegt oft auf demselben Storage-System wie das Original und schützt dich nicht vor einem Ausfall dieses Systems. Wie du Volumes und den Cluster-Zustand insgesamt robust sicherst, inklusive etcd, beschreibe ich im Beitrag Kubernetes Backup: etcd und Volumes sichern.
Häufige Fragen
Was ist der Unterschied zwischen einem Persistent Volume und einer StorageClass?
Ein Persistent Volume ist ein konkretes Stück Speicher, das ein Pod über ein PVC beansprucht. Eine StorageClass beschreibt dagegen, wie Kubernetes ein solches Volume bei Bedarf selbst erzeugen soll, inklusive Treiber, Reclaim Policy und Bindungsverhalten. Ohne StorageClass müsstest du jedes PV von Hand anlegen.
Welche Access Modes gibt es für ein Kubernetes Persistent Volume?
Vier: ReadWriteOnce für Lese- und Schreibzugriff von einer Node, ReadOnlyMany für lesenden Zugriff von mehreren Nodes, ReadWriteMany für schreibenden Zugriff von mehreren Nodes und ReadWriteOncePod, das den Zugriff clusterweit auf einen einzigen Pod begrenzt. Welcher Modus verfügbar ist, hängt vom Storage-Typ ab.
Was bedeutet WaitForFirstConsumer bei einer StorageClass?
Es verzögert das Erzeugen des Persistent Volumes, bis ein Pod das PVC tatsächlich verwendet. Dadurch legt Kubernetes das Volume gleich in der richtigen Zone oder auf der richtigen Node an, statt vorschnell einen Ort zu wählen, an dem später kein passender Pod landen kann.
Was passiert mit einem PV, wenn ich das zugehörige PVC lösche?
Das hängt von der Reclaim Policy ab. Bei Retain bleibt das PV mitsamt seinen Daten bestehen und wechselt in den Status Released, bis du manuell eingreifst. Bei Delete, dem Standard für dynamisch erzeugte PVs, werden PV und Daten sofort und endgültig gelöscht.
Brauche ich Volume Snapshots, wenn ich schon Backups mache?
Nicht zwingend. Snapshots sind vor allem für schnelle Zwischenstände nützlich, etwa vor einem riskanten Update. Für eine vollständige Backup-Strategie mit Wiederherstellung an anderer Stelle brauchst du trotzdem eine Lösung, die Daten außerhalb des ursprünglichen Storage-Systems sichert.
Wie du weitermachst
Wenn deine Anwendung mehrere Instanzen mit eigener Identität und eigenem Volume braucht, lohnt sich ein Blick auf das StatefulSet, das Persistent Volumes über volumeClaimTemplates automatisch pro Pod erzeugt: Kubernetes StatefulSet vs Deployment. Willst du klären, ob sich dein Storage überhaupt selbst betreiben lässt oder eine Managed-Lösung sinnvoller ist, hilft dir Kubernetes on-premise oder Cloud?.
Mein Vorschlag für heute: Lege ein PVC mit einer StorageClass deiner Wahl an, prüfe mit kubectl get pv,pvc den Status und lösche das PVC anschließend testweise, um zu beobachten, wie sich die konfigurierte Reclaim Policy tatsächlich verhält.
Ausführlich mit allen Beispielen, von den Storage-Typen über CSI-Treiber bis zu Snapshots und Projected Volumes, steht das in Kapitel 6 meines Kubernetes-Praxisbuchs bei Rheinwerk. Alle Informationen zum Buch findest du auf meiner Kubernetes-Seite.