Ceph auf vier ARM-Boards — das Speicher-Fundament unterm Lab

Fast jeder Lab-Artikel sagt beiläufig ceph-block oder ceph-filesystem — erklärt wurde es nie. Hier ist die Schicht darunter: Rook-Ceph, das die NVMe von vier RK1-Boards zu einem fehlertoleranten Speicher bündelt. Mit der echten CephCluster-Config, dem OSD-pro-NVMe-Mapping, den drei StorageClasses — und den Day-2-Kriegsgeschichten, wenn ein MON-Quorum oder eine OSD wegbricht.
Table of contents

In nahezu jedem Beitrag taucht es beiläufig auf: storageClass: ceph-block, eine RWO-PVC, ein copyMethod: Snapshot. Erklärt habe ich diese Schicht nie — dabei funktioniert ohne sie nichts. Das hier ist das Fundament: Rook -Ceph, das die NVMe-SSDs der Turing-Pi-/RK1-Boards zu einem fehlertoleranten Speicher zusammenfasst.

Warum überhaupt verteilt?

Jedes RK1-Compute-Module trägt seine eigene NVMe. Der naheliegende Weg wäre local-path: PVC = Verzeichnis auf genau diesem Board. Nur: Damit klebt das Volume am Board. Fällt das Board aus, sind die Daten weg — und der Pod kann nirgendwo anders hochkommen, weil sein Volume nicht mitwandert.

Verteilter Speicher dreht das um. Ceph poolt die einzelnen NVMes zu einem logischen Store, repliziert jeden Block über mehrere Boards und entkoppelt die PVC vom Node: Der Pod folgt seinem Volume auf jeden Worker, und ein Board darf sterben. Genau diese Eigenschaft setzen VolSync-Backups und die meisten Apps stillschweigend voraus.

Die Bausteine

Ceph wirkt von außen komplex, besteht aber aus wenigen Rollen, die Rook allesamt als Pods fährt:

  • MON (Monitor) — hält die Cluster-Map und bildet ein Quorum. Ungerade Anzahl, Mehrheit muss leben.
  • MGR (Manager) — Dashboard, Metriken, Orchestrierung. Aktiv/Standby.
  • OSD (Object Storage Daemon) — einer pro Datenträger, verwaltet die rohen Blocks auf der NVMe.
  • CRUSH — der Algorithmus, der ohne zentrale Tabelle deterministisch entscheidet, auf welchen OSDs ein Block landet.
   RK1-1 (worker)     RK1-2 (worker)     RK1-3 (worker)
   ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
   │ MON   OSD   │    │ MON   OSD   │    │ MON   OSD   │
   │        ↑    │    │        ↑    │    │        ↑    │
   │   /dev/nvme0n1   │   /dev/nvme0n1   │   /dev/nvme0n1
   └─────────────┘    └─────────────┘    └─────────────┘
          └──── CRUSH: replicated size 3, failureDomain=host ────┘

