← Übersicht  ·  Skripte & Dateien  ·  Lokal · Betrieb & Diagnose

OpenShift Local auf Windows

CRC bringt einen vollständigen OpenShift-Single-Node auf den Laptop – in einer Hyper-V-VM, mit echter Registry, echten Routen, echtem oc. Das ist erstaunlich nähe am Original und erstaunlich empfindlich gegenüber drei Dingen: zu wenig Platte, zu langsamer Speicher und zu wenig CPU. Diese Seite sammelt, was beim Betrieb tatsächlich weh tut – unabhängig davon, welches Projekt darauf laufen soll.

Stand: 2. September 2026 CRC 2.63 · OKD 4.22 · Preset okd Windows 10 Pro · Hyper-V

01 Dimensionierung

Die Vorgabewerte reichen für einen leeren Cluster. Sobald etwas darauf laufen soll, nicht mehr.

Drei Maschinen auf einem Laptop, die sich gegenseitig für entfernte Rechner halten.

Zeichnung aus Diagramme: Hetzner & CRC (09).

Windows-Host CRC-VM · Hyper-V OKD 4.22 · Router · interne Registry image-registry.openshift-image-registry .svc:5000 – zum Ziehen, clusterintern default-route-openshift-image-registry .apps-crc.testing – zum Schieben, von außen Zwei Adressen für dieselbe Registry. Wer sie verwechselt, sucht lange. WSL2 · podman-machine-default baut die Images, schiebt sie in die Registry NAT, kein Mirrored-Networking – also eine eigene Maschine mit eigenem Loopback /etc/hosts auf 127.0.0.1 zeigt hier ins Leere Port 443 alle Interfaces Port 6443 nur Loopback TCP über die WSL-Gateway-Adresse, z. B. 172.25.224.1 HTTPS auf 443 · Adresse aus der Default-Route der WSL-Maschine und kann sich nach einem WSL-Neustart ändern × Der rote Strich ist die Falle: Podman in WSL sieht das 127.0.0.1 des Windows-Hosts nicht – es ist aus dieser Sicht eine andere Maschine. Dass Port 443 auf allen Interfaces bindet, ist der Grund, warum der grüne Weg überhaupt existiert; 6443 bliebe auch so unerreichbar. Deshalb steht in der /etc/hosts der WSL-Maschine die Gateway-Adresse – und ein alter Eintrag auf 127.0.0.1 muss dort weg, sonst gewinnt er.
Drei Netzwelten auf einem Gerät. Die CRC-VM, der Windows-Host und die WSL-Maschine sind füreinander entfernte Rechner – localhost bedeutet in jeder etwas anderes. Der grüne Weg funktioniert nur, weil CRC den Port 443 nicht auf Loopback beschränkt.
EinstellungVorgabePraxiswertWoran man merkt, dass es zu wenig ist
memory1075216384Bei 10,5 GB sind 93 % des Node-Speichers schon von OpenShift selbst angefordert. Neue Pods bleiben Pending mit Insufficient memory.
disk-size3145+Node bekommt den Taint disk-pressure, laufende Pods werden verdrängt. Der leere Cluster belegt allein schon rund 23 GB.
cpus46JVM-Kaltstarts dauern Minuten; siehe Diagnose.
Laptop · PowerShell
crc config set memory 16384
crc config set disk-size 45
crc config set cpus 6
crc config view

# Alle drei greifen erst beim naechsten Start:
crc stop; crc start
Die Disk wächst, sie schrumpft nicht

disk-size lässt sich jederzeit erhöhen – CRC vergrößert die VHDX und die Partition beim nächsten Start automatisch. Zurück geht es nicht ohne crc delete. Der Wert ist zugleich das Minimum 31; kleiner lässt CRC nicht zu, weil das Bundle-Image selbst schon so groß ist.

02 Was auf die SSD gehört

Nicht alles unter ~/.crc ist gleich empfindlich.

VerzeichnisGrößeZugriffWohin
~/.crc/machines/~30 GBständig, wahlfrei – hier liegt die VM-DiskSSD
~/.crc/cache/~35 GBnur beim Start und beim Entpacken, sequentiellHDD reicht
~/.crc/bin/< 1 GBseltenegal
Was passiert, wenn die VM-Disk auf einer Festplatte liegt

