← Übersicht  ·  Skripte & Dateien  ·  Heimnetz-Setup · Kubernetes auslagern

Kubernetes Lokal & k3s auf Hetzner

Lerne Kubernetes lokal auf deinem Windows-Laptop (Minikube, Kind, k3d) kennen oder verlagere die Rechenlast auf einen schlanken k3s-Cluster auf Hetzner. Steuerung erfolgt überall identisch per kubectl.

Stand: 1. September 2026 ca. 15–20 Minuten Windows-Laptop (WSL2 / Docker / Hetzner)

Platzhalter einsetzen – werden in allen Befehlen unten ersetzt, nur im Browser, nichts wird gesendet

Das Prinzip

Egal ob lokal auf dem Laptop oder remote auf Cloud-Servern: Du steuerst deine Cluster immer mit demselben Tool: kubectl. Lokal eignen sich Tools wie Minikube, Kind oder k3d. Für die Cloud nutzen wir ein schlankes k3s auf Hetzner via HTTPS (Port 6443).

Dein Laptop kubectl · helm · Minikube / k3d
Lokal testen oder Cloud steuern
Hetzner-Server k3s: API-Server + Scheduler
Pods laufen hier
Blauer Streifen = auf deinem Laptop (PowerShell) Roter Streifen = auf dem Server (bash, erst ssh root@…)

00 Lokale Kubernetes-Installation & Deinstallation

Wenn du Kubernetes erst einmal gebührenfrei auf deinem Laptop testen möchtest, ohne Cloud-Ressourcen zu nutzen, hast du unter Windows (mit Docker Desktop, Podman oder WSL2) drei hervorragende Optionen.

Dieselbe Idee in zwei Größen – und die eine Zeile, für die es in Kubernetes bewusst keine Entsprechung gibt.

Zeichnung aus Diagramme: Hetzner & CRC (21).

compose.yaml – ein Host services: catalog: image: ghcr.io/…/catalog:sha-a1b2 ports: ["8081:8081"] environment: SPRING_PROFILES_ACTIVE: cloud volumes: ["data:/var/lib/data"] depends_on: [db] deploy: { replicas: 1 } restart: always Eine Datei, ein Rechner, ein Befehl. Fällt der Rechner aus, fällt alles aus – und genau das ist oft völlig in Ordnung. Kubernetes – verteilt auf mehrere Objekte Deployment image, replicas, Neustartverhalten Service der Port – clusterintern erreichbar Ingress / Route erst hiermit von außen erreichbar PersistentVolumeClaim aus volumes ConfigMap / Secret aus environment depends_on – gibt es nicht Absicht, kein Versaeumnis image, replicas ports volumes environment depends_on Warum es kein depends_on gibt Weil eine Startreihenfolge in einem verteilten System nichts garantiert. Die Datenbank kann nach dem Start jederzeit wieder wegfallen – wer nur beim Start prüft, hat davon nichts. Kubernetes verlangt stattdessen, dass jeder Dienst mit einem fehlenden Gegenüber umgehen kann: Pod stirbt, wird neu gestartet, versucht es erneut – und die Readiness-Probe hält ihn so lange aus dem Verkehr. In der Praxis kostet das etwas: der config-server aus Zeichnung 10 muss zuerst laufen, sonst gehen acht Dienste in eine Neustart-Schleife und brauchen Minuten, um sich zu fangen. Deshalb steht im Rollout-Skript ein rollout status genau an dieser einen Stelle – von Hand nachgebaut, was compose mit einer Zeile gelöst hätte.
Compose beschreibt einen Rechner, Kubernetes einen Sollzustand. Fast jede Zeile hat eine Entsprechung – nur wird sie auf mehrere Objekte verteilt, die man einzeln ändern kann. Die Ausnahme ist depends_on: was dort eine Zeile ist, wird hier zur Anforderung an den Dienst selbst.

Variante A: Minikube (Der Einsteiger-Standard)

Minikube startet eine vollständige Single-Node-Kubernetes-VM oder einen Container auf deinem Rechner.

Laptop · PowerShell · Installation & Start
# Installation per winget
winget install Kubernetes.minikube

# Cluster starten (nutzt Docker, WSL2 oder Hyper-V)
minikube start

# Status pruefen
kubectl get nodes
minikube dashboard   # Oeffnet das grafische Kubernetes-Dashboard im Browser
Laptop · PowerShell · Deinstallation
# Cluster loeschen & Daten/VM-Dateien bereinigen
minikube delete --all --purge

# CLI-Tool deinstallieren
winget uninstall Kubernetes.minikube

Variante B: Kind (Kubernetes in Docker)

Kind verwendet Docker-Container als Cluster-Nodes. Es ist extrem schnell hochgefahren und ideal für lokale CI/CD-Tests.

