Das Prinzip
OKD (OpenShift Kubernetes Distribution) ist das Community-OpenShift. Single-Node (SNO) packt
Control-Plane und Workload auf einen Server: API (Application Programming Interface) auf 6443, Web-Console + Router auf
443, interne Registry, alle Cluster-Operatoren. Dein oc auf dem Laptop
(Superset von kubectl) spricht über HTTPS (Hypertext Transfer Protocol Secure) mit dem API-Server. Die
Installation selbst läuft in WSL2 – openshift-install
gibt es nur für Linux.
steuert nur, rechnet nicht
alle Pods laufen hier
OKD-Nodes brauchen SCOS (CentOS Stream CoreOS) – ein unveränderliches Container-OS, das der Machine Config Operator selbst verwaltet. Ein normales Ubuntu/CentOS taugt nicht.
Hetzner Cloud hat keinen SCOS-Katalogeintrag und erlaubt keinen
Image-Upload. Lösung: vom Rescue-System per kexec in die
SCOS-Live-Umgebung springen (Ignition als /config.ign ins initramfs eingebettet).
Die Live-Umgebung führt dann bootstrap-in-place aus: temporaere
Control-Plane → coreos-installer schreibt SCOS + fertige Ignition auf
/dev/sda → Reboot in den fertigen Node.
Das macht alles scripts/sno.ps1 – du musst den Ablauf nicht
von Hand fahren (Abschnitt 7 zeigt ihn trotzdem).
Die frühere MicroShift-Fassung ist raus: ihr Installations-Repo
(@redhat-et/microshift-COPR) liefert für CentOS Stream 9 nur noch einen
Build von 2022 mit unauflösbaren Abhängigkeiten. MicroShift wird heute praktisch nur noch
auf RHEL (Red Hat Enterprise Linux) 9.4+ mit Subscription unterstützt – und Hetzner hat kein
RHEL-Image. OKD SNO (Single-Node OpenShift) deckt denselben Zweck ab (voller oc-Workflow, Web-Console)
und wird gepflegt.
01 Voraussetzungen
Der einmalige Teil steht in Server-Basis:
Hetzner-Konto + API-Token, hcloud CLI mit Kontext, SSH-Key bei Hetzner
hinterlegt, oc/kubectl im PATH (Programmsuchpfad).
zusätzlich WSL2
openshift-install läuft nur unter Linux. Einmalig eine WSL2-Distribution
installieren (Neustart nötig):
wsl --install -d Ubuntu
wsl -l -v # muss die Distro mit VERSION 2 zeigen
sno.ps1holtopenshift-install+ocselbst in die WSL (nach~/okd-sno/bin) – nichts weiter einzurichten.- Der WSL-Traffic geht über dieselbe öffentliche IP (Internet Protocol) wie der Laptop – die Firewall-Regel für 6443 passt also automatisch.
- Server-Größe: das offizielle SNO-Minimum ist 8 vCPU / 16 GB
(
cpx42) – das reicht für OKD leer (OKD belegt selbst schon ~11 GB RAM, CPU ist nie das Thema).sno.ps1nimmt darum standardmäßigcpx52(12 vCPU / 24 GB), damit auch eigene Workloads Platz haben.ccx33(8 dediziert / 32 GB) läuft am ruhigsten. Konkrete Auslastung mit einem ganzen Projekt-Stack: Bibliothek-Enterprise per Jenkins auf OKD.
02 Installation mit sno.ps1
Ein Befehl. Danach ~30–45 min warten (die ersten ~5 min mit Rueckfragen bei
neuem SSH-Host-Key, der Rest läuft durch). Server, Rescue, Ignition, kexec,
wait-for – alles automatisch.
.\scripts\sno.ps1 install
# Vorgabe: -Type cpx52 (12 vCPU / 24 GB)
# optional: -Type cpx42 (16 GB, nur OKD leer) | -Type ccx33 (dediziert, ruhiger)
# -Location fsn1 (anderer Standort)
Was das Skript der Reihe nach tut:
- neueste
okd-scos-Version ermitteln,openshift-install+ocin die WSL holen - Firewall
okd-sno-fw(22+6443 nur von deiner IP, 80+443 offen) und eine reservierte Primary-IPv4okd-sno-ipanlegen – die feste IP ist wichtig, weil die API-Zertifikate und dienip.io-Namen an ihr hängen - Server
okd-sno(cpx52, Ubuntu als Rescue-Träger) erstellen, Rescue aktivieren, Hard-Reset (ein Soft-Reboot geht nicht in den Rescue) - in der WSL die
install-config.yaml+single-node-ignition-configerzeugen (mit der festen IP:baseDomain: <ip>.nip.io, Pull-Secret ist ein Dummy – OKD braucht kein echtes) - Ignition +
sno-kexec.shins Rescue hochladen, dort SCOS-Live-Kernel/initramfs holen, Ignition als/config.ign(xz-cpio) anhängen,kexec - vom Laptop aus
openshift-install wait-for install-completegegenapi.sno.<ip>.nip.io:6443 - kubeconfig nach
~\.kube\okd-sno-hetzner.yamlkopieren,KUBECONFIGsetzen, Console-URL + kubeadmin-Passwort ausgeben
Der häufigste Punkt ist zu wenig RAM (auf cpx42) während der
Operator-Konvergenz. oc get clusterversion / oc get co zeigen den
Stand. Oft läuft es mit ein paar Minuten mehr doch durch. Sonst
.\scripts\sno.ps1 destroy und mit dem größeren Vorgabe-Typ
(cpx52) oder -Type ccx33 neu.
Bricht install direkt nach „kexec … auslösen“ ab,
läuft der Server trotzdem schon los. Prüfen: ssh core@<ip>
– zeigt es „CentOS Stream CoreOS“, dann nicht neu installieren, sondern nur
weiterwarten:
wsl -e bash scripts/sno-wsl.sh wait und danach
… sno-wsl.sh finish /mnt/c/Users/<du>/.kube/okd-sno-hetzner.yaml.
03 Was dabei passiert (zum Mitlesen)
Während wait-for läuft, kannst du dem Node zusehen –
.\scripts\sno.ps1 ssh (User core).
| Phase | ca. | woran erkennbar |
|---|---|---|
SCOS-Live bootet, node-image-pull + release-image | ~5–10 min | ssh core@… geht, OS (Betriebssystem) = „CentOS Stream CoreOS“ |
bootkube – temporaere Control-Plane, rendert die Master-Ignition | ~6 min | journalctl -b -u bootkube -f |
install-to-disk – coreos-installer schreibt /dev/sda, dann Reboot | ~1 min | SSH (Secure Shell) bricht ab |
Reboot in den Node, bootstrap-in-place-post-reboot: CSRs, alle Operatoren, CVO | ~20–30 min | API 6443 antwortet, oc get co |
Der Node rebootet dabei ein- bis zweimal selbst (Machine Config Operator). Das ist normal.
04 Cluster prüfen
$env:KUBECONFIG = "$env:USERPROFILE\.kube\okd-sno-hetzner.yaml"
oc whoami # -> system:admin
oc get clusterversion # AVAILABLE=True, "Cluster version is 4.x.x-okd-scos.y"
oc get co # alle 34 auf AVAILABLE=True
oc get nodes -o wide # 1 Node, ROLES control-plane,master,worker, cri-o
.\scripts\sno.ps1 console öffnet
https://console-openshift-console.apps.sno.<ip>.nip.io
und zeigt Login kubeadmin + Passwort (aus ~/okd-sno/cfg/auth/kubeadmin-password
in der WSL). Zertifikat ist selbstsigniert – im Browser einmal bestätigen.
KUBECONFIG dauerhaft: hat install schon per
SetEnvironmentVariable(…,'User') gesetzt – in neuen Terminals aktiv.
Zurücksetzen macht sno.ps1 down / destroy.
05 Erstes Projekt, App & Route
Normaler OpenShift-Workflow – wie im großen Cluster.
oc new-project demo
oc new-app --image=docker.io/library/nginx --name=web
oc expose service/web # -> Route auf *.apps.sno.<ip>.nip.io
oc get route
# -> http://web-demo.apps.sno.<ip>.nip.io (Port 80/443 sind fuer alle offen)
- S2I-Builds,
BuildConfig,DeploymentConfig, SCCs (Security Context Constraints),oc adm– alles da, es ist ein echtes OpenShift. - Auto-Deploy: Manifeste in Git + Argo CD (Continuous Delivery)/Flux (OLM/OperatorHub ist installiert).
- Ganzes Projekt per CI (Continuous Integration): Bibliothek-Enterprise per Jenkins auf OKD – Jenkins baut die Images, pusht in die interne Registry, deployt per Helm (jede Falle dokumentiert).
- Privat statt Route:
oc port-forward svc/web 8080:80.
06 Kosten steuern: down / up
Preise sind Richtwerte. Hetzner rechnet stundenweise ab, solange der Server existiert – herunterfahren spart nichts, nur löschen. Aktuelle Preise + Rechner: Kosten & Budget.
.\scripts\sno.ps1 down # kubelet stoppen -> Snapshot -> Server loeschen (IP + Snapshot bleiben)
.\scripts\sno.ps1 up # Server aus dem Snapshot, gleiche feste IP -> Cluster kommt zurueck
.\scripts\sno.ps1 status # laeuft er? seit wann? ~Kosten? API erreichbar?
.\scripts\sno.ps1 destroy # alles weg: Server, Snapshot, reservierte IP, Firewall
| Posten | €/Std | €/Tag | €/Monat |
|---|---|---|---|
Server CPX52 (12 vCPU / 24 GB, Vorgabe), während er läuft | ~0,138 | ~3,30 | ~100 |
Server CPX42 (16 GB, nur OKD leer), während er läuft | ~0,095 | ~2,28 | ~69 |
reservierte Primary-IP (bleibt bei down) | ~0,0008 | ~0,02 | ~0,60 |
Snapshot des konvergierten Node (~10 GB, bleibt bei down) | – | ~0,005 | ~0,15 |
Nach down laufen nur IP + Snapshot weiter (~0,75 €/Monat). Eine 3-h-Session
auf cpx52 ist ~40 Cent. Warum die feste IP: API-Zertifikate
und alle nip.io-Namen sind auf die IP ausgestellt – mit einer neuen IP müsste
man neu installieren.
.\scripts\create-shortcuts.ps1 -For sno legt Desktop-Icons
OKD-SNO-up/-down/-status an. .\scripts\install-autodown.ps1 -For sno
löscht den Node jede Nacht um 03:00 (Windows-Aufgabenplanung). Der wichtigste Kostenschutz,
weil Hetzner kein Ausgabenlimit hat. Voller Kill-Switch-Überblick:
Server-Snapshot & Restore.
+ Manuell (ohne sno.ps1)
Falls du den Ablauf verstehen oder anpassen willst. Die drei Bausteine im Repo:
scripts/sno.ps1 (Orchestrierung), scripts/sno-wsl.sh (Linux-Teil),
scripts/sno-kexec.sh (Rescue-Teil).
1 WSL: Werkzeuge + Ignition
W=~/okd-sno; mkdir -p $W/bin; cd $W
OKD=4.22.0-okd-scos.8 # neueste: github.com/okd-project/okd/releases/latest
curl -L https://github.com/okd-project/okd-scos/releases/download/$OKD/openshift-install-linux-$OKD.tar.gz | tar xz -C bin
curl -L https://github.com/okd-project/okd-scos/releases/download/$OKD/openshift-client-linux-$OKD.tar.gz | tar xz -C bin oc kubectl
apiVersion: v1
baseDomain: <ip>.nip.io
metadata:
name: sno
compute:
- { name: worker, replicas: 0 }
controlPlane:
name: master
replicas: 1
networking:
networkType: OVNKubernetes
machineNetwork:
- cidr: <ip>/32
platform:
none: {}
bootstrapInPlace:
installationDisk: /dev/sda
pullSecret: '{"auths":{"fake":{"auth":"aWQ6cGFzcwo="}}}'
sshKey: |
<dein id_ed25519.pub>
bin/openshift-install --dir cfg create single-node-ignition-config
# -> cfg/bootstrap-in-place-for-live-iso.ign (+ cfg/auth/kubeconfig, kubeadmin-password)
bin/openshift-install coreos print-stream-json | grep -oE 'https://[^"]+live-(kernel|initramfs|rootfs)[^"]*'
2 Hetzner: Server + Rescue
hcloud primary-ip create --type ipv4 --name okd-sno-ip --location hel1 --assignee-type server
hcloud server create --name okd-sno --type cpx52 --image ubuntu-24.04 `
--ssh-key laptop --firewall okd-sno-fw --location hel1 --primary-ipv4 okd-sno-ip
hcloud server enable-rescue okd-sno --ssh-key laptop
hcloud server reset okd-sno # HARD reset - Soft-Reboot geht nicht in den Rescue
Vier Aufrufe, und der letzte klingt harmloser als er ist:
| Schalter | gehört zu | bewirkt |
|---|---|---|
hcloud primary-ip create --type ipv4 |
zuerst | Reserviert eine öffentliche IPv4-Adresse als eigenständiges Objekt, unabhängig vom Server. Normalerweise gehört die Adresse zum Server und ist mit ihm weg; hier bleibt sie bestehen. Das ist der Grund, warum der Cluster später gelöscht und aus einem Snapshot neu aufgebaut werden kann, ohne dass DNS und Zertifikate nachgezogen werden müssen. Kostet rund 0,50 EUR im Monat, auch während kein Server daran hängt. |
--assignee-type server |
primary-ip | Legt fest, woran die Adresse später hängen darf. Aktuell gibt es nur diese eine Art – die Angabe ist trotzdem Pflicht, sonst lehnt die API den Aufruf ab. |
--location hel1 (zweimal) |
primary-ip, server | Muss an beiden Stellen identisch sein. Eine reservierte Adresse ist an ihren Standort gebunden und laesst sich nicht an einen Server in einem anderen Rechenzentrum hängen – der server create scheitert dann, nachdem die Adresse bereits angelegt (und berechnet) ist. |
--primary-ipv4 okd-sno-ip |
server create | Hängt die eben reservierte Adresse an den neuen Server, statt eine beliebige neue zu ziehen. Der Name aus dem ersten Aufruf, keine Zahl. |
--firewall okd-sno-fw |
server create | Hängt die Firewall schon bei der Erstellung an, damit der Server nicht erst ein paar Minuten ungeschützt im Netz steht. Auffällig ist, was fehlt: kein --user-data-from-file. Das Ubuntu-Image ist hier nur ein Vehikel, um in den Rescue-Modus zu kommen – das eigentliche Betriebssystem wird gleich darüber installiert. |
enable-rescue --ssh-key laptop |
Rescue | Schaltet für den nächsten Start ein Notfall-Linux ein, das aus dem Netz gebootet wird und die Platte unangetastet laesst. Genau das braucht der coreos-installer später: er überschreibt /dev/sda, was aus einem laufenden System heraus nicht ginge. Ohne --ssh-key vergibt Hetzner ein Einmal-Passwort und zeigt es nur einmal an. |
hcloud server reset |
Rescue | Ein harter Reset – das Gegenstück zum Ausschalten am Netzschalter, ohne dass das laufende System etwas davon mitbekommt. Nötig, weil ein sauberes reboot das bestehende System wieder von der Platte startet und den Rescue-Modus schlicht überspringt. Auf einem Server mit Daten wäre derselbe Befehl gefährlich; hier ist die Platte im nächsten Schritt ohnehin dran. Der verwandte Befehl hcloud server reset-password tut etwas völlig anderes – nicht verwechseln. |
3 Rescue: kexec in SCOS-Live
Ignition + scripts/sno-kexec.sh ins Rescue kopieren, dann:
KERNEL_URL=… INITRD_URL=… ROOTFS_URL=… IGN=/root/sno.ign bash /root/sno-kexec.sh
Das Skript installiert kexec-tools, lädt Kernel+initramfs, hängt die Ignition
als /config.ign (xz-cpio) an das initramfs (genau das macht
coreos-installer pxe customize intern – der Container-Weg scheitert im Rescue an
fehlendem BPF) und löst systemctl kexec aus. Ab hier läuft alles selbst.
4 Warten & kubeconfig
bin/openshift-install --dir cfg wait-for install-complete
cp cfg/auth/kubeconfig /mnt/c/Users/<du>/.kube/okd-sno-hetzner.yaml
cat cfg/auth/kubeadmin-password
+ Absicherung
- API + SSH nur über Tailscale –
okd-sno-fwlässt nur noch 80/443 offen (App-Routes). 22 und 6443 sind zu.oc/helm/SSH laufen übers Tailnet (siehe Kasten). Klassisch mit Firewall-Regeln von deiner IP geht auch – dann$TailscaleHost = ''insno.ps1.
Ausnahme: der Hetzner-Jenkins (Jenkins → OKD) ist nicht im Tailnet –jenkins.ps1stanzt daher beimupeine eigene 6443-Regel inokd-sno-fwund räumt sie beimdownwieder weg. Ein lokal betriebener Jenkins braucht das nicht. - kubeadmin ist Voll-Admin – nach dem Einrichten eines eigenen Identity-Providers (htpasswd/OIDC) abschalten. Die kubeadmin-kubeconfig nicht weitergeben.
- SCOS updatet sich über den Cluster (
oc adm upgrade) – keindnfauf dem Node. - SSH zum Node nur als
coremit Key.
curl -fsSL https://tailscale.com/install.sh | sh scheitert auf
dem Node: SCOS ist immutable, kein dnf, keine Paketquellen
(Error: There are no enabled repositories). Der CoreOS-Weg – statische
Binärdateien + systemd-Unit:
VER=$(curl -fsSL 'https://pkgs.tailscale.com/stable/?mode=json' | grep -oE '"Version":"[0-9.]+"' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
curl -fsSL -o /tmp/ts.tgz "https://pkgs.tailscale.com/stable/tailscale_${VER}_amd64.tgz"
sudo tar xzf /tmp/ts.tgz -C /tmp
cd /tmp/tailscale_${VER}_amd64
sudo install -m 0755 tailscale tailscaled /usr/local/bin/
sudo sed 's#/usr/sbin/tailscaled#/usr/local/bin/tailscaled#' systemd/tailscaled.service | sudo tee /etc/systemd/system/tailscaled.service >/dev/null
sudo cp systemd/tailscaled.defaults /etc/default/tailscaled
# sudo hat /usr/local/bin nicht im PATH -> ergaenzen (SCOS-minimal):
echo 'Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' | sudo tee /etc/sudoers.d/10-localpath >/dev/null
sudo systemctl daemon-reload && sudo systemctl enable --now tailscaled
sudo /usr/local/bin/tailscale up --ssh --hostname sno # druckt Login-URL
Danach:
- Im Admin-Panel bei
okd-sno→ Disable key expiry (sonst fällt der Node nach 180 Tagen raus). - hosts-Eintrag auf dem Laptop (Admin-PowerShell), damit der API-Zert-SAN
passt – die kubeconfig bleibt unverändert:
Add-Content $env:SystemRoot\System32\drivers\etc\hosts "<tailscale-ip>`tapi.sno.<ip>.nip.io" $TailscaleHostinsno.ps1auf den MagicDNS-Namen setzen (<cluster>.<tailnet>.ts.net) →ssh/down/status/uplaufen dann über Tailscale.okd-sno-fwauf nur 80/443 setzen:hcloud firewall replace-rules okd-sno-fw --rules-file <json mit 80+443>.- SELinux-Warnung
Tailscale SSH may not work: das--ssh-Feature (Login ohne Key) braucht ein SELinux-Modul, das SCOS nicht hat. Klassischesssh core@<cluster>.<tailnet>.ts.net(key-basiert) geht trotzdem – und reicht fürs „kein offener Port“-Ziel.
Alternativen (rpm-ostree, Container) +
ACLs/MagicDNS/Exit-Node:
Tailscale-Anleitung.
Grundausstattung-Checkliste – 2FA aufs Hetzner-Konto, Tailscale, Backup außer Haus, Uptime-Monitoring, Log-Limits.
+ Grenzen & andere Wege
- Keine HA (Hochverfügbarkeit). Ein Node = ein Ausfallpunkt. Für „echt betreiben“ der mehrknotige OKD-Cluster (Stufe 4).
- RAM statt CPU (Central Processing Unit) ist der Engpass. OKD selbst belegt ~11 GB (CPU
< 1 Kern im Leerlauf).
cpx42(16 GB) reicht nur für OKD leer, Vorgabe ist darumcpx52(24 GB). Mit einem ganzen Projekt-Stack (Beispiel: ~15,6 GB gesamt) istcpx52knapp bequem,ccx33/cpx62entspannt. - Nur Kubernetes, ohne OpenShift-Schicht: die k3s-Anleitung – leichter, schneller, ~512 MB (Megabyte) Overhead.
- Nur
ocüben, kostenlos: die Developer Sandbox (Stufe 0) – gehostetes OpenShift von Red Hat, kein Server. - nip.io hatte schon längere Ausfaelle. Für mehr als ein Lab eine echte
Wildcard-Domain (
*.apps.sno.deine-domain) auf die feste IP zeigen lassen.
| Option | Art | Bemerkung |
|---|---|---|
| ROSA (Red Hat OpenShift Service on AWS) / ARO (Azure Red Hat OpenShift) | managed | OpenShift auf AWS (Amazon Web Services) bzw. Azure, mit Console/OperatorHub. ~$0,17/h Control-Plane + Worker. |
| OpenShift Dedicated | managed | Von Red Hat gehostet auf AWS/GCP (Google Cloud Platform). Ab ~$1k/Monat. |
| CRC / OpenShift Local | lokal | SNO auf dem Laptop (~4 vCPU / 9 GB). Zum Entwickeln, nicht „remote“. |
| Assisted Installer | self-host | Web-UI erzeugt eine Discovery-ISO. Braucht ISO-Boot – bei Hetzner Cloud nicht möglich, daher hier der kexec-Weg. |