Allgemein Tech

Espejote: Eine GitOps-Reise

14. Apr. 2026

Espejote (grosser Spiegel auf Spanisch) verwaltet beliebige Ressourcen in einem Kubernetes-Cluster. Von Grund auf entwickelt, um Server-Side Apply und Jsonnet-Templating optimal zu nutzen.

Wir betreiben bei VSHN eine grosse Anzahl von Kubernetes-Clustern für unsere Kunden und wir versuchen, so viel wie möglich zu automatisieren, um unsere Betriebsabläufe effizient und nachhaltig zu gestalten. Wir nutzen GitOps-Prinzipien, aber manchmal muss externer Zustand mit dem in Git definierten Zielzustand zusammengeführt werden. Diese GitOps-Reise führte uns von Ansible-Playbooks, die direkt YAML anwenden, über verschiedene Operatoren, hin zu Bash-„Reconcilern“ und schliesslich zu Espejote, unserem neuen GitOps-Operator.

Kapitel 1: Die Ansible-Ära und erste Operator-Versuche

Am Anfang nutzten wir Ansible-Playbooks und eigene Rollen, um unsere OpenShift 3 Kubernetes-Cluster zu verwalten. Wir hatten eine Sammlung von YAML-Dateien, die den gewünschten Zustand unserer Cluster definierten und führten Ansible-Playbooks aus, um diese auf die Cluster anzuwenden. Das funktionierte, war aber nicht besonders effizient. Wir mussten die Playbooks manuell ausführen, und wenn wir das vergassen, drifteten die Cluster vom gewünschten Zustand ab.

Die Sammlung von Rollen erhielt den Spitznamen „mungg“, das schweizerdeutsche Wort für „Murmeltier“. Niemand weiss genau warum, aber der Name blieb.

Wir begannen gerade erst damit, Operatoren zu entwickeln und entwickelten espejo, um Ressourcen schnell zwischen Namespaces zu synchronisieren. Es war die sehr frühe Phase unserer Operator-Reise.

Kapitel 2: Das Meer der Operatoren und der Tränen

Um das Problem der manuellen Eingriffe zu lösen (und weil wir auf OpenShift 4 migrierten, wo das Installationsverfahren kein Ansible mehr nutzt), begannen wir, uns mit Kubernetes-Operatoren zu beschäftigen. So schwer kann es doch nicht sein, ein Kubernetes-Manifest zu patchen. Oder? Falsch.
Einige Operatoren waren fehlerhaft, andere nicht flexibel genug, manche gerieten zufällig in Reconcile-Loops und die meisten verbrauchten zu viele Ressourcen. Einige brachten sogar unsere API-Server zum Absturz. Wir begannen mit resource-locker-operator, migrierten zu patch-operator, verursachten Ausfälle mit Kyverno und testeten alle Policy-Engines, die wir finden konnten. Kubewarden war die einzige, die uns wirklich gefiel, aber die Cluster-Context-API war noch nicht flexibel genug für unsere Anwendungsfälle.

Espejo war ein guter Anfang, aber wir hatten noch nicht die Erfahrung, gut designte Operatoren zu bauen. Das zeigte sich. Jedes Event löste eine vollständige Reconciliation aller Ressourcen aus, wodurch die Synchronisation in grösseren Clustern drastisch langsamer wurde. Uns fehlte viel Flexibilität.

Kapitel 3: Verzweifelte Suche nach stabilen Landungen

Wir hatten genug von den ständigen Bugs und Breaking Changes in Kyverno und patch-operator wurde kaum gepflegt. Espejo war an seine Grenzen gestossen.

Verzweifelte Zeiten erfordern verzweifelte Massnahmen, also begannen wir, eine Mischung aus Bash-„Reconcilern“ – Hacks mit Cronjobs, kleinen eigenen Controllern und Vorverarbeitung von Ressourcen in Project Syn.

Wir nutzten zunehmend Jsonnet. Project Syn-Komponenten verwenden hauptsächlich Jsonnet. Wir setzen Jsonnet für unseren cloudscale machine-api provider, für unsere SSO-Lösung und viele weitere Projekte ein.

Ein wachsendes Problem waren unsere stark angepassten OpenShift-Alerting-Regeln. Wir kuratieren Upstream-Regeln und aktivieren nur diejenigen, die wir benötigen. Einige sind stark modifiziert. Mit jeder OpenShift-Version werden die Upstream-Definitionen verschoben und sind teilweise nur noch in Go-Code eingebettet verfügbar. Wir brauchten etwas, das bereits im Cluster deployte Regeln patchen kann, da dies die einzige stabile Schnittstelle war.

Kapitel 4: Espejote, der neue GitOps-Operator

Mit wachsender Operator-Erfahrung und unserer Begeisterung für Jsonnet entschieden wir uns, unseren eigenen Operator zu bauen – einen, der alles kann. Wir wollten etwas Flexibles, Effizientes und Einfaches. Etwas, das all unsere Anwendungsfälle abdeckt, vom Synchronisieren von Ressourcen zwischen Namespaces bis hin zum Patchen von OpenShift-Alerting-Regeln.

