Posts

Mastodon im Lab betreiben (Fediverse-Stack)

Mastodon unter der Haube — PostgreSQL, Cache, Object Storage und Gateway API im GitOps-Lab unter this-is-fine.social.

Mastodon ist ein föderierter Microblog-Server: lokale Timelines plus ActivityPub-Federation zu anderen Instanzen. Betrieb heißt State (PostgreSQL, Medien, Cache) und Edge (HTTPS, /.well-known für Discovery). Im Lab lag die Instanz unter https://this-is-fine.social — Deployment als Helm -HelmRelease über Flux auf dem Primärcluster hydra, im selben Monorepo-Stil wie der GitOps-Überblick.

Manifeste: k8s/clusters/hydra/applications/fediverse-system/mastodon/. Das Apps-Overlay aktiviert Mastodon heute nicht mehr — zu schwergewichtig für eine kleine Instanz auf vier RK1-Modulen; Fediverse läuft mit snac. Der Stack bleibt im Repo als Referenz und für den Vergleich „vorher/nachher“.

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab
Aktueller Betrieb: this-is-fine.social zeigt auf snac, nicht auf Mastodon. Dieser Artikel beschreibt die Mastodon-Architektur, wie sie 2025 im Lab geplant und getestet wurde.

Andere Server finden die Instanz über Hostname und /.well-known. Bei DNS- oder TLS-Drift bricht Federation auch bei healthy Pods — Edge (Gateway API auf 10.103.0.1 aus dem LB-Pool 10.103.0.0/28, cert-manager, DNS) ist First-Class-Abhängigkeit, nicht Nachgedanke.

IRC im Lab: InspIRCd und ein ZNC-Bouncer

Klassisches IRC auf Kubernetes — InspIRCd als Netzwerk, ZNC als persistenter Bouncer für trennende Clients.

IRC bleibt ein einfaches Modell: ein Server, viele Channels, Text über eine langlebige TCP-Session. Auf hydra betreibt InspIRCd das Netzwerk im Cluster; ZNC ist der Bouncer, der eingeloggt bleibt, wenn der Client trennt, und Backlog beim Wiederverbinden liefert.

Beides sind plain Deployments unter Flux in k8s/clusters/hydra/applications/irc/ — InspIRCd und ZNC jeweils mit eigenem Overlay, VolSync-Bundle und Gateway-CRs. Öffentlich: bnc.this-is-fine.social:6697 (TLS). Die Web-UI liegt intern unter bnc.this-is-fine.internal (HTTPRoute auf shared-gateway-internal, LB 10.103.0.2). Repo:

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Clients erwarten langlebige TCP-Sessions und TLS auf einem Nicht-443-Port. Die Gateway API TLSRoute auf Gateway znc-external deckt das ab — IRC in einen HTTP-Ingress zu pressen würde das Protokoll brechen.

Architektur

DaemonRolle
InspIRCdChannels, Benutzer, Server-Links — Netzwerk-Kern
ZNCPersistente Client-Session und Backlog — Bouncer vor InspIRCd

InspIRCd hält Netzwerkname und Channels (TLS-only im Lab: Plaintext-Listener auf 6667 wird aus der Config entfernt). ZNC sitzt vor Menschen: der Client verbindet zu ZNC, ZNC bleibt zu InspIRCd verbunden — über Nacht trennen, ohne Channel-Kontext zu verlieren. In znc.conf zeigt Server = inspircd.irc.svc.cluster.local +6697 auf den Cluster-internen Dienst. Beide nutzen das VolSync-Template (APP = znc bzw. inspircd, Label volsync.backube/privileged-movers: "true").

Cluster-weites Tailscale: Headscale, Tailnet-DNS und Cross-Cluster-Routen

Self-hosted Headscale und wenige Kubernetes-Bausteine — privates WireGuard-Mesh ohne den offiziellen Tailscale-Operator.

Tailscale baut ein WireGuard -Mesh mit minimalem Konfigurationsaufwand — ideal für Laptops, Nodes und APIs, die nicht ins öffentliche Internet gehören. Headscale ist ein offener Control Server für dieselben Clients; Policy und Schlüssel bleiben unter eigener Kontrolle.

Das Lab auf hydra nutzt bewusst nicht den Tailscale Kubernetes Operator . Wenige Deployments und ein gemeinsames tailnet-gateway-StatefulSet pro Cluster reichen — Manifeste unter k8s/clusters/common/applications/tailscale-system/ und k8s/clusters/hydra/applications/tailscale-system/headscale/.

Ziel: ein Tailnet für Laptops und Talos-Nodes ( Talos Tailscale Extension ), Kubernetes-APIs und internes HTTP unter *.tif.internal, ohne diese Namen ins öffentliche Internet zu legen. Teil des GitOps-Labs.

