Vault als PKI rausgeworfen — eine Offline-CA-Hierarchie mit cert-manager

Vault als interne Zertifizierungsstelle war bequem und ein Single Point of Failure zugleich: Läuft Vault nicht, wird kein Cert mehr ausgestellt. Ich habe die PKI auf eine klassische Offline-Hierarchie umgestellt — Root und Intermediate-Key liegen air-gapped, eine kurzlebige Sub-CA lebt als cert-manager-ClusterIssuer im Cluster, und Name Constraints erzwingen, dass kein Blatt je außerhalb meiner internen Domains signiert wird. Ein staged Cutover ohne Trust-Lücke inklusive.

Lange hat Vault in meinem Lab die internen Zertifikate ausgestellt — eine PKI-Engine, ein cert-manager-ClusterIssuer namens vault, fertig. Bequem. Und ein Single Point of Failure mit Ansage: Ist Vault sealed, abgelaufen oder beim Bootstrap, stellt niemand mehr ein Cert aus. Genau dieses Henne-Ei hat mich schon einmal in eine Break-Glass-Recovery gezwungen. Also habe ich die PKI von Vault gelöst und auf das gestellt, wofür X.509 eigentlich gedacht ist: eine Offline-Hierarchie, deren teuerste Schlüssel nie online sind.

Drei Schichten für ein Secret — von pass über SOPS und Vault in den Pod

In einem öffentlichen GitOps-Repo darf nie ein Klartext-Secret liegen — und trotzdem muss jeder Workload an seine Passwörter kommen. Mein Lab löst das in drei klar getrennten Schichten: SOPS+PGP/age für die paar Bootstrap-Secrets, die vor Vault existieren müssen; Vault KV als Langzeitspeicher für alles Anwendungsnahe; und der External Secrets Operator, der daraus zur Laufzeit Kubernetes-Secrets materialisiert. Eine Tour durch den Lebensweg eines Geheimnisses.

Mein Lab ist ein öffentliches GitOps-Repo — über meinen Radicle -Seed-Node seed.this-is-fine.io kann es jeder klonen. Daraus folgt eine harte Regel: kein Klartext-Secret, niemals, nirgends in Git. Und trotzdem braucht jeder Workload im Cluster seine Passwörter, API-Tokens und Signing-Keys. Diesen Widerspruch löse ich in drei Schichten, von denen jede genau eine Aufgabe hat.

Im Beitrag über die pre-commit-Hooks ging es darum, wie ich verhindere, dass ein Secret versehentlich in Git landet. Hier geht es um den geplanten Weg: wie ein Secret absichtlich von meiner Maschine bis in einen Pod fließt, ohne je unverschlüsselt das Repo zu berühren.

Renovate als Dependency-Bot für ein GitOps-Monorepo

Mein Lab ist ein einziges Flux-Repository — Helm-Charts, Container-Images, Talos- und Kubernetes-Versionen, CLI-Pins in Dockerfiles und shell.nix. Renovate hält das alles aktuell, automerged das Unkritische und zwingt mich bei Ceph, Cilium und Vault zum Hinschauen. Hier ist, wie ich es scharf gestellt habe.

Mein Lab ist ein einziges Repository: ein GitOps-Monorepo, aus dem Flux mehrere Talos-Cluster reconciled. Alles, was eine Version trägt, steht darin — Helm-Charts, Container-Images, die Talos- und Kubernetes-Version, dazu CLI-Pins in einem Dockerfile und in shell.nix. Manuell halte ich das nicht aktuell. Das macht Renovate , und zwar so, dass es mir die langweiligen Bumps abnimmt und mich genau bei den drei, vier Dingen stoppt, bei denen ein blinder Merge teuer wäre.

Ceph auf vier ARM-Boards — das Speicher-Fundament unterm Lab

Fast jeder Lab-Artikel sagt beiläufig ceph-block oder ceph-filesystem — erklärt wurde es nie. Hier ist die Schicht darunter: Rook-Ceph, das die NVMe von vier RK1-Boards zu einem fehlertoleranten Speicher bündelt. Mit der echten CephCluster-Config, dem OSD-pro-NVMe-Mapping, den drei StorageClasses — und den Day-2-Kriegsgeschichten, wenn ein MON-Quorum oder eine OSD wegbricht.

In nahezu jedem Beitrag taucht es beiläufig auf: storageClass: ceph-block, eine RWO-PVC, ein copyMethod: Snapshot. Erklärt habe ich diese Schicht nie — dabei funktioniert ohne sie nichts. Das hier ist das Fundament: Rook -Ceph, das die NVMe-SSDs der Turing-Pi-/RK1-Boards zu einem fehlertoleranten Speicher zusammenfasst.

Cilium ClusterMesh durchs Tailnet — und warum ich es wieder rausgerissen habe

Zwei Talos-Cluster mit Cilium ClusterMesh über das Headscale-Tailnet verbinden — eine Sammlung der Fallstricke, der Erkenntnis, die die Datenebene zu retten schien, und des Outages, der mich am Ende zur Föderation ohne Node-Mesh zurückbrachte.

Im vorigen Beitrag habe ich das tailnet-gateway gebaut — die kleine Brücke, die API, Talos und DNS eines Clusters ins Headscale-Tailnet bringt. Das hier ist die Fortsetzung. Mein Lab hat inzwischen einen zweiten Cluster (cosmos bei Hetzner, neben dem bare-metal hydra), und beide sollten föderiert werden: Pods und Services clusterübergreifend, mit Cilium ClusterMesh .

Die offizielle Anleitung sagt: clustermesh-apiserver per LoadBalancer exponieren, dafür sorgen, dass sich die Knoten beider Cluster direkt sehen, fertig. Genau das wollte ich nicht. Ich habe schon ein verschlüsseltes WireGuard-Fabric über Headscale — die Föderation soll da durch, nicht über öffentliche IPs. Diese eine Entscheidung hat mich durch ein halbes Dutzend Fallstricke geführt. Hier sind sie.