Workshop: Hochverfügbarer K3s Edge-Cluster auf NVMe-SSDs
Der Betrieb eines einzelnen Kubernetes-Knotens an der Edge birgt ein enormes Ausfallrisiko (Single Point of Failure). Fällt die Stromversorgung aus oder überhitzt eine Komponente, sind Telemetrie-Gateways, Messdatenerfassung und Steuerungs-APIs schlagartig offline.
In diesem praxisnahen Workshop bauen wir einen echten 3-Knoten Hochverfügbarkeits-Cluster (HA) mit K3s auf drei Raspberry Pi 5 Platinen mit M.2 NVMe SSDs über PCIe Gen 3 auf. Wir nutzen das eingebettete etcd von K3s für absolute Konsistenz, richten MetalLB für virtuelle IP-Adressen (VIP) ein und sichern Workloads so ab, dass ein physischer Totalausfall eines Knotens ohne Unterbrechung abgefangen wird.
1. Topologie & Quorum-Mathematik
Eine hochverfügbare Kubernetes-Control-Plane verlangt stets eine ungerade Anzahl an abstimmungsberechtigten Knoten, um Split-Brain-Situationen im Netzwerk auszuschließen:
%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
subgraph Network["Lokales Gigabit-Netzwerk"]
VIP["Virtuelle IP (MetalLB VIP)<br/>192.168.10.100"]
end
subgraph Cluster["3-Knoten HA etcd Quorum Mesh"]
direction TB
N1["rpi-node01 (.51)<br/>Control Plane + etcd 1"]
N2["rpi-node02 (.52)<br/>Control Plane + etcd 2"]
N3["rpi-node03 (.53)<br/>Control Plane + etcd 3"]
N1 <-->|Raft Konsens| N2
N2 <-->|Raft Konsens| N3
end
VIP -->|Aktiver ARP-Leader| N1
N1 -.->|Quorum Failover| N2
N2 -.->|Quorum Failover| N3
Die Raft-Quorum-Bedingung
Damit der Cluster bei Knotenausfällen handlungsfähig bleibt, benötigt etcd stets die absolute Mehrheit der Stimmen:
Quorum-Berechnung:
Quorum = (N ÷ 2) + 1
Für einen 3-Knoten-Cluster: Quorum = (3 ÷ 2) + 1 = 2 Knoten
Fehlertoleranz: Kann den Ausfall von 1 Knoten vollständig kompensieren.
Bei nur 2 Knoten führt der Ausfall eines Knotens dazu, dass nur noch 1 Knoten aktiv ist (weniger als das nötige Quorum von 2). Der gesamte Cluster schaltet dann zum Schutz vor Inkonsistenzen in den Nur-Lese-Zustand. Drei Knoten sind das mathematische Minimum für Hochverfügbarkeit.
2. Hardware-Architektur & Systemvorbereitung
Hardware-Komponenten
- Knoten: 3× Raspberry Pi 5 (mit 4 GB oder 8 GB RAM).
- Datenträger: 3× M.2 NVMe SSDs (z. B. Samsung 980 oder WD Black SN770) auf offiziellen Raspberry Pi M.2 HAT+ Boards.
- Stromversorgung: 3× 27 W USB-PD Netzteile oder PoE+ Switch mit entsprechenden HATs.
- Netzwerk: 1× Managed Gigabit Switch mit Cat6-Patchkabeln.
Vorbereitung auf allen 3 Knoten durchführen
Auf rpi-node01, rpi-node02 und rpi-node03 per SSH einloggen:
1. Feste IP-Adressen vergeben
rpi-node01:192.168.10.51rpi-node02:192.168.10.52rpi-node03:192.168.10.53
2. Speicher-cgroups aktivieren
In /boot/firmware/cmdline.txt am Ende der Zeile cgroup_memory=1 cgroup_enable=memory anhängen und neu starten.
3. Swap deaktivieren
3. Ersten Master-Knoten initialisieren (rpi-node01)
Auf dem ersten Knoten (rpi-node01) wird der K3s-Cluster mit dem Flag --cluster-init initialisiert:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--cluster-init \
--tls-san=192.168.10.50 \
--tls-san=k8s.edge.local \
--disable traefik \
--disable servicelb \
--write-kubeconfig-mode 644" sh -
Parameter-Erklärung
--cluster-init: Initialisiert ein verteiltes etcd-Cluster anstelle von SQLite.--tls-san=192.168.10.50: Fügt eine virtuelle IP (VIP) zum TLS-Zertifikat hinzu, damitkubectlohne Zertifikatsfehler über die VIP kommunizieren kann.--disable traefik --disable servicelb: Gibt Ports und RAM für MetalLB und eigene Ingress-Controller frei.
Status prüfen:
Sicherheitstoken für die weiteren Knoten auslesen:
Token notieren: K10398f7e2d9...::server:7b2a9e...
4. Knoten 2 und 3 als vollwertige Master einbinden
Die Knoten 2 und 3 treten nicht als einfache Worker, sondern als gleichberechtigte Server-Knoten bei.
Knoten 2 beitreten lassen (rpi-node02)
Per SSH auf rpi-node02 einloggen:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--server https://192.168.10.51:6443 \
--token <DEIN_CLUSTER_TOKEN> \
--tls-san=192.168.10.50 \
--tls-san=k8s.edge.local \
--disable traefik \
--disable servicelb \
--write-kubeconfig-mode 644" sh -
Knoten 3 beitreten lassen (rpi-node03)
Per SSH auf rpi-node03 einloggen und denselben Befehl ausführen:
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server \
--server https://192.168.10.51:6443 \
--token <DEIN_CLUSTER_TOKEN> \
--tls-san=192.168.10.50 \
--tls-san=k8s.edge.local \
--disable traefik \
--disable servicelb \
--write-kubeconfig-mode 644" sh -
Hochverfügbarkeit überprüfen
Auf rpi-node01 zurückkehren und den Cluster-Status prüfen:
Erwartetes Ergebnis:
NAME STATUS ROLES AGE VERSION
rpi-node01 Ready control-plane,etcd,master 8m12s v1.30.2+k3s1
rpi-node02 Ready control-plane,etcd,master 3m45s v1.30.2+k3s1
rpi-node03 Ready control-plane,etcd,master 90s v1.30.2+k3s1
5. Netzwerklastverteilung mit MetalLB (Layer 2)
Auf Bare-Metal-Platinen bleiben LoadBalancer-Dienste in Kubernetes ohne Clouddienst im Status <pending> hängen. MetalLB löst dies über Layer-2-ARP-Broadcasts im lokalen Netzwerk.
Schritt 1: MetalLB installieren
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
Warten, bis alle MetalLB-Pods laufen:
kubectl wait --namespace metallb-system \
--for=condition=ready pod \
--selector=app=metallb \
--timeout=120s
Schritt 2: IP-Adressbereich konfigurieren
Datei metallb-config.yaml erstellen (IP-Bereich z. B. 192.168.10.100 bis 192.168.10.110):
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: edge-pool
namespace: metallb-system
spec:
addresses:
- 192.168.10.100-192.168.10.110
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- edge-pool
Anwenden:
6. Hochverfügbares Deployment mit Anti-Affinity
Wir stellen einen Nginx-Webserver mit 3 Replikaten und Pod-Anti-Affinity bereit. Anti-Affinity sorgt dafür, dass Kubernetes niemals zwei Pods auf denselben physischen Raspberry Pi legt:
Datei ha-workload.yaml erstellen:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ha-edge-web
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: ha-web
template:
metadata:
labels:
app: ha-web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- ha-web
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 200m
memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
name: ha-web-service
namespace: default
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: ha-web
Bereitstellen:
Dienst abfragen:
MetalLB weist die virtuelle IP 192.168.10.100 zu. Die Seite ist ab sofort netzwerkweit unter http://192.168.10.100 erreichbar!
7. Der Härtetest: Stromkabel unter Last ziehen
Jetzt testen wir die Hochverfügbarkeit unter realen Ausfallbedingungen:
- In einem Terminal die Pods überwachen:
- In einem zweiten Terminal kontinuierlich Anfragen an die virtuelle IP senden:
- Ziehe das Stromkabel von
rpi-node01(dem ersten Master-Knoten) ab!
Was passiert im System:
- Der etcd-Raft-Konsens erkennt den Verlust von Knoten 1 innerhalb von 1 Sekunde.
- Die Knoten 2 und 3 behalten das Quorum (2 von 3 Stimmen = 66 % Mehrheit).
- MetalLB übernimmt die virtuelle IP
192.168.10.100binnen 2 bis 3 Sekunden auf Knoten 2 oder 3 per Gratuitous-ARP. - Der
curl-Befehl liefert ohne Unterbrechung weiterhin HTTP 200 Antworten! - Der Kubernetes Controller-Manager startet den ausgefallenen Pod automatisch auf einem der verbleibenden Knoten nach.
Steckst du das Stromkabel von rpi-node01 wieder ein, bootet die NVMe-SSD in rund 20 Sekunden, synchronisiert sich mit dem etcd-Transaktionsprotokoll und gliedert sich nahtlos wieder in das Cluster-Quorum ein.
Masterclass abgeschlossen
Herzlichen Glückwunsch! Du hast das gesamte Curriculum der Raspberry Pi Enterprise Masterclass gemeistert: von ARM-Silizium-Grundlagen und physikalischem Bus-Engineering bis zur ausfallsicheren Cluster-Orchestrierung.