PerformanceBlog

by Anton Pütz | Dipl. Informatiker (Univ.) »

Ich habe einen self-hosted Kubernetes-Cluster von Grund auf gebaut – das steckt wirklich dahinter

Und dann habe ich einen Rust-Microservice darauf deployt, um mein Garagentor per iOS zu öffnen.

Wer über Kubernetes spricht, meint meistens EKS, GKE oder AKS. Managed Cluster, bei denen die Control Plane das Problem von jemand anderem ist. Das ist völlig okay – es ist pragmatisch. Aber es gibt eine andere Kategorie von Herausforderung, über die es sich zu reden lohnt: Das ganze Ding selbst bauen, auf Bare Metal, mit Networking auf Production-Niveau, weil man die volle Kontrolle über den Stack will.

Genau das habe ich getan. Und um zu beweisen, dass der Cluster auch wirklich zu etwas nütze ist, habe ich eine Rust-basierte MQTT-Bridge darauf deployt, mit der ich mein Garagentor über einen iOS-Shortcut öffnen kann.

Lass mich dich durch den Prozess führen, denn trivial ist das Ganze nicht.

Der Cluster

Der Cluster läuft in einem privaten Netzwerk, der API-Server ist unter 10.0.0.21:6443 erreichbar. Kein Cloud-Provider. Keine verwaltete Control Plane. Keine bequemen Wrapper.

Der Admin-Zugriff erfolgt über gegenseitiges TLS (mTLS) mit X.509-Client-Zertifikaten – den Standard-Credentials für kubernetes-admin, die von kubeadm generiert werden. Der Clustername lautet schlicht kubernetes, was schon zeigt: Hier wurde traditionell gearbeitet – mit kubeadm, direkt auf dem System, von Grund auf.

Digitale Souveränität: Warum die Hardware entscheidend ist

Bevor wir in die technischen Details des CNI eintauchen, müssen wir über das „Warum“ sprechen. In einer Welt, in der die „Cloud“ oft nur ein schickes Wort für „der Computer von jemand anderem“ ist, ist der Betrieb auf eigener Hardware der ultimative Schritt zu echter digitaler Souveränität.

  • Datensouveränität: Auf Bare Metal verlassen keine Daten das Haus, ohne dass ich es explizit erlaube. Es gibt keine Blackbox-Provider, keine unklaren Zugriffsberechtigungen durch Drittstaaten (Stichwort: Cloud Act) und kein Scannen von Workloads durch den Infrastruktur-Anbieter.

  • Technologische Freiheit: Wer proprietäre Cloud-Dienste nutzt, baut sich oft einen „Golden Cage“. Ein Wechsel wird durch Vendor Lock-in fast unmöglich. Mit diesem Cluster nutze ich ausschließlich Open-Source-Standards. Der gesamte Stack ist portabel.

  • Operative Autonomie: Ich entscheide über Update-Zyklen, Sicherheits-Patches und die genaue Konfiguration des Kernels. Ich bin kein bloßer Nutzer mehr – ich bin der Eigentümer.

Souveränität bedeutet jedoch auch Verantwortung: Man ist nicht nur der König seines Clusters, sondern auch sein Hausmeister.

Der schwierige Teil: Networking (CNI)

Hier scheitern die meisten Self-Hosting-Versuche. Das Container Network Interface (CNI) ist die Schicht, die die Pod-zu-Pod-Kommunikation über Nodes hinweg ermöglicht. Wenn hier etwas schiefgeht, können Pods nicht miteinander sprechen, Services brechen weg, DNS schlägt fehl und alles fühlt sich wie ein einziges Rätselraten an.

Ich habe mich für Calico entschieden, installiert über den Tigera Operator (v1.40.3).

Warum das eine bewusste Entscheidung war:

Flannel ist das CNI für Einsteiger – ein einfaches VXLAN-Overlay, das überall funktioniert und null Konfiguration erfordert. Aber es bringt Overhead mit sich: Jedes Paket zwischen Pods auf verschiedenen Nodes wird gekapselt, was Latenz verursacht und CPU-Zyklen frisst.

Calico ohne Kapselung ist anders. Es nutzt BGP, um Pod-CIDRs zwischen den Nodes auf der Routing-Ebene bekannt zu machen. Pakete reisen nativ – kein Tunneling, kein Overhead. Aber das setzt voraus, dass dein zugrunde liegendes Netzwerk diese Präfixe auch routen kann. Man muss also Layer-3-Networking verstehen, damit das funktioniert.

Meine Konfiguration:

# IP Pool
cidr: 10.245.0.0/16
blockSize: 26       # 64 IPs pro Node
encapsulation: None # Natives Routing — kein VXLAN, kein IPIP
natOutgoing: Enabled

