Ein Follow-up zum GitOps-Aufbau meines Labs: Wenn das Repository den Zustand beschreibt, brauche ich trotzdem eine Handvoll Anweisungen, um den Cluster zu bedienen — bootstrappen, Secrets rotieren, Backups zurückspielen, Zertifikate erneuern. Lange lebten diese Anweisungen in Makefiles. Heute steckt jede davon in einem go-task -Taskfile, und der Umstieg war eine der besseren kleinen Entscheidungen.
Warum nicht mehr Make#
make ist ein großartiges Werkzeug — für das, wofür es gebaut wurde: Dateien aus anderen Dateien zu erzeugen, anhand von Zeitstempeln. Für operative Anweisungen ist es das falsche Modell. Meine Targets bauen nichts; sie führen Befehle gegen einen Cluster aus. Und genau da reibt sich Make an allen Ecken: bedeutungstragende Tabs, .PHONY-Deklarationen für alles, fummeliges Quoting in Rezept-Zeilen, umständliche Parameterübergabe und eine Lesbarkeit, die mit jedem $(shell …) schlechter wird.
go-task dreht das um. Ein Taskfile ist deklaratives YAML, Parameter und Variablen sind erste Bürger, und es bringt von Haus aus mit, was ich im Cluster-Alltag wirklich brauche: Preconditions, interaktive Prompts vor gefährlichen Schritten, Includes zum Namespacing und sauberes Verschachteln von Tasks. Es ist kein Build-System, das ich zweckentfremde, sondern ein Task-Runner für genau diesen Zweck.
Ein Anweisungs-Verzeichnis mit Namespaces#
Das Root-Taskfile.yaml tut zwei Dinge: Es definiert die geteilten Variablen — Cluster-Name, Kontexte, Pfade zu KUBECONFIG und TALOSCONFIG — und bindet die thematischen Taskfiles als Namespaces ein:
1vars:
2 CLUSTER_NAME:
3 sh: echo "${CLUSTER_NAME:-current}"
4 KUBE_CONTEXT:
5 sh: echo "${KUBE_CONTEXT:-admin@${CLUSTER_NAME}}"
6
7includes:
8 core: .taskfiles/core
9 talos: .taskfiles/talos
10 flux: .taskfiles/flux
11 k8s: .taskfiles/k8s
12 vault: .taskfiles/vault
13 ceph: .taskfiles/ceph
14 backup: .taskfiles/backup
15 tailscale: .taskfiles/tailscale
16 cert-manager: .taskfiles/cert-manager
17 matrix: .taskfiles/matrix
Damit wird task selbst zur Dokumentation — ein nacktes task listet alle Anweisungen mit Beschreibung, gruppiert nach Namespace:
1task talos:bootstrap # Talos-Cluster hochziehen
2task k8s:bootstrap # Kubernetes formen
3task flux:bootstrap # Kontrolle an Flux übergeben
4task vault:bootstrap # Vault initialisieren
5task core:cluster-create # neuen Cluster aus Vorlage scaffolden
6task backup:restore # VolSync-Restore
7task tailscale:gen-authkey # getaggten Pre-Auth-Key minten
8task matrix:recover-e2ee # Matrix-E2EE des Bots wiederherstellen
Der gesamte Onboarding-Aufwand für eine neue Maschine schrumpft damit auf direnv allow && task — der erste Befehl lädt die gepinnte Toolchain aus shell.nix, der zweite zeigt, was es zu tun gibt.
Was ein Task ausmacht#
Im einfachsten Fall ist ein Task nur ein benanntes Bündel Befehle mit Beschreibung — etwa „zeige mir, wo der Cluster vom Soll-Zustand abgewichen ist":
1show-drifts:
2 desc: Show resources that drifted from desired state
3 cmds:
4 - kubectl get events -A --field-selector=reason=DriftDetected
5 --sort-by='.lastTimestamp'
6 -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.involvedObject.name
Spannender sind die Mechanismen, an denen Make scheiterte. Preconditions brechen früh und mit klarer Meldung ab, statt mitten im Lauf zu zerschellen:
1bootstrap:
2 desc: Bootstrap Flux
3 preconditions:
4 - flux check --pre
5 - sh: kubectl config get-contexts "{{.KUBE_CONTEXT}}"
6 msg: "Kubectl context {{.KUBE_CONTEXT}} not found"
7 cmds: [ … ]
Parameter kommen sauber als Variablen herein, ohne Quoting-Akrobatik — hier ein gezieltes Force-Release einer einzelnen Ressource:
1force-release:
2 desc: Forcefully perform a release install/upgrade
3 cmds:
4 - kubectl annotate --field-manager=flux-client-side-apply --overwrite
5 "helmrelease/{{.RELEASE_NAME}}" -n "{{.NAMESPACE}}"
6 "reconcile.fluxcd.io/requestedAt=$(date +%s)"
1task flux:force-release RELEASE_NAME=zot NAMESPACE=zot
Und für die wirklich gefährlichen Anweisungen — alles, was Daten überschreibt oder Identitäten rotiert — schiebt go-task einen Prompt davor, der ohne --yes interaktiv bestätigt werden will:
1recover-e2ee:
2 desc: Recover Matrix E2EE after a crypto-store wipe (mints a NEW device_id)
3 prompt: "Mint a NEW Matrix device + token for the bot and rewrite the secret?"
4 cmds: [ … ]
Genau dieser Prompt rettet einen davon, das Matrix-E2EE-Recovery versehentlich doppelt auszuführen.
Ein grober Abriss, was geht#
Über die Namespaces zieht sich der komplette Lebenszyklus des Labs:
| Namespace | Beispiele | Zweck |
|---|---|---|
talos | bootstrap, cluster-gen-configs | Talos-Knoten ausrollen, Configs erzeugen |
k8s / flux | bootstrap, force-release, show-drifts | Kubernetes formen, Flux steuern |
vault | bootstrap, renew-tokens, gen-cert | Vault initialisieren, Tokens/Zertifikate |
cert-manager | renew-all-certs | TLS-Material erneuern |
backup | list, snapshot, restore | VolSync-Restic -Backups |
tailscale | gen-authkey, prune-stale-gateway | Headscale -Keys & Tailnet |
matrix | recover-e2ee, prune-bot-devices | Matrix-Bot-Wartung |
core | cluster-create, update-gpg-keys | Cluster-Vorlage, SOPS-Recipients |
Der schönste Nebeneffekt: Auch ZeroClaw
führt aus seinem Pod heraus task in einem Repo-Klon aus. Mensch und Agent bedienen den Cluster damit über exakt dieselben Anweisungen — es gibt keine zweite, undokumentierte „so macht man das wirklich"-Ebene.
Fazit#
Der Wechsel von Make zu go-task hat keine einzige Fähigkeit gekostet, dafür aber Lesbarkeit, sichere Parameter, Preconditions und Prompts gebracht. Aus einer Sammlung kryptischer Targets wurde ein durchsuchbares Anweisungs-Verzeichnis, das sich selbst dokumentiert und das ich einem Agenten genauso in die Hand geben kann wie mir selbst. Für ein deklaratives Lab ist das genau die richtige Imperativ-Schicht: klein, explizit und jederzeit per task auffindbar.