Gemessen mit der VHDX auf einer 2,5-Zoll-HDD:

VM-Disk auf HDDVM-Disk auf NVMe
iowait beim Start62–74 %0–9 %
Load (6 vCPU)bis 25Spitze 20, dann ~2
crictl images22 s< 1 s
Start bis Running~45 Min, mit Handeingriff~6 Min

Auf der HDD meldet crc start zuverlässig cluster operators are still not stable after 11m, und die Pods hängen reihenweise in ImageInspectError: DeadlineExceeded. Das sieht nach einem kaputten Cluster aus, ist aber nur CRI-O, das seine Zeitfenster nicht einhält, weil die Platte nicht hinterherkommt.

03 Cache auslagern – und die Junction-Falle

Wenn die SSD zu klein ist, kann der Cache auf die große Platte. Aber nicht bedingungslos.

CRC kennt keine Konfiguration für einen abweichenden Cache-Pfad. Der übliche Weg ist deshalb eine Junction – ein Verzeichnis-Verweis auf NTFS-Ebene, den CRC gar nicht bemerkt:

Laptop · cmd oder PowerShell
# Cache auf die grosse Platte verlagern, machines/ bleibt auf der SSD
robocopy "%USERPROFILE%\.crc\cache" "D:\OpenShift-Local\.crc\cache" /E /MOVE
mklink /J "%USERPROFILE%\.crc\cache" "D:\OpenShift-Local\.crc\cache"
Falle: Der Bundle-Download scheitert an genau dieser Junction

Seit CRC 2.63 werden Bundles als OCI-Artefakt geladen, nicht mehr per HTTP. Die dahinterliegende Bibliothek legt ein temporäres Zielverzeichnis im Cache an – und das schlägt in einem Reparse-Point fehl:

level=error msg="invalid destination name C:\Users\...\.crc\cache\tmpBundleImage3964368163:
The system cannot find the path specified."

Lesen und Entpacken durch die Junction funktionieren einwandfrei – nur das Anlegen bricht. Der laufende Betrieb ist also nicht betroffen, sondern ausschließlich der einmalige Bundle-Download. Vorgehen bei einem Versions-Update:

  1. Junction entfernen – rmdir ohne /s löst nur den Verweis, die Daten auf der anderen Platte bleiben
  2. Echtes cache-Verzeichnis auf der SSD anlegen (braucht ~35 GB frei)
  3. crc setup laufen lassen
  4. Cache zurück auf die große Platte kopieren, verifizieren, Quelle löschen
  5. Junction neu setzen
Nie crc cleanup, nie den Uninstaller

Beide arbeiten auf Deinstallations-Ebene und können ~/.crc mitsamt Junction entfernen. Ein anschließendes crc setup legt dann ein echtes Verzeichnis auf der Systemplatte an und zieht rund 35 GB dorthin. crc delete ist dagegen unbedenklich – es läuft durch die Junction und lässt sie stehen. Der MSI-Installer eines Versions-Updates fasst das Verzeichnis ebenfalls nicht an.

04 Der Pod, der die Platte frisst

Ein Defekt in dieser OKD-Version, der reproduzierbar den ganzen Cluster lahmlegt.

openshift-console/downloads schreibt fortlaufend Temporärdateien in sein emptyDir und räumt sie nie auf. Gemessen: 17 GB in 3,5 Stunden, verteilt auf mehrere Dateien zu je 3,1 GB – also rund 5 GB pro Stunde. Irgendwann greift DiskPressure, der Node bekommt einen NoSchedule-Taint, und laufende Pods werden verdrängt.

Das Symptom sieht völlig anders aus als die Ursache: Pods, die eben noch liefen, stehen plötzlich auf Pending mit node(s) had untolerated taint(s).

Laptop · bash · Verursacher finden
# Groesstes emptyDir in der VM aufspueren
ssh -i ~/.crc/machines/crc/id_ed25519 -p 2222 core@127.0.0.1 \
  "sudo du -xh --max-depth=1 /var/lib/kubelet/pods | sort -rh | head -5"