Laptop · PowerShell · Installation & Start
# Installation per winget
winget install Kubernetes.kind

# Cluster erstellen
kind create cluster --name lokales-k8s

# Status pruefen
kubectl cluster-info --context kind-lokales-k8s
Laptop · PowerShell · Deinstallation
# Cluster loeschen
kind delete cluster --name lokales-k8s

# CLI-Tool deinstallieren
winget uninstall Kubernetes.kind

Variante C: k3d (k3s in Docker – Perfekt als Vorbereitung auf Hetzner)

k3d verpackt k3s (das wir später auf Hetzner nutzen) in Docker-Container. Damit testest du exakt dieselbe Kubernetes-Distribution lokal.

Laptop · PowerShell · Installation & Start
# Installation per winget
winget install k3d

# Local k3s-Cluster erstellen
k3d cluster create mein-k3s --ports "8080:80@loadbalancer"

# Status pruefen
kubectl get nodes
Laptop · PowerShell · Deinstallation
# Cluster loeschen
k3d cluster delete mein-k3s

# CLI-Tool deinstallieren
winget uninstall k3d
Zwischen lokalen Clustern und Hetzner wechseln

kubectl verwaltest du über Kontexte. So wechselst du beliebig hin und her:

kubectl config get-contexts                  # Alle verfuegbaren Cluster anzeigen
kubectl config use-context minikube          # Auf lokalen Minikube wechseln
kubectl config use-context default           # Auf Hetzner k3s wechseln

01 Voraussetzung: Server-Basis

Konto, API-Token, hcloud CLI, SSH-Key, Firewall-Prinzip und die Skripte – der einmalige Teil steht in einer eigenen Anleitung.

Einmal einrichten

Server-Basis durcharbeiten. Für k3s zusätzlich in der Firewall Port 6443 (kube-API) auf deine IP freigeben. Werkzeuge: kubectl + helm.

02 cloud-init: die k3s-Zeile

cloud-init läuft beim ersten Boot einmalig. Es installiert k3s als Server-Node. --tls-san trägt die öffentliche IP ins API-Zertifikat ein.

Datei · cloud-init.yaml
#cloud-config
package_update: true
runcmd:
  - 'IP=$(curl -s http://169.254.169.254/hetzner/v1/metadata/public-ipv4); curl -sfL https://get.k3s.io | sh -s - server --write-kubeconfig-mode 644 --tls-san "$IP"'

03 Server erstellen

Ein Befehl. Server bootet in Sekunden, k3s ist ~60–90 s später bereit.

Laptop · PowerShell
hcloud server create `
  --name k3s-host `
  --type cpx22 `
  --image ubuntu-24.04 `
  --ssh-key laptop `
  --firewall k3s-fw `
  --user-data-from-file cloud-init.yaml

hcloud server ip k3s-host        # IP anzeigen -> oben ins Feld "Server-IP"

04 Cluster prüfen & Host-Key bestätigen

Laptop · PowerShell
ssh root@<IP> "k3s kubectl get nodes"

05 kubeconfig auf den Laptop holen & KUBECONFIG verstehen

Die kubeconfig sagt kubectl wo der Cluster ist. k3s schreibt sie mit 127.0.0.1 – die tauschen wir gegen die IP.

Laptop · PowerShell
New-Item -ItemType Directory -Force $env:USERPROFILE\.kube | Out-Null

scp root@<IP>:/etc/rancher/k3s/k3s.yaml $env:USERPROFILE\.kube\k3s-hetzner.yaml

(Get-Content $env:USERPROFILE\.kube\k3s-hetzner.yaml) -replace '127\.0\.0\.1','<IP>' |
  Set-Content $env:USERPROFILE\.kube\k3s-hetzner.yaml

$env:KUBECONFIG = "$env:USERPROFILE\.kube\k3s-hetzner.yaml"
kubectl get nodes        # muss den Node zeigen
Was hat es mit KUBECONFIG auf sich?

Die kubeconfig-Datei (YAML) ist der Schlüsselbund für deinen Cluster. Sie enthält drei Hauptbestandteile:

  • clusters: Wo liegt der API-Server? (URL, IP, Port 6443, CA-Zertifikat).
  • users: Wie authentifiziere ich mich? (Client-Zertifikate, Tokens, Passwörter).
  • contexts: Verknüpft einen User mit einem Cluster und einem Default-Namespace.

Die Umgebungsvariable $env:KUBECONFIG: Standardmäßig sucht kubectl unter ~/.kube/config. Mit der Variable legst du fest, welche Konfigurationsdatei genutzt werden soll.

Mehrere Cluster verwalten (Merged Config): Du kannst mehrere Dateien verknüpfen:

