Zum Inhalt springen
shellyshale

Homelab-Cluster

k3s auf Proxmox, vollständig per GitOps verwaltet. Was im Git steht, läuft; was nicht im Git steht, gibt es nicht.

Vom Commit zum laufenden Dienst
  1. 01GitCode im App-Repo, Manifeste im Cluster-RepoGitLab
  2. 02CIBaut das Image auf einem eigenen RunnerGitLab Runner
  3. 03RegistryImage mit sha- und latest-TagHarbor
  4. 04Argo CDGleicht Cluster und Repo ab, App-of-AppsKustomize
  5. 05k3sWorker als VMs auf zwei Proxmox-HostsNFS-Speicher
  6. 06ZugangIntern über Traefik, öffentlich nur per TunnelCloudflare

Kein Port ist nach aussen offen. Öffentliche Dienste laufen ausschliesslich über einen Cloudflare-Tunnel; alles andere ist nur im Heimnetz erreichbar.

Warum

Ich wollte Dienste, die ich täglich brauche, selbst betreiben, und zwar so, dass ich sie nach einem halben Jahr noch verstehe. Darum gibt es nur einen Weg in den Cluster: über Git. Keine Handgriffe, die nur in meinem Kopf existieren.

Aufbau

Die Knoten sind virtuelle Maschinen auf zwei Proxmox-Hosts. Darauf läuft k3s, verwaltet von Argo CD nach dem App-of-Apps-Muster: ein Verzeichnis pro Anwendung, darin eine Kustomize-Basis und Overlays pro Cluster. Images liegen in einer eigenen Harbor-Registry, Geheimnisse als Sealed Secrets im selben Repo wie alles andere.

Speicher kommt per NFS von einer NAS. Lokale Volumes auf einzelnen Knoten habe ich bewusst ausgeschlossen: Ein Pod soll auf jeden Worker wechseln können, ohne dass seine Daten zurückbleiben.

Ein zweiter Cluster für Kundenseiten hängt am selben Argo CD, siehe Zweiter Cluster.

Was ich dabei gelernt habe

Prune heisst nicht löschen. Entfernt man eine Application aus dem Repo, räumt Argo CD das Application-Objekt weg. Deployment, Service und Namespace bleiben aber stehen, verwaist und für Argo CD unsichtbar, solange der Finalizer fehlt:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: beispiel-site
  namespace: argocd
  finalizers:
    # Ohne diese Zeile ist der Prune nicht kaskadierend.
    - resources-finalizer.argocd.argoproj.io

Ein Helm-Chart mit Zufallswerten rollt bei jedem Rendern. Erzeugt ein Chart Secrets mit zufälligem Inhalt, sieht Argo CD bei jedem Abgleich eine Änderung, und die Pods starten neu. Seither prüfe ich nach Änderungen am Repo, ob solche Dienste noch erreichbar sind, und aktualisiere nie alle Anwendungen auf einmal.

DNS-Platzhalter wirken weiter als gedacht. Eine neue VM ohne passende Suchdomain schickte Anfragen aus Pods an einen Wildcard-Eintrag und damit an den Ingress statt an den eigentlichen Dienst. Die Fehlermeldungen (500, 502) zeigten in eine ganz andere Richtung.

Neustarts ohne Ausfall brauchen eine Reihenfolge: cordon, Deployments neu verteilen, drain, erst dann den Knoten neu starten. Einzelne Replicas, die man vergessen hat (bei mir CoreDNS), merkt man genau dann.

PlatzhalterFoto der Hardware (zwei Mini-PCs, NAS)