← Alle Beiträge

GitOps mit ArgoCD — Ein praktischer Leitfaden für deklarative Deployments

Matthias Bruns · · 7 Min. Lesezeit
gitops argocd kubernetes devops engineering

Das Deployment-Problem, über das niemand spricht

Deine CI-Pipeline baut das Image. Und dann? Jemand führt kubectl apply aus. Oder ein Helm-Befehl, der in einem Deploy-Step steckt. Oder ein Skript, das per SSH auf einen Bastion-Host geht. Drei Monate später weiß keiner mehr, was tatsächlich in Production läuft — weil der Cluster-State von dem abgedriftet ist, was in Git steht.

GitOps löst das Problem, indem Git zur einzigen Wahrheitsquelle für den Cluster-State wird. ArgoCD ist der meistgenutzte GitOps-Operator für Kubernetes — deklarativ, auto-sync-fähig, und es zeigt Dir genau, wo Realität und Soll-Zustand auseinanderlaufen.

Dieser Leitfaden zeigt die Einrichtung für echte Workloads, nicht die Hello-World-Version.

Was GitOps tatsächlich bedeutet

GitOps ist ein Deployment-Pattern mit vier Prinzipien, formalisiert vom OpenGitOps-Projekt:

  1. Deklarativ — der gesamte gewünschte Systemzustand wird deklarativ beschrieben (YAML, Helm Charts, Kustomize Overlays)
  2. Versioniert und unveränderlich — der Soll-Zustand liegt in Git, Audit-Trail und Rollback gibt’s gratis
  3. Automatisch gezogen — ein Agent (ArgoCD) gleicht den Cluster-State kontinuierlich mit Git ab
  4. Kontinuierlich abgeglichen — Drift wird erkannt und korrigiert, ohne manuelles Eingreifen

Der entscheidende Unterschied: Deine CI-Pipeline fasst den Cluster nicht mehr an. Sie baut und pusht ein Image, aktualisiert dann ein Manifest in Git. ArgoCD erledigt den Rest.

ArgoCD installieren

Die empfohlene Installation nutzt das offizielle Helm Chart. Für Production das HA-Manifest verwenden:

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/ha/install.yaml

Oder via Helm für mehr Kontrolle:

helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --set server.extraArgs={--insecure} \
  --set configs.params."server\.insecure"=true

Das --insecure-Flag deaktiviert TLS auf dem ArgoCD-Server selbst — TLS wird am Ingress-Controller terminiert, was dem Standard-Pattern entspricht. ArgoCD niemals ohne TLS-Terminierung irgendwo in der Kette exponieren.

Initiales Admin-Passwort abrufen:

kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

Sofort ändern. Noch besser: SSO via OIDC konfigurieren und den Admin-Account komplett deaktivieren.

Repository-Struktur, die skaliert

Der häufigste Fehler: Application-Manifeste im selben Repo wie der Application-Code. Das erzeugt zirkuläre Abhängigkeiten — eine Code-Änderung triggert CI, die Manifeste aktualisiert, was wiederum CI triggert.

Nutze ein dediziertes Config-Repository:

infra-gitops/
├── apps/
│   ├── api/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── staging/
│   │       │   ├── kustomization.yaml
│   │       │   └── replicas-patch.yaml
│   │       └── production/
│   │           ├── kustomization.yaml
│   │           └── replicas-patch.yaml
│   └── frontend/
│       ├── base/
│       └── overlays/
├── platform/
│   ├── cert-manager/
│   ├── ingress-nginx/
│   └── monitoring/
└── argocd/
    ├── projects.yaml
    └── applicationsets.yaml

Wichtige Entscheidungen:

  • Kustomize statt Helm für eigene App-Manifeste — Helm ist großartig für Third-Party-Charts. Für eigene Services sind Kustomize Overlays einfacher im PR zu reviewen und leichter zu diffen.
  • apps/ von platform/ trennen — Platform-Komponenten (cert-manager, Ingress, Monitoring) haben andere Change-Kadenzen und Approval-Anforderungen.
  • Ein Verzeichnis pro Environment-Overlay — macht Promotion explizit und nachvollziehbar.

Application-Manifeste

Eine ArgoCD Application-Ressource sagt dem Controller, was wo deployed werden soll:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-staging
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/infra-gitops.git
    targetRevision: main
    path: apps/api/overlays/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: api
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Die kritischen Einstellungen:

  • automated.prune: true — entfernt Ressourcen, die nicht mehr in Git existieren. Ohne das bleiben gelöschte Manifeste als verwaiste Ressourcen im Cluster.
  • automated.selfHeal: true — macht manuelle kubectl-Änderungen rückgängig. Das ist der ganze Sinn von GitOps — wenn jemand etwas per Hand patcht, korrigiert ArgoCD es.
  • retry — transiente Fehler passieren (API-Server-Überlastung, Webhook-Timeouts). Exponentielles Backoff verhindert kaskadierende Retries.

ApplicationSets für Multi-Environment