Espejote ist das Ergebnis dieser Reise. Es kombiniert Cluster-Zustand mit GitOps-Prinzipien und nutzt Jsonnet zur Definition des gewünschten Zustands. Es cached den Cluster-Zustand effizient, und die Reconcile-Trigger-Logik ist explizit definiert. Sinnvolle controller-runtime Rate Limits kommen zum Einsatz. Jsonnet bietet enorme Flexibilität, und natives Server-Side Apply macht das Hinzufügen und Entfernen von Schlüsseln einfach. Jeder Espejote „Resource Manager“ – also der dynamische Controller pro Konfigurationseinheit – nutzt eine eigene ServiceAccount für Least Privilege.

Espejote ist genau der Operator, den wir immer wollten und wir freuen uns, ihn mit der Welt zu teilen.

Was ist Espejote?

Espejote ist ein Kubernetes-Operator, mit dem du beliebige Ressourcen in einem Kubernetes-Cluster verwalten kannst. Er kombiniert GitOps-Prinzipien mit dem Zustand im Cluster.

Warum Espejote?

Es gibt viele ähnliche Tools (und Policy-Engines), aber Espejote hebt sich durch drei zentrale Säulen ab:

1. Powered by Jsonnet

Espejote nutzt Jsonnet als Templating-Engine. Im Gegensatz zu YAML kombiniert mit Go-Templates behandelt Jsonnet Konfigurationen als Datenstruktur. Es versteht Objekte, Arrays und Strings. Es kann nicht versehentlich fehlerhaftes YAML erzeugen, da Jsonnet sicherstellt, dass die interne Datenstruktur gültig ist, bevor überhaupt eine Datei exportiert wird.

2. Native Server-Side Apply

Espejote wurde von Grund auf für Server-Side Apply (SSA) entwickelt. Das bedeutet, dass Espejote gut mit anderen Controllern und Operatoren zusammenarbeitet. Es kann einzelne Annotationen oder komplette Ressourcen verwalten – SSA sorgt dafür, dass Änderungen sauber zusammengeführt werden, ohne andere Tools zu überschreiben.

3. Zuverlässigkeit

Zuverlässigkeit ist kein nachträglicher Gedanke. Espejote entstand aus der Frustration über Operatoren, die in Endlosschleifen geraten oder Cluster zum Absturz bringen. Es bietet:

  • Sinnvolle Rate Limits und Backoff-Strategien.
  • Jede Konfigurationseinheit („Resource Manager“) läuft als eigener, dynamisch erzeugter Controller, sodass fehlerhafte Einheiten andere nicht beeinflussen.
  • Least Privilege: Jeder Resource Manager nutzt eine eigene ServiceAccount.
  • Volle Kontrolle: Keine impliziten Watches oder „magischen“ Trigger – du bestimmst genau, was wann reconciled wird.

Praxisbeispiele

Was kannst du konkret mit Espejote machen? Hier einige Beispiele aus dem produktiven Einsatz bei VSHN:

  • Secret-Synchronisation: Automatisches Replizieren von Secrets (z.B. Image Pull Secrets oder Zertifikate) über mehrere Namespaces hinweg.
  • Autoscaler-Patching: Anpassung des OpenShift Cluster Autoscalers über Admission Webhooks.
  • Alerting-Regel-Management: Kuratieren und Patchen von OpenShift-Alerting-Regeln über verschiedene Cluster-Versionen hinweg.

Die Zukunft: WASM und mehr

Die Roadmap umfasst einen kro-ähnlichen API-Builder zur einfachen Erstellung eigener Ressourcen sowie Unterstützung für WebAssembly-Plugins. Damit können Entwickler eigene Logik in nahezu jeder Sprache schreiben und sicher im Espejote-Controller ausführen.

Erste Schritte

Beispiel

Dieses Beispiel ManagedResource patcht die RedHat OperatorHub-Konfiguration, um alle Standardquellen zu deaktivieren. Es zeigt den einfachsten Anwendungsfall: ein statisches Manifest bedingungslos patchen. Komplexere Anwendungsfälle findest du im Abschnitt „Erste Schritte“.

apiVersion: espejote.io/v1alpha1
kind: ManagedResource
metadata:
  annotations:
  name: disable-default-sources
  namespace: openshift-marketplace
spec:
  serviceAccountRef:
    name: disable-default-sources
  triggers:
    - name: operatorhub
      watchResource:
        apiVersion: config.openshift.io/v1
        kind: OperatorHub
        name: cluster
  template: |-
    {
        "apiVersion": "config.openshift.io/v1",
        "kind": "OperatorHub",
        "metadata": {
            "name": "cluster"
        },
        "spec": {
            "disableAllDefaultSources": true
        }
    }

Sebastian Widmer

Kontaktiere uns

Unser Expertenteam steht für dich bereit. Im Notfall auch 24/7.

Kontakt