☸️ Kubernetes Lektion 16/20 ~9 Min. Experte

CSI und Storage-Architekturen

Container Storage Interface und Cloud-Storage.

📝 Meine Notizen

Lernziele

Nach dieser Lektion kannst du erklären, warum das Container Storage Interface (CSI) eingeführt wurde und wie ein CSI-Driver aus Controller- und Node-Plugin aufgebaut ist. Du kannst den Weg eines Volumes von „PVC erstellt” bis „im Container gemountet” nachzeichnen, Volume Snapshots einordnen und die Storage-Optionen für Cloud (EBS, Azure Disk, GCE PD) und On-Prem (Rook/Ceph, Longhorn) gegeneinander abwägen.

Das Problem: Storage-Treiber im Kubernetes-Core

In den frühen Jahren steckten die Storage-Treiber direkt im Kubernetes-Quellcode — „in-tree”: EBS-Code, GCE-PD-Code, NFS, Ceph, alles kompiliert in kubelet und Controller-Manager. Die Folgen: Ein Bugfix im EBS-Treiber musste auf das nächste Kubernetes-Release warten, Hersteller mussten ihren Code ins Kubernetes-Projekt einbringen, und jeder Cluster trug den Code aller Storage-Systeme mit sich herum — samt deren Sicherheits- und Stabilitätsrisiken.

Das Container Storage Interface (CSI) löst das durch eine stabile gRPC-Schnittstelle zwischen Kubernetes und Storage-Treibern: Wer Storage anbinden will, implementiert eine definierte Menge von Aufrufen (CreateVolume, ControllerPublishVolume, NodeStageVolume, …) und liefert seinen Treiber als ganz normale Container aus — unabhängig vom Kubernetes-Release-Zyklus. CSI ist bewusst kein Kubernetes-Standard, sondern ein Industriestandard, den auch andere Orchestratoren nutzen können. Die alten In-Tree-Treiber sind inzwischen auf CSI-Driver migriert; in den Cloud-Distributionen installierst du den jeweiligen CSI-Driver heute als Addon.

Anatomie eines CSI-Drivers: Controller- und Node-Plugin

Ein CSI-Driver besteht aus zwei Teilen mit klar getrennten Aufgaben — die Trennung folgt der Frage „Wo muss diese Operation stattfinden?”:

Das Controller-Plugin läuft als Deployment (typisch 1–2 Replicas, zentral) und erledigt alles, was gegen die Storage-API geht: Volume erzeugen und löschen, Volume an einen Node attachen/detachen, Snapshots anlegen, Volumes vergrößern. Es braucht Credentials für das Storage-Backend, aber keinen Zugriff auf einzelne Nodes.

Das Node-Plugin läuft als DaemonSet auf jedem Node und macht die lokale Arbeit: das attachte Block-Device formatieren, mounten, in den Pod-Mount-Namespace bringen. Es braucht privilegierte Rechte auf dem Host (Mounts!), aber keine Cloud-Credentials.

Dazu kommen die Sidecar-Container aus dem Kubernetes-Projekt als Bindeglied zur API: external-provisioner (beobachtet PVCs, ruft CreateVolume), external-attacher, external-snapshotter, external-resizer. Der Treiber-Hersteller implementiert nur die gRPC-Calls — die Kubernetes-Integration liefern die generischen Sidecars. Ein Schnittstellen-Design, das du im Interview als Muster erwähnen kannst.

Der komplette Lebensweg eines Volumes, den du erzählen können solltest: PVC erstellt → external-provisioner ruft CreateVolume → Volume existiert im Backend, PV-Objekt entsteht und bindet → Pod wird auf Node X geplant → Attach: Volume wird an Node X gehängt (bei Cloud-Block-Storage ein API-Call) → Node-Plugin: NodeStageVolume formatiert/mountet das Device auf den Node, NodePublishVolume bind-mountet es in den Pod → Container sieht ein Dateisystem.

Snapshots: Backup-Primitive, kein Backup-Konzept