$env:KUBECONFIG = "$env:USERPROFILE\.kube\config;$env:USERPROFILE\.kube\k3s-hetzner.yaml"
kubectl config use-context default   # Zwischen Clustern umschalten

06 Erste Workload & Ports

Laptop · PowerShell (→ Cluster)
kubectl create deployment web --image=nginx
kubectl expose deployment web --port=80 --type=LoadBalancer
kubectl get svc web        # EXTERNAL-IP wird die Server-IP (k3s ServiceLB)

07 Updates & Manifeste

Vom Hostnamen bis zum Prozess – und die Stelle, an der die Readiness-Probe darüber entscheidet, ob es klappt.

Zeichnung aus Diagramme: Hetzner & CRC (23).

Der Weg der Anfrage Browser library.apps-crc.testing Route Host → Service, TLS endet hier Service feste ClusterIP, wählt per Label Endpoints nur die BEREITEN Pods catalog-7d9f-x2k · bereit 10.128.2.14:8081 catalog-7d9f-9ab · startet nicht in den Endpoints kein Verkehr Wer die Pods erzeugt Deployment „ich will 1 davon, dieses Image“ erzeugt je Image-Version eines ReplicaSet zählt nach und gleicht ab Pod eine IP, ein oder mehrere Container, gemeinsames Netz Die drei Proben – und was sie steuern startupProbe solange sie läuft, pausieren die anderen beiden – genau das rettete Keycloak mit 5 min 34 Startzeit readinessProbe entscheidet allein über die Endpoints-Mitgliedschaft – schlägt sie fehl, bekommt der Pod keinen Verkehr mehr livenessProbe schlägt sie fehl, wird der Container GETOETET zu knapp gesetzt = endlose Neustart-Schleife Warum ein Rollout ohne Ausfall funktioniert: der neue Pod entsteht, ist aber nicht bereit – also nicht in den Endpoints. Erst wenn seine Readiness-Probe anschlaegt, bekommt er Verkehr, und erst dann verschwindet der alte. Der Service-Name bleibt derselbe, die Pod-IPs wechseln. Und warum „Pod läuft, trotzdem 503“: laufen und bereit sind zwei verschiedene Dinge. oc get pods zeigt Running, die Spalte READY aber 0/1 – dann steht der Pod in keinem Endpoint, und die Route hat niemanden, an den sie weiterreichen kann. Der Servicename ist zugleich der Hostname im Cluster: postgres, kafka, redis-master. Genau deshalb ist fullnameOverride in den Helm-Werten kein Schönheitsfehler, sondern die Voraussetzung dafür, dass die Anwendung ihre Gegenstellen überhaupt findet.
Der Service ist ein Name mit einer Mitgliederliste. Was in dieser Liste steht, entscheidet allein die Readiness-Probe – nicht ob der Pod läuft. Diese eine Trennung erklärt sowohl das Rollout ohne Ausfall als auch die häufigste Verwirrung im Betrieb: ein Pod, der läuft, und trotzdem antwortet niemand.
  • Manifeste versionieren: YAML in einen Ordner, kubectl apply -f .
  • Auto-Deploy auf dem Server: Dateien in /var/lib/rancher/k3s/server/manifests/ legen.
  • k3s aktualisieren: ssh root@<IP> "curl -sfL https://get.k3s.io | sh -"

+ Was Kubernetes übernimmt & Vergleich mit Alternativen

Keine Befehle, die ausgeführt werden – nur Schleifen, die vergleichen. Das ist der ganze Trick.

Zeichnung aus Diagramme: Hetzner & CRC (22).