# BGP
asNumber: 64513
nodeToNodeMeshEnabled: true

Die /26 Blockgröße bedeutet, dass jeder Node ein Subnetz mit 64 Adressen aus dem 10.245.0.0/16 Pool erhält. Ein BGP Full-Mesh sorgt dafür, dass jeder Node direkt mit jedem anderen Node peert und Routen austauscht.

Die Edge: HAProxy als Dual-Stack L4 Proxy

Bevor der Traffic überhaupt den Cluster erreicht, trifft er auf den HAProxy, der auf dem Host läuft. Hier ist die vollständige Konfig:

Code-Snippet

frontend ipv6_http
    bind [::]:80 v4v6
    default_backend envoy_http

frontend ipv6_https
    bind [::]:443 v4v6
    default_backend envoy_https

backend envoy_http
    server envoy1 10.100.234.70:80 send-proxy-v2

backend envoy_https
    server envoy1 10.100.234.70:443 send-proxy-v2

Ein paar Details dazu:

  • Dual-Stack von Anfang an: Clients beider Protokolle (IPv4/IPv6) erhalten denselben Pfad.
  • L4 Passthrough: HAProxy leitet rohe TCP-Streams weiter. TLS wird erst downstream bei Envoy terminiert. Das hält das Setup simpel und extrem performant.
  • PROXY protocol v2: Damit Envoy die echte IP-Adresse des Clients sieht und nicht nur die des HAProxy.

DNS & Gateway API

CoreDNS läuft automatisch in kube-system. Jeder Service ist sofort via DNS-Name erreichbar. Zusätzlich nutze ich die moderne Kubernetes Gateway API über den Tigera Operator. Sie ist der Nachfolger von „Ingress“ und bietet deutlich mehr Kontrolle über das Routing und eine sauberere Trennung der Verantwortlichkeiten.

Die Anwendung: Eine Rust MQTT Bridge

Die eigentliche Anwendung: Mein Garagentor per iOS-Shortcut steuern.

Die Architektur: iOS Shortcut → HTTPS → Envoy (API Key) → Rust Service → MQTT/TLS → Garagentor.

  • Warum Rust? Extrem sicher, minimale Ressourcen (32–128 MB RAM) und blitzschnell.
  • Sicherheit: API-Key an der Edge (Envoy), mTLS zum MQTT-Broker, Secrets-Management in Kubernetes. Der gesamte Round-Trip dauert weniger als 200 ms.

Automatische Image-Updates: Continuous Deployment ohne CI

Der Image Automation Controller ist eines der am meisten unterschätzten Features von Flux. Und so funktioniert er:

  1. ImageRepository scannt alle 5 Stunden eine Container-Registry nach neuen Image-Tags.

  2. ImagePolicy filtert Tags basierend auf einer SemVer-Regel (z. B. 2.14.x für n8n, >9.0.0 für CyberChef).

  3. Wird eine passende neue Version gefunden, editiert ImageUpdateAutomation die Deployment-YAML in Git und committet sie automatisch.

  4. Flux erkennt den Commit und rollt das Update aus.

Der Marker im jeweiligen Deployment sieht so aus:

image: docker.n8n.io/n8nio/n8n:2.14.2 # {„$imagepolicy“: „flux-system:n8n“}

Dieser Kommentar ist nicht nur Kosmetik – Flux parst ihn, um zu wissen, welche Zeile aktualisiert werden muss. Wenn n8n die Version 2.14.3 veröffentlicht, wird innerhalb von 5 Stunden die YAML in Git aktualisiert, der Cluster rollt den neuen Pod aus und beendet den alten. Keine CI-Pipeline erforderlich. Kein manuelles Tagging. Der Cluster hält sich selbst aktuell.

Das deckt alle wichtigen Workloads auf dem Cluster ab – Automatisierungstools, Dokumentenmanagement, Benachrichtigungsdienste, File-Sharing und mehr.

External-DNS + Cloudflare: DNS auf Autopilot

Das war der Teil, der sich beim ersten Mal wie Magie anfühlte. External-DNS (v1.19.x) überwacht Kubernetes Gateway-API-Ressourcen und erstellt automatisch DNS-Einträge in Cloudflare. Die Konfiguration:

provider: cloudflare

sources:
  - gateway-httproute
  - gateway-tlsroute
policy: sync

Die Gateway-Ressource trägt die Annotation für die öffentliche IP (sowohl IPv4 als auch IPv6):

 

annotations:
  external-dns.alpha.kubernetes.io/target: "<ipv4>,<ipv6>"

