GitOps ~4 Min.

Argo CD ApplicationSets für Multi-Cluster

Generatoren, Templates und Fleet-GitOps.

von PlatPrep

Fünf Cluster. Zwanzig Teams. Jedes Team hat mehrere Applikationen. Jede Applikation braucht eine ArgoCD Application-Ressource.

Wer das manuell pflegt, hat entweder sehr viel Zeit oder macht es falsch.

ApplicationSets sind ArgoCDs Antwort auf dieses Problem. Ein ApplicationSet ist ein Template für Application-Ressourcen, kombiniert mit einem oder mehreren Generators, die beschreiben, für welche Instanzen das Template ausgerollt wird. Statt hundert Application-Objekte zu pflegen, pflegst du ein ApplicationSet — und der ApplicationSet-Controller erzeugt, aktualisiert und löscht die Applications für dich.

Generators: woher die Parameter kommen

Der list-Generator ist der einfachste: eine explizite Liste von Parameter-Sets, volle Kontrolle, kein Automatismus. Gut für drei Cluster, mühsam für dreißig.

Interessanter ist der git-Generator. Er scannt ein Repo nach Verzeichnissen oder Dateien mit einem bestimmten Muster und erzeugt pro Match eine Application. Liegt jeder Service unter apps/<team>/<service>/, reicht ein neues Verzeichnis im Repo — und ArgoCD legt die Application automatisch an. Onboarding eines neuen Services wird ein Pull Request statt eines Plattform-Tickets.

Der clusters-Generator nutzt die in ArgoCD registrierten Cluster-Secrets als Quelle. Über Label-Selektoren steuerst du, welche Cluster betroffen sind. Richtig mächtig wird es, wenn man beides per matrix-Generator kreuzt:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: team-apps
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchLabels:
                  env: prod
          - git:
              repoURL: https://github.com/org/apps.git
              revision: main
              directories:
                - path: "apps/*/*"
  template:
    metadata:
      name: "{{.path.basename}}-{{.name}}"
    spec:
      project: tenants
      source:
        repoURL: https://github.com/org/apps.git
        targetRevision: main
        path: "{{.path.path}}"
      destination:
        server: "{{.server}}"
        namespace: "{{.path.basename}}"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Jeder Service aus dem Git-Repo landet auf jedem Prod-Cluster. Das Kreuzprodukt ist genau so mächtig wie gefährlich: 30 Services mal 10 Cluster sind 300 Applications, und jede Änderung am ApplicationSet betrifft alle gleichzeitig — ein Tippfehler im Template ist ein Tippfehler in 300 Applications. Für die meisten Setups ist ein einfacher Generator pro ApplicationSet die wartbarere Wahl — Matrix nur dort, wo das Fleet-Muster wirklich gebraucht wird.

Die Stellen, an denen es wehtut

Löschen kaskadiert. Der Controller besitzt die generierten Applications. Löschst du das ApplicationSet — oder ändert sich der Generator-Output so, dass eine Application herausfällt — wird sie entfernt, inklusive der Cluster-Ressourcen, wenn prune aktiv ist. Ein versehentlich gelöschtes Verzeichnis im Git-Repo räumt also Produktions-Workloads ab. Für den Übergang gibt es preserveResourcesOnDeletion in der syncPolicy des ApplicationSets; langfristig hilft nur Disziplin: Branch Protection auf dem Apps-Repo und ein Review-Pflicht-Workflow.

Polling vs. Webhooks. Der Git-Generator pollt standardmäßig alle drei Minuten. Bei vielen ApplicationSets auf einem Monorepo läufst du in API-Rate-Limits bei GitHub und wunderst dich über träge Updates. Ein Webhook vom Git-Provider an den ApplicationSet-Controller löst beides.

Konfigurationsdrift zwischen Umgebungen. Die Versuchung ist groß, Umgebungsunterschiede über immer mehr Generator-Parameter und Template-Bedingungen abzubilden. Ab einem gewissen Punkt ist niemand mehr in der Lage, aus dem ApplicationSet abzulesen, was auf welchem Cluster tatsächlich landet. Die wartbarere Struktur: Unterschiede gehören in die Kustomize-Overlays oder Helm-Values im Apps-Repo (overlays/prod, overlays/staging), das ApplicationSet bleibt dumm und reicht nur den Pfad durch.

Template-Ausdruckskraft. Mit goTemplate: true stehen Go-Template-Funktionen zur Verfügung (lower, replace, Sprig-Funktionen) — wichtig, weil Application-Namen DNS-konform sein müssen und Verzeichnisnamen das nicht immer sind. Wer einzelne generierte Applications vom Template abweichen lassen muss, kann mit templatePatch gezielt patchen, statt ein zweites ApplicationSet zu pflegen.

Platform- und Team-Workloads trennen

Ein Pattern, das sich bewährt hat: separate ApplicationSets für Platform-Services und für Team-Applikationen. Der Monitoring-Stack, Ingress-Controller und cert-manager kommen aus einem eng kontrollierten Infra-Repo, mit eigenem ArgoCD-Project, das nur definierte Ziel-Namespaces und Ressourcen-Kinds erlaubt. Team-Apps laufen über den Git-Generator mit einem Project, das ihnen Autonomie im eigenen Namespace gibt — aber eben nur dort. Die AppProject-Ressource ist hier der eigentliche Sicherheitsmechanismus: Ohne sie kann jedes generierte Application-Objekt theoretisch cluster-weite Ressourcen anlegen.

Beim Rollout über viele Cluster fehlt ApplicationSets von Haus aus eine Staffelung — alle Applications synchen, sobald sich die Source ändert. Es gibt dafür Progressive Syncs als Alpha-Feature; wer das produktiv braucht, fährt heute robuster mit einem Umweg: getrennte ApplicationSets pro Wave (erst Staging-Cluster, dann Prod), gesteuert über Cluster-Labels.

ApplicationSets verwandeln ArgoCD von einem App-by-App-Tool in eine skalierbare GitOps-Plattform. Wer die Grundlagen von Application, Project und Sync-Mechanik noch festigen will, findet sie im ArgoCD-Lernpfad — und wer noch zwischen den großen GitOps-Tools schwankt, im Vergleich Argo CD vs. Flux.