CSI bringt Volume Snapshots als Kubernetes-Ressourcen mit: Eine VolumeSnapshotClass definiert, wie Snapshots erzeugt werden, ein VolumeSnapshot fordert einen Snapshot eines PVC an, und aus einem Snapshot lässt sich per dataSource im neuen PVC ein Volume wiederherstellen — praktisch für „Stand vor der Migration einfrieren” oder zum Klonen von Umgebungen.

Zwei ehrliche Einschränkungen gehören in jede Antwort dazu. Erstens: Ein Snapshot ist crash-konsistent, nicht anwendungskonsistent — eine Datenbank mitten im Schreibvorgang ist im Snapshot in genau diesem Zustand; saubere Datenbank-Backups brauchen Koordination mit der Anwendung oder datenbankeigene Tools. Zweitens: Snapshots liegen meist im selben Storage-System wie die Daten — gegen dessen Ausfall helfen sie nicht. Echte Backup-Strategien kombinieren Snapshots mit Tools wie Velero, die in externe Object Stores sichern.

Cloud vs. On-Prem: gekaufte Einfachheit vs. betriebener Storage

In der Cloud ist die Entscheidung einfach: Du nimmst den CSI-Driver des Providers — EBS (AWS), Azure Disk, GCE Persistent Disk. Das Storage-Backend ist ein Managed Service: Replikation, Hardware-Ausfälle, Kapazität — alles das Problem des Providers. Die Grenzen kennst du aus Lektion 15: Block-Volumes sind zonengebunden und RWO; für RWX greifst du zu Datei-Diensten wie EFS oder Azure Files (eigene CSI-Driver, andere Performance-Charakteristik).

On-Premises musst du das Storage-System selbst mitbringen. Zwei verbreitete Cloud-Native-Optionen:

Rook/Ceph: Rook ist ein Operator, der Ceph — ein ausgereiftes verteiltes Storage-System — im Cluster betreibt. Das liefert Block (RBD), Filesystem (CephFS, RWX!) und S3-kompatiblen Object Store aus einem System, skaliert weit und ist battle-tested. Der Preis ist erheblicher Betriebsaufwand: Ceph ist ein eigenes verteiltes System mit eigenen Konzepten (OSDs, Monitors, Placement Groups), das du im Fehlerfall verstehen musst.

Longhorn: leichtgewichtiger Ansatz (CNCF-Projekt, von Rancher initiiert) — repliziert Volumes blockweise über die lokalen Disks der Nodes, mit eingebauten Snapshots und Backups nach S3/NFS. Deutlich einfacher zu betreiben als Ceph, dafür weniger mächtig und für sehr große oder I/O-intensive Setups nicht die erste Wahl.

Die Interview-taugliche Abwägung: Managed Cloud-Storage, wo immer möglich (Betriebslast extern); on-prem Longhorn für überschaubare Anforderungen, Rook/Ceph wenn RWX, Object Storage oder große Skalierung gefordert sind — und nur mit einem Team, das bereit ist, Ceph zu lernen.

Praxis

CSI-Komponenten in einem echten Cluster inspizieren — das geht in jedem Cluster, hier mit kind (dessen lokaler Provisioner ist kein voller CSI-Driver, daher schauen wir uns die generischen Objekte an):

kind create cluster

# Welche CSI-Driver sind registriert? (in Cloud-Clustern aussagekräftiger)
kubectl get csidrivers
kubectl get csinodes -o yaml | head -n 30

# StorageClass → Provisioner-Zuordnung
kubectl get storageclass -o custom-columns=NAME:.metadata.name,PROVISIONER:.provisioner

# Lebenszyklus beobachten: PVC anlegen und Events verfolgen
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
EOF
kubectl run user --image=busybox:1.36 --restart=Never \
  --overrides='{"spec":{"containers":[{"name":"user","image":"busybox:1.36","command":["sleep","3600"],"volumeMounts":[{"name":"v","mountPath":"/data"}]}],"volumes":[{"name":"v","persistentVolumeClaim":{"claimName":"demo"}}]}}'
kubectl get events --sort-by=.lastTimestamp | grep -Ei 'provision|volume'