Wenn ich eine neue HTTPRoute erstelle, die auf einen neuen Hostnamen zeigt, erkennt External-DNS dies, ruft die Cloudflare-API auf und erstellt automatisch die A- und AAAA-Records. Innerhalb von Minuten ist der Hostname weltweit auflösbar.

Durch die TXT-Registry mit einem eindeutigen Präfix schreibt External-DNS Ownership-TXT-Records neben jeden DNS-Eintrag. So überschreibt es niemals Einträge, die ihm nicht gehören, und räumt Einträge sauber auf, wenn Routen entfernt werden. Mehrere Domains über all meine Projekte hinweg – und null manuelle DNS-Edits.

TLS-Zertifikate: Kostenlos, automatisch, immer aktuell

Jeder Dienst im Cluster erhält ein gültiges TLS-Zertifikat. Keine selbstsignierten Zertifikate, sondern echte Let’s Encrypt-Zertifikate, die vor Ablauf automatisch erneuert werden. Hier erledigt der cert-manager (v1.19.x) seinen Job.

Die Konfiguration ist rein deklarativ. Man fügt einfach eine Certificate-Ressource hinzu:

 

apiVersion: cert-manager.io/v1
kind: Certificate
spec:
  dnsNames:
    - myapp.example.com
  issuerRef:
    name: letsencrypt-gateway-prod
  secretName: myapp-tls

Der cert-manager übernimmt den Rest:

  1. Erstellt eine ACME HTTP-01 Challenge.

  2. Routet diese automatisch durch das Envoy Gateway – kein separater Ingress nötig.

  3. Let’s Encrypt validiert den Domainbesitz.

  4. Das Zertifikat wird ausgestellt und als Kubernetes Secret gespeichert.

  5. Erneuerung erfolgt automatisch vor Ablauf.

Kombiniert man dies mit External-DNS, sieht der gesamte Lebenszyklus so aus:

HTTPRoute hinzufügen → External-DNS erstellt DNS → cert-manager stellt TLS aus → Service ist live via HTTPS.

Keine DNS-Konsole. Kein Zertifikatsportal. Keine manuelle Verlängerung. Ein Push zu Git, und die Sache ist erledigt.

Was ich anbiete

Ich baue genau diese Art von Infrastruktur für Produkt-Teams, die ein selbst gehostetes Kubernetes benötigen, ohne dafür ein eigenes, dediziertes Plattform-Team einzustellen.

Falls Sie Workloads betreiben, die Folgendes erfordern:

  • On-Premise oder Private-Cloud-Deployments (Compliance, Datenresidenz, Latenz)

  • Volle Kontrolle über den gesamten Netzwerk-Stack

  • Sicherheit auf Enterprise-Niveau ohne Abhängigkeit von einem bestimmten Cloud-Anbieter (Vendor Lock-in)

Und falls Sie Wert darauf legen:

  • Einen Cluster zu haben, der tatsächlich läuft, überwacht wird und wartbar ist

  • GitOps-gesteuerte Deployments — kein manuelles kubectl apply, kein Configuration Drift

  • Automatisches DNS, TLS und Image-Updates, damit die Plattform ohne manuellen Aufwand (Toil) aktuell bleibt

  • Anwendungen, die nach sauberen Kubernetes-Patterns deployed sind

  • Dokumentation und Runbooks für Ihr Team

Dann brauchen Sie jemanden, der das bereits in der Praxis umgesetzt hat und nicht nur darüber gelesen hat.

Ich habe den Cluster gebaut. Ich habe das CNI konfiguriert. Ich habe die Workloads geschrieben. Ich habe den gesamten Stack abgesichert – von der Edge bis zur Hardware.

Lassen Sie uns darüber sprechen, was Sie gerade bauen.

Dieser Artikel wurde auf English in LinkedIN veröffentlicht.

Optimierung der Finanzverwaltung: Wie ich mit Beancount und LLMs Amazon-Bestellungen effizient analysiere

Seit einigen Jahren nutze ich Beancount, um unsere Familienfinanzen im Blick zu behalten. Beancount ist ein doppelte Buchführungssystem, das auch ...

Sicherer WLAN Betrieb im Haus – Small Office

Mit dem verstärkten Einsatz von Homeoffice sind die IT-Sicherheits – Anforderungen an das eigene Netz größer als davor. Mich stört ...
office_setup

Mein Setup nach einem Jahr Homeoffice

In meinen letzten Artikel hatte ich mein damaliges Setup vorgestellt. Ich habe danach sehr viel ausprobiert und bin mittlerweile mit ...

Creating a Wireguard VPN Server on a Raspberry Pi 4

Update August 9th. 2021: The current Raspi OS versions include Wireguard as a package- so there is no need to ...