# Pod-UID aus dem Pfad einem Namen zuordnen
oc get pods -A -o jsonpath='{range .items[*]}{.metadata.uid}{"  "}{.metadata.namespace}{"/"}{.metadata.name}{"\n"}{end}' \
  | grep <uid>
Laptop · bash · dauerhaft stilllegen
# Ohne Unmanaged skaliert der Console-Operator das Deployment sofort zurueck
oc patch console.operator.openshift.io/cluster --type=merge \
  -p '{"spec":{"managementState":"Unmanaged"}}'
oc scale deploy/downloads -n openshift-console --replicas=0
Was das kostet

Der Pod liefert in der Web-Konsole nur die Downloadseite für oc, kubectl und verwandte Werkzeuge. Auf einem lokalen Entwicklungscluster kostet sein Abschalten nichts – und es gibt sofort 17 GB zurück. Die Web-Konsole selbst läuft weiter.

05 Diagnose: Load richtig lesen

Dieselbe Kennzahl, zwei völlig verschiedene Ursachen.

Der Load Average unter Linux zählt Prozesse, die laufen, auf einen Kern warten – oder in ununterbrechbarem Warten stecken, praktisch immer auf Platten-I/O. Genau deshalb sagt die Zahl allein noch nicht, woran es klemmt. Der Bezugspunkt ist die Kernzahl: Bei 6 vCPUs bedeutet Load 6 volle Auslastung ohne Warteschlange.

Laptop · bash · in die VM schauen
# CRC 2.6x hat kein "crc ssh" - direkt ueber den Bundle-Schluessel
ssh -i ~/.crc/machines/crc/id_ed25519 -p 2222 \
    -o StrictHostKeyChecking=no core@127.0.0.1 \
    "cat /proc/loadavg; vmstat 1 2 | tail -1; df -h /var | tail -1"

Entscheidend sind die letzten Spalten von vmstat:

BeobachtungLoadvmstatUrsacheWas hilft
Start dauert ewig, VM-Disk auf HDD20+ wa=74  us=6 Platten-I/OVM-Disk auf SSD
Viele JVMs starten gleichzeitig157 wa=0  us=64  sy=35  id=0 CPU-Sättigunggestaffelt starten
Wenn der Cluster nicht mehr von allein zurückkommt

Bei anhaltender CPU-Sättigung verhungert der kube-apiserver (TLS handshake timeout). Dann schlagen die Probes fehl, Liveness tötet Pods, jeder Neustart erzeugt einen weiteren Kaltstart – eine Rückkopplung, die sich selbst nährt. In diesem Zustand läuft sogar crictl stop in Timeouts.

Ausweg: crc stop, crc start, und während der Cluster hochfährt die Anwendungs-Deployments auf 0 skalieren, sobald die API antwortet. Danach einzeln hochfahren.

06 Auslastung protokollieren

Weil die interessanten Zustände meist dann auftreten, wenn man nicht hinschaut.

Nützlich sind Host- und VM-Werte nebeneinander – und in der VM ausdrücklich iowait, weil erst das die beiden Fälle aus Abschnitt 05 trennt. Ein PowerShell-Skript, das im Minutentakt eine CSV schreibt, reicht dafür völlig.

SpalteWoherWozu
Host-CPU, Host-RAMWMIMerkt man, wenn Windows selbst knapp wird
freier Platz je LaufwerkGet-PSDriveVHDX-Wachstum im Blick behalten
VM-CPU, zugewiesenes RAMGet-VMWas Hyper-V der VM tatsächlich gibt
Load, us/sy/id/wa, /varSSH in die VMDer eigentliche Diagnosewert
fehlerhafte Pods und OperatorenocWirkung auf den Cluster
Zwei Stolpersteine beim Auslesen

vmstat darf man nicht über feste Spaltenpositionen auswerten: Neuere procps-ng-Versionen hängen hinter st noch eine Spalte gu an, wodurch alle Indizes verrutschen. Die Spalten über die Kopfzeile auflösen.

Und der Hyper-V-Speicher der VM taucht auf dem Host nicht als Prozess auf – ihn liefert nur Get-VM, nicht Get-Process.

⌂ Cockpit