Rook ist dabei der Operator, der das alles in Kubernetes verwaltet — installiert als zwei HelmReleases: ein rook-ceph-Operator und ein rook-ceph-cluster (Chart v1.19.2, Ceph v19.2.3 „Squid").

Eine OSD pro NVMe

Der entscheidende Teil der cephClusterSpec ist kurz:

storage:
  useAllNodes: true
  useAllDevices: false
  deviceFilter: "^nvme"     # nur NVMe → eine OSD pro Board, nichts anderes anfassen

useAllDevices: false plus deviceFilter: "^nvme" ist die Versicherung gegen den klassischen Unfall, dass Rook das Boot-Medium für sich beansprucht. Es greift ausschließlich NVMe-Devices und macht aus jedem eine OSD — auf allen Worker-Nodes.

Der Rook-CSI-Treiber läuft nur auf Worker-Nodes; Control-Plane-Nodes haben keinen. Das ist der Grund, warum stateful Dienste wie Headscale oder die Garage-Knoten worker-gebunden sind — ihre ceph-block-PVC kann gar nicht auf einem CP-Node landen.

Drei Wege auf denselben Speicher

Auf dem OSD-Pool sitzen die StorageClasses, die der Rest des Labs konsumiert:

ceph-block (RBD) — die Default-Class, RWO-Blockgeräte für die allermeisten Apps:

cephBlockPools:
  - name: ceph-blockpool
    spec:
      failureDomain: host
      replicated:
        size: 3
    storageClass:
      name: "${BLOCK_STORAGE_CLASS}"   # → "ceph-block"
      isDefault: true
      parameters:
        imageFormat: "2"
        imageFeatures: layering
        csi.storage.k8s.io/fstype: ext4

ceph-filesystem (CephFS) — wenn mehrere Pods gleichzeitig schreiben müssen (RWX). Dahinter ein MDS (activeCount: 1, activeStandby: true), der die Metadaten serviert.

Object/S3 — gab es früher als Ceph RGW, ist aber entfernt (cephObjectStores: []). Die S3-Schicht macht jetzt Garage, weil sie cross-cluster repliziert — was RGW im Lab-Setup nicht konnte.

Dazu aktiviert das Chart VolumeSnapshotClasses für Block (default) und Filesystem. Genau die sind der Unterbau für copyMethod: Snapshot aus dem VolSync-Artikel: VolSync friert per CSI einen konsistenten Snapshot ein und sichert den, statt das Live-Volume zu lesen.

size 3, failureDomain: host — auf vier Boards

Die zwei Zeilen replicated: size: 3 und failureDomain: host tragen die ganze Fehlertoleranz: Jeder Block existiert dreifach, und CRUSH verteilt die drei Kopien auf drei verschiedene Hosts. Das setzt mindestens drei OSD-Nodes voraus — bei vier RK1-Workern darf also ein Board komplett ausfallen, und der Pool läuft mit zwei Kopien weiter, bis Ceph neu balanciert.

Der Preis ist RAM: Ceph ist nicht für Boards mit knappem Speicher gebaut, deshalb sind die Requests bewusst klein gehalten (MON 256Mi, OSD 1Gi/Limit 2Gi, MGR 512Mi). Es läuft — aber Ceph auf ARM mit gedeckelten Limits ist eine Gratwanderung, kein Selbstläufer.

Day 2: wenn es kracht

Verteilter Speicher ist großartig, bis ein Board hart wegbricht. Zwei Situationen, die im Lab real vorkamen — und die Handgriffe, die zählen.

MON-Quorum verloren. Finden die MONs keinen Leader mehr (z. B. nach gleichzeitigem Ausfall mehrerer Boards), bricht man auf einen überlebenden MON herunter:

kubectl-rook-ceph mons restore-quorum c

Hängende OSD nach Node-Ausfall (degraded PGs). Eine OSD auf einem toten Board entfernt man erst, wenn genug freie Kapazität da ist und die übrigen Placement Groups gesund sind:

kubectl-rook-ceph rook purge-osd 1 --force
kubectl-rook-ceph operator restart

Referenziert Ceph danach noch den gelöschten OSD-Key, hilft der Holzhammer aus der Toolbox:

ceph auth del osd.1
purge-osd erst ausführen, wenn die anderen PGs wirklich active+clean sind und Kapazität frei ist — sonst nimmt man dem Cluster die letzte gesunde Kopie weg, während er noch rebalanciert. Im Zweifel zuerst ceph status in der Toolbox lesen, dann handeln.

Das ist die unglamouröse Schicht, ohne die kein anderer Beitrag funktioniert: Jedes beiläufige ceph-block löst sich genau hier auf — in vier NVMe-SSDs, die man in der Hand halten kann, zusammengehalten von CRUSH und drei Replicas. Wenn das Fundament steht, denkt niemand mehr daran. Bis ein Board stirbt — und dann ist man froh, dass size: 3 keine Dekoration war.