Skip to content

Live-Demo: Self-Service auf Bare Metal

Dauer: 30 Minuten · Bühne: sthings.lab · Story: Von einem physischen Node zu einer entwickler-bestellten VM — ohne dass ein Entwickler jemals Harvester zu Gesicht bekommt.


Vorbemerkung zum Titel

Der Titel „Bare-Metal-Provisionierung" kann zwei Dinge meinen. Was dieses Lab zeigt, ist die zweite Lesart:

Lesart Im Lab vorhanden?
Physische Server per PXE/Redfish/Metal3 ausrollen (Layer 0) Nur als Runbook — docs/install.md beschreibt Node, Netz, Disks und die äquivalente automatische config.yaml (PXE-tauglich). Kein Metal3/Tinkerbell, keine Live-Neuinstallation in 30 Minuten.
Self-Service auf einer Bare-Metal-Plattform (Layer 1–2) Vollständig live — Harvester/SUSE Virtualization auf dem Blech, darüber Backstage → Git → Argo CD → Crossplane.

Empfehlung: Layer 0 in 3–4 Minuten als reproduzierbares Artefakt zeigen (Runbook + config.yaml), nicht live installieren. Der Rest der Zeit gehört dem Self-Service-Pfad. Wenn das Publikum echtes PXE-Provisioning physischer Server erwartet, den Titel vorher auf „Self-Service auf Bare Metal" schärfen — sonst entsteht eine Erwartungslücke, die die Demo nicht schließen kann.


Die Bühne (was tatsächlich läuft)

Eine physische Maschine, alles andere sind VMs darauf — das ist die Pointe der Demo und sollte am Anfang explizit gesagt werden.

Ebene System Adresse Rolle
Blech harvester 192.168.10.110, VIP .139 Harvester v1.8.0 (SUSE Virtualization), Single Node, SL Micro 6.2
VM platform 192.168.10.160 k3s: Backstage, claim-machinery-api, Argo CD, OpenBao (PKI), Clusterbook (IP/DNS), Harbor, MinIO (Golden Images), Rancher
VM crossplane-mgmt 192.168.10.108 Crossplane für den VM-Pfad: XVirtualMachine → Harvester. Von Argo CD auf platform gespeist (showcase-crossplane-xrs)
VM machinery-hv 192.168.10.105 Crossplane für ganze Cluster (ClusterStack), Flux-gesteuert
VM app-dev-hv 192.168.10.176 Ein bestellter Cluster (aus machinery-hv), darauf homerun2 + tabletennis
VM homerun2-dev 192.168.10.117 App-Cluster (homerun2, tabletennis)
Netz ddwrt 192.168.10.1 Gateway, DNS *.sthings.lab, DHCP .100–.149