Ziele und Adressraum

Öffentliche Control-Plane-URL: ts.this-is-fine.io (HTTPRoute auf shared-gateway-external). MagicDNS-Suffix: tif.internal (TAILNET_DOMAIN in k8s/clusters/common/flux/vars/common-config.yaml). Adressraum: 100.64.0.0/10 — der übliche Tailscale CGNAT-Bereich .

Tailnet-Topologie

flowchart TB HS[Headscale ts.this-is-fine.io] HS --> Nodes[Talos RK1 + Laptops] HS --> TGW[tailnet-gateway hydra] TGW --> API[K8s API :6443 via socat] TGW --> Talos[Talos API :50000] TGW --> DNS[CoreDNS sidecar :53] DNS --> Magic[*.tif.internal]
                    ┌──────────────────────┐
                    │ Headscale            │
                    │ ts.this-is-fine.io   │
                    └──────────┬───────────┘
                               │ WireGuard
         ┌─────────────────────┼─────────────────────┐
         ▼                     ▼                     ▼
   Talos Nodes           Laptops / Ops          tailnet-gateway
   (Tailscale ext)       (Tailscale client)     (hydra)
         │                     │                     │
         └─────────────────────┴─────────────────────┘
                    MagicDNS: *.tif.internal

Kubernetes-API über das Mesh

Pro Cluster läuft tailnet-gateway in tailscale-system: tailscaled, socat und ein CoreDNS-Sidecar in einem Pod. Tailnet-Clients erreichen die API unter https://hydra-gateway.tif.internal:6443 — Muster <cluster>-gateway.tif.internal — ohne die LAN-VIP (192.168.100.30 auf hydra) oder eine öffentliche Endpoint-URL zu exponieren. Talos-API parallel auf Port 50000 (hydra-wrkr-3.tif.internal bleibt möglich).

VolSync, Volume Snapshots und Restic-Offsite-Backups

CSI-Snapshots, VolSync mit restic-Mover und verschlüsselte Offsite-Backups — Credentials aus Vault über External Secrets.

Stateful Workloads brauchen zwei Dinge: eine konsistente Momentaufnahme auf dem Cluster und eine Kopie außerhalb — falls Ceph, ein Rack oder der ganze Knoten weg ist. Auf hydra (StorageClasses ceph-block, ceph-filesystem, ceph-bucket aus k8s/clusters/hydra/flux/vars/cluster-config.yaml) erledigt das VolSync mit dem restic-Mover: Kubernetes erzeugt einen VolumeSnapshot , VolSync startet einen kurzlebigen Job, restic schreibt deduplizierte, verschlüsselte Blobs ins Remote-Repository. Repository-URL und Passwort liegen in Vault (Primärcluster hydra) und kommen per External Secrets als Secret in den Namespace — nicht als Klartext in Git.

Manifeste: k8s/templates/volsync/, Operator unter k8s/clusters/common/applications/storage-system/volsync/. Das Muster hängt am GitOps-Überblick.

git clone https://seed.this-is-fine.io/z6MQ7ck2rSh7h4qEgbRYg3ftnrGv.git lab

Voraussetzungen

VoraussetzungIm Lab
CSI Snapshot ControllerCommon-Layer unter k8s/clusters/common/applications/storage-system/snapshot-controller
StorageClass mit Snapshot-SupportRook-Ceph ceph-block
VolSync-OperatorEinmalig per Flux-HelmRelease (Chart backube/volsync)
Remote restic-RepositoryBorgBase-Pfad in Vault; pro App eigener Unterordner ${APP}

VolSync beobachtet eine ReplicationSource, triggert per Cron-Schedule und startet Mover-Jobs. Ohne funktionierende VolumeSnapshotClass bleibt copyMethod: Snapshot wirkungslos — der Snapshot-Controller ist keine optionale Deko. Cluster-Default für VOLSYNC_SCHEDULE in hydra: 0 3 * * 0 (Sonntag 03:00) — bewusst Cron-Syntax, kein @weekly (YAML 1.1 behandelt @ als Alias).

Ein Lab, das sich selbst beschreibt: GitOps mit Flux

Wie aus imperativen Skripten ein deklaratives Monorepo wurde, das Soll- und Ist-Zustand zugleich abbildet — und wie Flux die Ressourcen im Cluster verdrahtet

Die Hardware ist nur das Fundament. Was mein Lab eigentlich ausmacht, ist ein einziges Git-Repository, das den kompletten Cluster beschreibt — und zwar nicht als Dokumentation, sondern als verbindliche Quelle der Wahrheit. Dieser Beitrag ist die kurze Geschichte, wie es dazu kam, und wie die Teile heute zusammenspielen.