kubectl / helm auf deinem Rechner „so soll es sein“ HTTPS, 6443 Control Plane API-Server prüft, nimmt an, benachrichtigt etcd der einzige Ort, an dem etwas steht Nur der API-Server spricht mit etcd. Alles andere fragt ihn – deshalb ist er der einzige Punkt, an dem Rechte, Prüfungen und Protokollierung greifen. Scheduler sucht für jeden Pod einen Node Controller Deployment, ReplicaSet, Node … Der Regelkreis – jeder Controller macht nur das Soll lesen → Ist ansehen → Differenz ausgleichen → von vorn Node kubelet fragt: was soll auf mir laufen? CRI-O – die Container-Runtime bei OKD; anderswo containerd deine Container Das kubelet bekommt keine Befehle geschickt – es fragt selbst und gleicht selbst ab. Auch das ist ein Regelkreis, nur einer pro Node. Was aus dem Regelkreis folgt – und alles erklärt, was einen anfangs verwirrt kubectl apply kommt sofort zurück Es hat nur den Wunsch hinterlegt. Ob er erfüllt wird, entscheidet sich danach – und vielleicht nie. Deshalb gibt es rollout status und --wait: sie warten, bis das Ist dem Soll entspricht. Ein gelöschter Pod kommt zurück Nicht aus Trotz: das ReplicaSet zählt weiter eins und sieht null. Also legt es einen an. Wirklich weg ist er erst, wenn das Soll sich ändert – also am Deployment. Handänderungen verschwinden Der Controller kennt nur sein Soll. Was du am Objekt änderst, gleicht er beim nächsten Durchlauf wieder aus. Dasselbe gilt für Helm – siehe Zeichnung 26. Warum das auf CRC besonders weh tut Control Plane und Node sind hier dieselbe Maschine. Die acht JVM-Kaltstarts aus Zeichnung 10 haben also nicht nur die Anwendung ausgebremst, sondern API-Server und etcd gleich mit – und damit genau die Instanz, die man braucht, um nachzusehen, was los ist. Der Ausweg in genau dieser Lage: crictl auf dem Node. Es spricht direkt mit CRI-O und geht am API-Server vorbei – man sieht die laufenden Container also auch dann noch, wenn kubectl in Zeitüberschreitungen läuft. Es kann dafür nichts vom Soll wissen. Umgekehrt gilt: ein Node unter Druck bekommt vom Node-Controller eine Markierung wie DiskPressure – und der Scheduler plant dann nichts Neues mehr dorthin. Pods bleiben in Pending, ohne dass an ihnen selbst etwas falsch wäre.
Kubernetes führt nichts aus – es gleicht ab. Du hinterlegst einen Sollzustand, und mehrere unabhängige Schleifen vergleichen ihn unablässig mit dem Ist. Wer das einmal verinnerlicht hat, wundert sich nicht mehr, warum ein gelöschter Pod zurückkommt, warum ein Befehl sofort zurückkehrt, obwohl nichts fertig ist, und warum Handarbeit am Objekt nicht hält.
ObjektRolle
Podkleinste Einheit – 1+ Container, die zusammengehören
Deployment„halte N Repliken dieses Pods am Leben“
Servicestabile Adresse + Lastverteilung
IngressHTTP-Router von außen rein

Kubernetes (k8s / k3s) im Vergleich zu anderen Orchestrierungs-Lösungen

Eigenschaft Docker Compose Docker Swarm Kubernetes (k8s / k3s) HashiCorp Nomad Serverless / PaaS (Cloud Run)
Zielgruppe Lokale Dev-Umgebung, Single-Node Einfache Multi-Node Cluster Komplexe, skalierbare Multi-Node Workloads Flexibler Orchestrator (Container & VMs) Zero-Ops, Event-Driven Webapps
Komplexität Sehr niedrig Niedrig Hoch (bei k3s mittel) Mittel Sehr niedrig (nur Code/Container liefern)
Multi-Node / HA Nein (nur 1 Host) Ja (integriert) Ja (Standard) Ja (sehr performant) Cloud-Provider übernimmt alles
Auto-Healing & Scaling Nur Container-Neustart Begrenzt (Replication) Sehr stark (HPA, VPA, Node-Autoscaling) Gut (über externe Telemetrie) Automatisch bis auf 0 Skalierbar
Ökosystem Klein Mittel (stagniert) Riesig (Standard-De-Facto, Helm, Operators) Mittel (HashiCorp-Welt) Anbieter-gebunden (Lock-in)
Ressourcenbedarf Minimal Gering K8s: hoch (~2GB RAM) / k3s: gering (~512MB RAM) Sehr gering N/A (Pay-per-Use)

Fazit: Docker Compose reicht für einfache Setups auf einem Server. Sobald du aber Ausfallsicherheit, automatisches Rollout, feingliedriges Scaling, selbstheilende Container und breite Tool-Unterstützung (CI/CD, Monitoring, Service Mesh) auf mehreren Servern brauchst, führt kein Weg an Kubernetes (k3s) vorbei.

+ Mehrere Nodes

Laptop · PowerShell
ssh root@<IP> "cat /var/lib/rancher/k3s/server/node-token"

+ Kosten steuern

Hetzner rechnet stundenweise ab. Bei Nichtgebrauch Server löschen oder Snapshots nutzen.

+ Absicherung

  • API nur von deiner IP – durch Firewall schützen.
  • Node-Token geheim halten.

+ Kill-Switch: Vergessen darf nichts kosten

Laptop · PowerShell
.\scripts\k3s.ps1 up       # aus letztem Snapshot, kubeconfig holen
.\scripts\k3s.ps1 down     # stoppt k3s, macht Snapshot + loescht Server

+ Andere Anbieter

k3s läuft auf jeder Linux-VM (DigitalOcean, Scaleway, Linode, AWS etc.).

⌂ Cockpit