infra und xplane gibt es nicht mehr (#240): Vault/PKI und Clusterbook sind nach platform gewandert (OpenBao), Crossplane nach crossplane-mgmt.

Quelle: system-inventory/inventory.yaml → topology.svg + die interaktive GitHub-Pages-Seite. Das ist der beste Einstiegs-Slide der ganzen Demo, weil er aus dem Repo generiert wird und nicht aus Visio.


Ablauf

Drei Teile, die aufeinander aufbauen (Runbook für die Generalprobe: #309):

  1. Teil 1 — Dev-Image per Backstage (Abschnitt 2): wasserdicht, läuft live an und wird am Ende eingesammelt.
  2. Teil 2 — Harvester-VM per Backstage + Argo CD (Abschnitte 3–4). Die VM bootet mit os: ubuntu24 genau das Image aus Teil 1.
  3. Teil 3 — Rancher-Cluster per ClusterStack (Abschnitt 5).

Choreografie: Teil 1 zuerst anstoßen, weil die Kette vom Klick bis zum Pin ~16 min braucht (Generalprobe 2026-09-30: Klick 05:32:50 → PR-Build 6 min → Auto-Merge → Release 6,5 min → Pin 05:46:13 → Argo CD live 05:48:52). Während sie läuft, Teil 2 und 3 zeigen, danach zu Teil 1 zurück.

0 · Architektur-Frame (3 min)

Interaktive Inventory-Seite öffnen (Tab Topology), auf harvester klicken.

„Das hier ist ein Rechner. Alles andere in diesem Bild ist eine VM darauf. Und dieses Bild ist kein Diagramm, das jemand gemalt hat — es fällt aus inventory.yaml heraus, CI bricht, wenn es veraltet."

Dann der Zielsatz für die nächsten 25 Minuten:

„Ein Entwickler will eine VM. Er bekommt sie über einen Pull Request. Er hat keinen Zugang zu Harvester, keinen Zugang zum Cluster, kein Ticket geschrieben."

Sichtbar machen: make check in system-inventory/ bricht bei doppelten IPs — die Adressvergabe ist ein CI-Gate, kein Excel-Sheet.


1 · Layer 0 — das Blech (4 min)

Harvester-UI (harvester.sthings.lab) → Hosts, Disks, VM-Networks. Ein Node, alle Cluster aus der Tabelle oben laufen als VMs darauf.

Dann docs/install.md daneben: dieselben Werte als Runbook — Bond enp5s0, MAC, VIP .139, CIDRs, Disk-Layout, DD-WRT-Reservierung. Und der Abschnitt „Equivalent automated config.yaml": derselbe Node, unbeaufsichtigt per PXE/Autoinstall.

„Der Hypervisor ist hier ein Artefakt, kein Klick-Protokoll. Wenn das Blech morgen abraucht, ist es eine Datei, kein Gedächtnisprotokoll."

Ehrlich bleiben: der Node wurde per TUI-Wizard installiert, die config.yaml ist die dokumentierte äquivalente Automatisierung. Genau diese Ehrlichkeit ist später ein Lessons-Learned-Punkt.


2 · Teil 1 — Dev-Image als Self-Service mit zwei Geschwindigkeiten (5 min + Rückkehr)

packer/ zeigen — die Governance steckt im Ordner-Layout:

1
2
3
packer/_build/          gemeinsame Build-Logik, EINE Kopie
packer/golden/          kuratierte Basis  → Review-gated, Release + Pin-PR nach Review
packer/dev/             Spielwiese        → PR-Build, Auto-Merge, Release + Pin-PR

packer/dev/u26-dev/users.yaml öffnen: ein Entwickler hängt seinen SSH-Key an, packages.yaml: seine Pakete. packer-pr-build.yml erkennt den geänderten Ordner, baut nur das Delta auf dem publizierten Golden-Artefakt aus MinIO (Checksumme geprüft), legt es in MinIO ab, registriert ein Wegwerf-Image <name>-pr<N>.<version> in Harvester (Harvester lädt es selbst herunter) und merged bei grün. Danach stößt der Auto-Merge packer-release.yml an: Release <name>-<version> plus ein pin-bot/<name>-PR, der den Pin verschiebt und sich bei Dev selbst merged. Erst mit dem Pin booten neue VMs das neue Image. Berührt derselbe PR ein golden/-Verzeichnis, erzwingt CI die Review.

Live: Backstage → Create → Create Harvester VM-Template. Basis ubuntu26 (→ u26-dev), einen User demo mit SSH-Key (kein : im Key-Kommentar), ein Paket (z. B. htop). Nicht Rocky/Leap — der Default groups: sudo passt dort nicht (offen aus #267). Absenden, dann:

  1. PR in packer/dev/u26-dev/ zeigen: users.yaml, packages.yaml, catalog-info.yaml. build.pkrvars.hcl (Golden-Basis + .sha256) ist nicht im PR, sondern steht schon auf main — daneben im Repo zeigen.
  2. GitHub → Actions: packer-pr-build.yml läuft auf dem kvm-Runner an. Hier weggehen — weiter mit Abschnitt 3.
  3. Rückkehr am Ende: PR ist auto-gemergt, packer-release.yml hat u26-dev-<version> released, der pin-bot/u26-dev-PR hat den Alias ubuntu24 (der einzige Eintrag für u26-dev) in env-config-virtualmachine.yaml verschoben und sich selbst gemergt. In Harvester: das Release-Image. Das Wegwerf-Image u26-dev-pr<N>.<version> ist dann schon weg — prune-images.sh löscht es im Release-Lauf, weil der PR geschlossen ist. Wer es zeigen will: in den ~6 min zwischen PR-Build und Release in die Harvester-Images schauen. Wenn die Zeit reicht: demo-vm-0 (Generalprobe, ubuntu24) — der User demo kommt per Key rein.

Das Showcase-Deck dazu liegt in backstage-resources (templates/harvester-packer-devimage/showcase, Slidev).

„Zwei Tiers, ein Mechanismus. Der Unterschied ist nicht das Tool, sondern welcher Ordner angefasst wird."


3 · Layer 2 — Der Hauptakt: VM per Pull Request (12 min)

Der durchgehende Pfad. Vier Fenster vorbereiten: Backstage, GitHub, Argo CD (argocd.platform.sthings.lab, App showcase-crossplane-xrs) plus Terminal (kubectl gegen crossplane-mgmt), Harvester-UI.

3.1 Bestellung (2 min) — Backstage (backstage.platform.sthings.lab) → Create → Template Harvester VM. Parameter: Name, T-Shirt-Size (small…xlarge), Base Image (sthings-u26 = gepinntes Golden Image), optional Ansible base-OS provisioning. Ziel steht fest auf stuttgart-things/harvester / clusters/crossplane-mgmt/xrs / virtual-machine (backstage-resources#58). Absenden.

Was dahinter passiert, dabei sagen: Backstage ruft die claim-machinery-api (claims.platform.sthings.lab), die rendert das KCL-Template xr-virtualmachine-harvester aus stuttgart-things/kcl und öffnet den PR.

3.2 Der PR (3 min) — GitHub, der frisch geöffnete PR gegen dieses Repo:

1
2
3
4
clusters/crossplane-mgmt/xrs/virtual-machine/<name>/<name>.yaml         # das XR (XVirtualMachine)
clusters/crossplane-mgmt/xrs/virtual-machine/<name>/kustomization.yaml
clusters/crossplane-mgmt/xrs/virtual-machine/<name>/catalog-info.yaml   # Backstage-Katalogeintrag
clusters/crossplane-mgmt/xrs/virtual-machine/kustomization.yaml         # Parent, um den Eintrag ergänzt

Das ist der Punkt, an dem die Demo entweder zündet oder nicht — den Diff wirklich zeigen. Vier Dinge daran benennen:

  1. Das XR ist deklarativ und kurz: Name, Size, Image. CPU/RAM/Disk, Netz, StorageClass und Image-Version kommen aus der EnvironmentConfig virtualmachine-harvester (clusters/crossplane-mgmt/platform/virtual-machine/env-config-virtualmachine.yaml), die der Packer-Pin-Bot pflegt — hier schließt sich der Kreis zu Abschnitt 2.
  2. Der Katalogeintrag entsteht mit der Ressource, nicht später von Hand.
  3. Git ist die Datenbank, es gibt keine zweite.
  4. CI läuft: Validate Claims (Kyverno-Policies), PR Lint.

Merge.

3.3 Reconcile (4 min) — Argo CD: App showcase-crossplane-xrs → Refresh (statt ~3 min Poll). Der App-Baum zeigt das neue XVirtualMachine. Generalprobe: Merge → XR Ready in 72 s. Parallel im Terminal gegen crossplane-mgmt:

1
2
3
export KUBECONFIG=~/.kube/crossplane-mgmt
kubectl get xvirtualmachines.resources.stuttgart-things.com -A -w
crossplane resource trace xvirtualmachine <name> -n default   # XR → HarvesterVM → Managed Resources

Hier sichtbar machen: Crossplane läuft auf crossplane-mgmt und schreibt über die ClusterProviderConfig harvester in den anderen Cluster — Harvester. Und: Argo CD auf platform verwaltet nur Git → crossplane-mgmt, nicht die VM.

3.4 Die VM (3 min) — Harvester-UI (Namespace vms): PVC wird angelegt, VM bootet, Guest-Agent meldet die IP. Die IP an der Quelle zeigen — die Harvester-UI oder:

1
KUBECONFIG=~/.kube/harvester kubectl get vmi -n vms <name>

status.share.ip am XR hing in der Generalprobe ~10 min hinter Ready (provider-kubernetes pollte alle 10 min); seit #320 pollt er jede Minute, für die Bühne trotzdem nicht darauf warten. Dann per SSH rein. DHCP vergibt IPs neu, known_hosts hat oft einen alten Key für dieselbe IP — vorher ssh-keygen -R <ip> oder auf der Bühne ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ….

„Von Formular abgeschickt bis SSH — und der einzige menschliche Eingriff dazwischen war ein Merge-Klick."

3.5 Rückweg in den Katalog (kurz) — Nach dem Merge öffnet der catalog-bot von selbst den PR catalog-bot/refresh („[catalog-bot]“), der catalog-info.yaml in .backstage/catalog-locations.yaml aufnimmt. Er merged nicht selbst: zeigen („der Katalog pflegt sich per PR“), mergen, dann in Backstage die Location refreshen. Die VM erscheint unter Kind Component, Type crossplane-claim — nicht unter Resources. (Seit dem Backstage-Neustart am 2026-09-30 lädt die Instanz den Index dieses Repos wirklich; vorher hing sie auf dem sthings-dev-Index.)

Deadtime-Trick: Während die VM bootet, den zweiten, vorbereiteten PR mergen (siehe Plan B) oder Abschnitt 4 vorziehen. Nie schweigend auf einen Boot warten.


4 · Day 2 — Löschen ist der ehrlichere Teil (3 min)

Löschen = den Bestell-PR reverten. Im gemergten Bestell-PR auf GitHub „Revert“ → „Create pull request“. Der Revert-PR entfernt genau, was die Bestellung angelegt hat: das Verzeichnis und die Zeile in der Parent-Kustomization. CI grün, mergen → Argo CD prune: true → das XR verschwindet, Crossplane löscht VM, PVC und cloud-init-Secret in Harvester. Generalprobe: 49 s vom Merge bis alles weg ist (#323 revertet #318).

Regeln:

  • Nur den zuletzt gemergten Bestell-PR reverten — alle Bestellungen ändern dieselbe Parent-kustomization.yaml; kam danach eine weitere dazu, meldet der Revert-Button einen Konflikt.
  • Keinen Packer-PR reverten: der Revert ist wieder ein Dev-PR, baut, merged, released und pinnt neu (~15 min). Ein Image-Rollback ist ein Revert des Pin-PRs.

Das Backstage-Template Delete Resource Claim nicht verwenden: sein Picker fragt die Machinery-Registry-API, die nicht deployt ist (404), und das Template ist seit dem Katalog-Wechsel nicht mehr im Katalog. Fallback ohne GitHub-UI:

1
2
3
4
git switch -c chore/delete-<name>
git rm -r clusters/crossplane-mgmt/xrs/virtual-machine/<name>
# Eintrag aus clusters/crossplane-mgmt/xrs/virtual-machine/kustomization.yaml entfernen
git commit -am "chore(crossplane-mgmt): delete VM <name>" && gh pr create --fill

„Es gibt keinen zweiten Weg. Wer das XR von Hand löscht, bekommt es von Argo CD zurück."

Das ist der Abschnitt, den die meisten Self-Service-Demos weglassen — und genau der, nach dem im Betrieb gefragt wird.


5 · Teil 3 — Ein ganzer Rancher-Cluster (5 min)

Derselbe Mechanismus, größeres Objekt: die Bestellung demo-hv (clusters/machinery-hv/xrs/demo-hv.yaml, #311). Je nach Laufzeit aus der Generalprobe (#309 Block 4) live bestellen (zu Beginn zusammen mit Teil 1 mergen, hier die Stufen zeigen) oder den fertigen demo-hv zeigen:

1
2
export KUBECONFIG=~/.kube/machinery-hv
crossplane resource trace clusterstack demo-hv -n default

Stufen: node-ip (Clusterbook) → rancher (Machine-Pool) → VM/k3s → kubeconfig (OpenBao) → access → platform (cilium, Vault-Issuer) → Registrierung in Argo CD.

Generalprobe: Rancher-Cluster angelegt 07:16:57 → VM läuft 07:17:20 → access 07:25:24 → platform 07:26:55 → ready 08:26:14. Die Stunde dazwischen waren drei Blocker, keiner davon Laufzeit: Umzug der Hardware, voller Clusterbook-Pool (#309, .174/.175 ergänzt) und clusterbook-operator v0.20.0 gegen die neuen Network-AppSets (#332). Ohne sie ~15–20 min. Für die Bühne deshalb: demo-hv fertig zeigen, nicht live bestellen. demo-hv war die erste Bestellung mit der eingeschränkten Identität crossplane-machinery und no-creator-rbac: der Rancher-Webhook verlangte get auf das Cloud-Credential (#325). Seitdem legt Rancher den Cluster ohne creatorId an — die Identität wird nicht Owner der Cluster, die sie baut. Das ist selbst eine Folie wert.

Als Ausbau-Stufe daneben: clusters/machinery-hv/xrs/app-dev-hv.yaml. Eine ClusterStack-Bestellung, aus der Crossplane auf machinery-hv einen ganzen Cluster macht: IP aus Clusterbook, VM über den Rancher-Machine-Pool auf Harvester, Kubeconfig nach OpenBao, cilium, Vault-Issuer, Registrierung in Argo CD — danach rollen die AppSets homerun2 und tabletennis aus. Zeigen: die ~50 Zeilen Bestellung, dann app-dev-hv in Argo CD mit seinen Applications und eine laufende App im Browser.

Ehrlich bleiben: für ClusterStack gibt es (noch) kein Backstage-Template — die Bestellung ist ein PR von Hand. Der Weg danach ist derselbe.

„Die Größe des Objekts ändert am Weg nichts. Das ist der eigentliche Gewinn."


Zeitplan

Min Abschnitt Fenster
0–3 Architektur / Inventory Browser: Inventory-Seite
3–7 Layer 0: Blech + Runbook Harvester-UI, docs/install.md
7–11 Teil 1 anstoßen: Dev-Image per Backstage Backstage → GitHub PR → Actions
11–21 Teil 2: VM per PR Backstage → GitHub → Argo CD / Terminal → Harvester
21–23 Delete / Day 2 GitHub, Argo CD
23–27 Teil 3: Rancher-Cluster GitHub, Terminal machinery-hv, Argo CD
27–29 Teil 1 einsammeln: Release, Pin-PR, Image GitHub, Harvester Images
29–30 Lessons Learned Slide

Puffer ist knapp. Bei Zeitdruck fällt zuerst Abschnitt 1 auf zwei Minuten zusammen, dann Delete. Teil 1 muss laufen, Teil 2 auch.


Plan B (vorher aufsetzen, nicht improvisieren)

Risiko Vorbereitung
Argo-CD-Poll (~3 min) Refresh in der App showcase-crossplane-xrs; nie auf den Poll warten
IP fehlt am XR (share.ip: []) IP aus kubectl get vmi -n vms bzw. der Harvester-UI zeigen
SSH: REMOTE HOST IDENTIFICATION HAS CHANGED ssh-keygen -R <ip> vorher, oder -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
Delete-Template / Registry Revert-Button am Bestell-PR (Abschnitt 4), getestet
demo-vm-0 bei Pin-Wechsel durch Teil 1 Getestet: VM läuft weiter, SSH geht, XR bleibt Ready, Argo Synced — nur das Disk-Object wird Synced=False (PVC-Spec unveränderlich). Nach Teil 1 den Trace von demo-vm-0 nicht zeigen, oder als ehrlichen Befund
VM-Boot dauert Zweiter, identischer PR bereits gemergt und die VM läuft — als Schnitt „hier eine, die ich vorbereitet habe"
claim-machinery-api / Backstage nicht erreichbar Das XR aus der Generalprobe kopieren, Name ändern, als PR nach clusters/crossplane-mgmt/xrs/virtual-machine/ — das ist ohnehin die Kernaussage „Backstage ist UI, keine Voraussetzung"
Image fehlt in Harvester Das in env-config-virtualmachine.yaml gepinnte Image (sthings-u26 → default/sthings-u26-<version>) als VirtualMachineImage in Harvester prüfen; sonst harvester-images/import-golden.sh. Achtung private CA, ohne additional-ca schlägt der Download an TLS fehl
Packer-Build zu langsam Nur gemergten PR + fertiges VirtualMachineImage zeigen
Live-Netz/DNS wackelt Screenshots von PR-Diff, Komoplane-Baum und laufender VM in der Hinterhand

Vor der Demo abhaken: das Runbook-Issue #309 durchgehen — Lab hoch, gepinnte Golden-Images in Harvester, Generalprobe-VM läuft, Delete-Weg getestet, Backstage-Katalog frisch, alter Demo-Name aufgeräumt (sonst belegt), Terminal-History, Schriftgrößen.


Lessons Learned — Vorschlag

Aus der Generalprobe (2026-09-30, #309): Die Kette selbst lief. Aufgehalten hat, was zwischen den Repos und Versionen driftet — Backstage lud eine alte Katalog-Config (subPath-Mount, kein Neustart), ein RBAC-Kommentar behauptete das Gegenteil des Webhook-Codes (#325), die Network-AppSets verlangten ein Label, das erst die nächste Operator-Version setzt (#332), und der Clusterbook-Pool war leer. Kandidat für eine eigene Folie.

Der bestehende Slide hat drei Bullets im Ton „Erkenntnis, nicht Feature". In demselben Ton, passend zu dieser Demo. Drei bis vier auswählen — mehr trägt der Slide nicht.

Empfohlene Auswahl (die stärksten vier):

  • Git ist das Interface, der Pull Request ist das Ticket. Self-Service heißt nicht, Entwicklern Zugang zur Virtualisierung zu geben — sondern ihnen den Zugang zu ersparen.
  • Die Governance steckt im Repo-Layout, nicht im Prozessdokument. golden/ ist review-pflichtig, dev/ merged automatisch. Derselbe Mechanismus, unterschiedliche Geschwindigkeit — durchgesetzt von CI, nicht von Disziplin.
  • Der Katalog muss aus derselben Quelle fallen wie die Ressource. catalog-info.yaml entsteht im selben PR wie der Claim. Ein Portal, das nachgepflegt werden muss, ist nach zwei Wochen eine Lügenmaschine.
  • Löschen ist der schwierigere Teil. Ohne prune und ohne Registry bleibt das Blech voll. Erst wenn der Rückweg automatisiert ist, ist es eine Plattform und kein Bestellformular.

Weitere, falls ein Bullet ausgetauscht werden soll:

  • Bare Metal bleibt — es hört nur auf, Handarbeit zu sein. Der Hypervisor-Node ist ein reproduzierbares Artefakt (config.yaml, PXE), kein Gedächtnisprotokoll.
  • Backstage darf keine Voraussetzung sein. Alles, was das Portal kann, kann auch die CLI. Sonst ist das schönste Entwicklerportal der teuerste Single Point of Failure im Rechenzentrum.
  • Ein Renderer, viele Objekte. VM, Namespace, Volume, Ansible-Run, ganzer Cluster — derselbe Weg. Der Wert liegt im Pfad, nicht in der Anzahl der Templates.
  • Die Wahrheit über das Lab gehört ins Repo. IP-Vergabe und Topologie sind generiert und CI-geprüft. Ein Diagramm, das jemand malt, ist beim nächsten Deployment falsch.

Anschluss an den bestehenden Slide

Die drei vorhandenen Bullets (gemeinsame Plattform/API · Virtualisierung bleibt, das Betriebsmodell ändert sich · KubeVirt bringt VMs ins Kubernetes-Modell) sind die Was-ändert-sich-Ebene. Die Bullets oben sind die Wie-betreibt-man-das-Ebene. Wenn beide Slides nebeneinanderstehen sollen: den bestehenden als „Lessons learned — Technologie", diesen als „Lessons learned — Betrieb" führen.