Individuelle Application-Ressourcen pro Service pro Environment skalieren nicht. ApplicationSets generieren Applications aus Templates:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - git:
        repoURL: https://github.com/your-org/infra-gitops.git
        revision: main
        directories:
          - path: apps/*/overlays/*
  template:
    metadata:
      name: '{{ index .path.segments 1 }}-{{ index .path.segments 3 }}'
    spec:
      project: default
      source:
        repoURL: https://github.com/your-org/infra-gitops.git
        targetRevision: main
        path: '{{ .path.path }}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{ index .path.segments 1 }}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Das entdeckt automatisch jedes apps/<service>/overlays/<env>-Verzeichnis und erstellt eine Application dafür. Das goTemplate: true-Flag aktiviert Go-Template-Syntax, den empfohlenen Modus für neue ApplicationSets. Neuen Service hinzufügen? Verzeichnisstruktur anlegen, pushen, fertig.

Secrets-Management

Das Einzige, was nicht im Klartext in Git darf: Secrets. Drei tragfähige Ansätze:

Sealed Secrets

Bitnami Sealed Secrets verschlüsselt Secrets client-seitig mit einem cluster-spezifischen Public Key. Nur der Controller im Cluster kann entschlüsseln.

kubeseal --format yaml < secret.yaml > sealed-secret.yaml

Das Sealed Secret kann sicher committet werden. Einfach, aber Key-Rotation erfordert Neuverschlüsselung aller Secrets.

External Secrets Operator

External Secrets Operator synchronisiert Secrets aus externen Stores (AWS Secrets Manager, Vault, GCP Secret Manager) in Kubernetes Secrets:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets
    kind: ClusterSecretStore
  target:
    name: api-secrets
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: /production/api/database-url

Das ist der empfohlene Ansatz für Production. Secrets liegen in einem dedizierten Secrets Manager mit eigenen Access-Policies, Audit-Logging und Rotation. Das Kubernetes Secret ist abgeleitet, nicht die Quelle der Wahrheit.

SOPS

Mozilla SOPS verschlüsselt einzelne Werte in YAML-Dateien. ArgoCD hat nativen SOPS-Support via Plugin. Guter Mittelweg, wenn kein externer Secrets Manager gewünscht ist.

Environment-Promotion

Der Promotion-Flow in GitOps:

  1. CI baut Image api:sha-abc123 und pusht in die Registry
  2. CI öffnet einen PR gegen das Config-Repo, der den Image-Tag im Staging-Overlay aktualisiert
  3. ArgoCD synct Staging automatisch
  4. Nach Validierung aktualisiert ein zweiter PR (oder manueller Merge) das Production-Overlay
  5. ArgoCD synct Production

Schritt 2 automatisieren mit Image Updater oder einem einfachen CI-Job:

# In der CI-Pipeline des App-Repos (separater Step nach dem Clonen von infra-gitops)
- name: Update staging manifest
  run: |
    kustomize edit set image api=$IMAGE_TAG
    git commit -am "chore: update api to $IMAGE_TAG"
    git push
  working-directory: infra-gitops/apps/api/overlays/staging

Für Production-Promotion einen PR mit Approval verlangen. Das liefert:

  • Audit-Trail — wer hat das Production-Deploy wann freigegeben
  • Rollbackgit revert auf den Merge-Commit
  • Diff-Review — der PR zeigt exakt, was sich zwischen den Environments geändert hat

Sync Windows und Waves

Nicht alles sollte sofort syncen. ArgoCD unterstützt Sync Windows, um einzuschränken, wann Syncs stattfinden:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
spec:
  syncWindows:
    - kind: allow
      schedule: '0 8-17 * * 1-5'
      duration: 9h
      applications: ['*']
      timeZone: Europe/Berlin

Das beschränkt Production-Syncs auf Geschäftszeiten an Werktagen — wenn jemand da ist, falls etwas kaputtgeht.

Für die Reihenfolge von Abhängigkeiten (Datenbank-Migration vor App-Deployment) Sync Waves nutzen:

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-1"  # Läuft vor Wave 0

Negative Waves laufen zuerst. Wave -1 für Migrationen, Wave 0 für die App, Wave 1 für Post-Deploy-Checks.

Monitoring und Alerts

ArgoCD exponiert Prometheus-Metriken auf :8082/metrics. Die wichtigsten Alerts:

  • App Sync fehlgeschlagenargocd_app_info{sync_status="OutOfSync"} für mehr als 10 Minuten
  • App Health degradiertargocd_app_info{health_status!="Healthy"} für mehr als 5 Minuten
  • Sync-Operation-Fehlerargocd_app_sync_total{phase="Error"} Rate-Anstieg

ArgoCD unterstützt auch Notifications via Slack, Teams, Webhooks oder E-Mail. Mindestens konfigurieren: Sync-Fehler und Health-Degradierung für Production-Apps.

Häufige Fallstricke

Secrets unverschlüsselt in Git. Klingt offensichtlich, passiert aber. Pre-commit Hooks mit gitleaks oder detect-secrets nutzen, um das vor dem Commit abzufangen.

Pruning nicht aktivieren. Ohne prune: true lässt das Löschen eines Manifests aus Git die Ressource weiterlaufen. Am Ende hat man Ghost-Services, die niemand wartet.

Resource Hooks ignorieren. ArgoCD respektiert Resource Hooks für Pre-Sync und Post-Sync Jobs. PreSync-Hooks für Datenbank-Migrationen statt Init-Container nutzen — sie sind in der ArgoCD-UI sichtbar und haben ordentliches Error-Handling.

Alles in einen Namespace syncen. Den Destination-Namespace in der Application nutzen, um Namespace-Grenzen durchzusetzen. Mit ArgoCD Projects kombinieren, um einzuschränken, in welche Namespaces ein Team deployen darf.

Das Fazit

GitOps mit ArgoCD eliminiert das Ratespiel „Was läuft gerade in Production?”. Jedes Deployment ist ein Git-Commit. Jedes Rollback ist ein Revert. Jede Änderung ist reviewbar.

Die Setup-Investition ist frontlastig — Repository-Struktur, ApplicationSets, Secrets-Management, Sync-Policies. Sobald es läuft, werden Deployments langweilig. Und langweilige Deployments sind genau das, was man will.

Mit einem Service in Staging anfangen. Die Feedback-Loop richtig hinbekommen. Dann auf Production erweitern und Services inkrementell hinzufügen. Nicht alles auf einmal migrieren.

Lesebarkeit

Schriftgröße