In einem EKS/AKS/GKE-Cluster lohnt zusätzlich ein Blick auf die Treiber-Pods selbst: kubectl -n kube-system get pods -l app.kubernetes.io/name=aws-ebs-csi-driver zeigt dir das Controller-Deployment und das Node-DaemonSet nebeneinander — die Architektur dieser Lektion zum Anfassen.

Typische Stolperfallen

Treiber-Logs am falschen Ort gesucht: Provisioning-Fehler stehen beim Controller-Plugin (Deployment), Mount-Fehler beim Node-Plugin (DaemonSet-Pod auf genau dem Node des betroffenen Pods). Wer das verwechselt, sucht lange.

Snapshot-CRDs fehlen: VolumeSnapshot funktioniert nur, wenn die Snapshot-CRDs und der snapshot-controller installiert sind — viele Distributionen liefern sie nicht automatisch mit. Symptom: VolumeSnapshot-Ressource wird angelegt, aber nichts passiert.

RWX vom Block-Driver erwartet: EBS-, Azure-Disk- und GCE-PD-CSI-Driver können kein ReadWriteMany. Der PVC bleibt Pending oder der zweite Pod scheitert beim Mount — die Lösung ist ein anderer Storage-Typ, kein Driver-Update.

Snapshots als Backup verkauft: Snapshot im selben Storage-System ≠ Backup. Wer im Interview oder im Audit „wir haben Snapshots” als Backup-Strategie präsentiert, fällt durch — externe Kopie (z. B. via Velero) gehört dazu.

Interview-Vorbereitung

Auf „Was ist CSI und warum gibt es das?” antwortest du historisch und architektonisch: Problem (Storage-Treiber in-tree: Release-Kopplung, Vendor-Code im Core) → Lösung (standardisierte gRPC-Schnittstelle, Treiber als Container, unabhängig deploybar) → Architektur (Controller-Plugin zentral für die Storage-API, Node-Plugin als DaemonSet für Mounts, generische Sidecars als Kubernetes-Brücke) → Nutzen (Provider innovieren unabhängig, Cluster trägt nur die Treiber, die er braucht).

Typische Follow-ups:

  • „Was passiert von PVC bis zum gemounteten Volume?” — Provisioner erzeugt Volume + PV, Bindung, Scheduling, Attach an den Node, NodeStage (formatieren/mounten), NodePublish (in den Pod) — in dieser Reihenfolge.
  • „Warum läuft das Node-Plugin als DaemonSet?” — Mount-Operationen müssen lokal auf dem Node passieren, auf dem der Pod läuft; jeder Node braucht das Plugin.
  • „Wie würdest du Storage on-prem aufbauen?” — Anforderungen klären (RWX? Object Storage? Skalierung?), dann Longhorn für Einfachheit vs. Rook/Ceph für Mächtigkeit — inklusive der Betriebskosten-Ehrlichkeit.
  • „Sind CSI-Snapshots ein Backup?” — Nein: crash-konsistent und im selben Storage-System; Backup braucht Anwendungskoordination und externe Kopie.

Zusammenfassung

CSI entkoppelt Storage-Treiber vom Kubernetes-Core über eine stabile gRPC-Schnittstelle: Das Controller-Plugin spricht zentral mit der Storage-API (provisionieren, attachen, snapshotten), das Node-Plugin mountet lokal als DaemonSet, generische Sidecars verbinden beides mit der Kubernetes-API. In der Cloud nimmst du den Provider-Driver (EBS, Azure Disk, GCE PD) und kaufst dir den Storage-Betrieb ab; on-prem wählst du zwischen Longhorns Einfachheit und der Mächtigkeit von Rook/Ceph. Snapshots sind ein nützliches Primitive — ein Backup-Konzept sind sie allein nicht.

Damit kennst du Storage von der Abstraktion bis zum Treiber. Die nächste Lektion wechselt die Perspektive: Wie entscheidet Kubernetes eigentlich, wo ein Pod läuft — und wie steuerst du das mit Affinity, Taints und Priorities?