GitOps: Automatisierte Deployments mit ArgoCD und Flux
Das Git-Repository ist die einzige Quelle der Wahrheit — für Infrastruktur und für Applikationen. GitOps eliminiert Click-Ops und schafft Nachvollziehbarkeit, Audit-Trails und automatisierte Rollbacks. ArgoCD und Flux machen Kubernetes-Deployments so sicher wie ein Git-Push.
Was ist GitOps? Das Prinzip erklärt
GitOps = Git als Single Source of Truth für deklarative Konfigurationen. Die Idee:
Der entscheidende Unterschied zu traditionellem CI/CD: Die desired State (in Git) und der actual State (im Cluster) werden permanent abgeglichen. Drift (unbeabsichtigte Änderungen) wird erkannt und korrigiert.
Drift Detection: Wenn jemand manuell einen Pod löscht oder eine ConfigMap ändert, erkennt ArgoCD/Flux das sofort und reconciled — es stellt den Git-Stated-Zustand wieder her. Manuelles Chaos hat keine Chance.
ArgoCD vs. Flux — Was nutzen?
| Kriterium | ArgoCD | Flux v2 |
|---|---|---|
| Ansatz | Push-basiert (ArgoCD pullt aus Git) | Push + GitOps Toolkit (modular) |
| UI | Eingebaute Web-UI (gut für Nicht-DevOps) | Keine native UI (CLI + Dashboard als Extra) |
| Multi-Cluster | Application Set + Federation | Flux Multi-Tenant Operator |
| Security | RBAC + SSO (Dex + OIDC) | SOPS-Integration, secrets encryption |
| Image-Updates | ArgoCD Image Updater (Plugin) | Flux Image Reflector + Automation |
| Einstieg | Schneller, mehr GUI, mehr "batteries included" | Mehr modular, besser für Kubernetes-first Teams |
| Maintenance | Relativ heavyweight (many CRDs) | Leichterer Operator, gute GitHub-Integration |
Für die meisten Teams ist ArgoCD der bessere Einstieg — die Web-UI macht GitOps für das ganze Team zugänglich, auch ohne CLI-Erfahrung.
Schritt 1: ArgoCD installieren
# Namespace erstellen und ArgoCD installieren
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.11.0/manifests/install.yaml
# Oder via Helm (empfohlen für Produktion)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd -n argocd \n --set server.service.type=LoadBalancer
# Initiales Admin-Passwort (aus Secret)
kubectl -n argocd get secret argocd-initial-admin-secret \n -o jsonpath="{.data.password}" | base64 -d
Nach Installation: UI auf https://argocd.$(minikube ip) oder via Ingress. Login mit User admin und dem Initial-Password.
Schritt 2: Applications in ArgoCD definieren
Eine ArgoCD Application ist ein deklaratives Kubernetes-Manifest, das ArgoCD sagt: "Synce diesen Git-Repo-Pfad zu diesem Cluster-Pfad."
# application.yaml — Application für die Webapp
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp-production
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/dein-org/k8s-config.git
targetRevision: main
path: apps/webapp/production
kustomize:
images:
# Automatisch: Image-Updater setzt dieses Tag
- webapp:deinregistry.io/webapp=registry.io/webapp:v1.2.3
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Alte Resources automatisch löschen
selfHeal: true # Drift automatisch korrigieren
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
prune: true + selfHeal: true sind die Kern-Features: ArgoCD erkennt Unterschiede zwischen Git und Cluster und fixt sie automatisch.
Schritt 3: Kustomize für Umgebungen
Ein zentrales Git-Repo mit Kustomize-overlays für prod/staging/dev — keine Duplikation, klare Hierarchie.
# apps/webapp/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
commonLabels:
app: webapp
images:
- name: registry.io/webapp
newName: registry.io/webapp
newTag: latest# apps/webapp/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
bases:
- ../base
patches:
- patch-replicas.yaml
- patch-resources.yaml
images:
- name: registry.io/webapp
newTag: v1.2.3 # Production → pinned Tag (kein latest!)# apps/webapp/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: staging
bases:
- ../base
replicas:
- name: webapp
count: 1 # Staging: nur 1 Replica
images:
- name: registry.io/webapp
newTag: staging-$(IMAGE_TAG_SUFFIX) # Automatisch aus PipelinePinned Tags statt latest: Production sollte nie latest verwenden. Nutze semantische Tags (v1.2.3) oder Git-Commit-SHAs. latest macht Rollbacks unmöglich — du weißt nicht, was "latest" gestern war.
Schritt 4: CI-Pipeline — Image bauen und Git aktualisieren
Pipeline-Ablauf: Build → Push → Update Image-Tag in Git → ArgoCD synced. GitOps-typisch: Pipeline pushed NIEMALS direkt in den Cluster, nur ins Git.
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Docker Build
run: |
docker build -t registry.io/webapp:${GITHUB_SHA::8} .
- name: Push to Registry
run: |
docker push registry.io/webapp:${GITHUB_SHA::8}
- name: Update image tag in Git
run: |
# Kubernetes Repo klonen
git clone https://github.com/dein-org/k8s-config.git
cd k8s-config
# Tag in kustomization.yaml updaten
sed -i "s|newTag:.*|newTag: ${GITHUB_SHA::8}|" apps/webapp/production/kustomization.yaml
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add -A
git commit -m "chore: deploy ${GITHUB_SHA::8}"
git push origin mainSchritt 5: Image-Updater — automatisches Sync ohne Pipeline
ArgoCD Image Updater scannt Container-Registries automatisch und updated Image-Tags im Git — ohne dass die CI-Pipeline Git ändern muss. Pipeline baut nur das Image; alles andere passiert automatisch.
# image-updater mit GitHub Container Registry
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: webapp-production
annotations:
# Image Updater aktivieren
argocd.argoproj.io/image-updater.autoupdate: "true"
argocd.argoproj.io/image-updater.sync-policy: "automated"
argocd.argoproj.io/image-updater.argocd.notification.address: ""
spec:
source:
kustomize:
images:
# Wenn neues Image vorhanden → update Git und sync
- image: registry.io/webapp
images:
- registry.io/webapp:{{ argocd-image-updater.tag }}
update_strategy: semver
# Automatisch neueste v1.x.y → deployed, kein Commit nötigRollbacks: Git als Sicherheitsnetz
GitOps macht Rollbacks trivial — du reverst den Git-Commit, ArgoCD synced automatisch.
# Per CLI (kubectl + argocd CLI)
argocd app rollback webapp-production
# Per Git — Commit revertieren
git revert HEAD
git push origin main
# ArgoCD erkennt Änderung und synced auf vorherige Version
# Per ArgoCD UI: Compare → Revision wählen → Sync
Ohne GitOps war ein Rollback eine komplexe Operation (altes Image manuell deployen, Confmaps wiederherstellen, etc.). Mit GitOps: ein git revert.
Multi-Cluster-Setup
Für Production empfehlen sich getrennte Cluster pro Umgebung (nicht Namespaces im gleichen Cluster). ArgoCD mit ApplicationSet managed mehrere Cluster aus einem Repo.
# application-set.yaml — deployed auf prod + staging automatisch
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: webapp-all-envs
spec:
generators:
- clusters:
values:
environment: production
- name: staging
server: https://staging-cluster.k8s.local
values:
environment: staging
template:
spec:
source:
repoURL: https://github.com/dein-org/k8s-config.git
path: apps/webapp/{{ values.environment }}
targetRevision: main
destination:
server: "{{ server }}"
namespace: "{{ values.environment }}"
syncPolicy:
automated:
prune: true
selfHeal: trueSecurity: Secrets in Git
Secrets (DB-Passwörter, API-Keys) gehören NICHT unverschlüsselt in Git. Workflow:
- Sealed Secrets (Bitnami): Kubernetes-Objekte, die nur vom Cluster entschlüsselt werden können. Public Key ins Git, Private Key im Cluster.
- SOPS + Age: Verschüsselung mit Age (oder GPG), Schlüssel nur auf Cluster.
- External Secrets Operator: Holt Secrets von HashiCorp Vault / AWS Secrets Manager / Azure Key Vault at runtime.
# sealed-secret.yaml — unverschlüsselt in Git, aber nicht lesbar ohne Cluster-Key
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: webapp-secrets
namespace: production
spec:
encryptedData:
DB_PASSWORD: AgA... # Verschlüsselt mit Cluster-Key
API_KEY: AgB... # Nur Cluster kann entschlüsseln
template:
metadata:
name: webapp-secrets
namespace: productionBranch-Strategie für GitOps
| Branch | Was passiert | Zielumgebung |
|---|---|---|
main | Auto-sync nach Approval | Production |
staging/* | Auto-sync ohne Approval | Staging |
feature/* | Preview-Namespace auf Anfrage | Feature-Cluster |
hotfix/* | Fast-Track → main → Production | Production (beschleunigt) |
GitOps-Spickzettel: Deine Checkliste
- Git-Repo = Single Source of Truth — Keine manuelle Änderungen direkt im Cluster erlaubt
- ArgoCD oder Flux — Beide sind production-ready. ArgoCD hat bessere UI; Flux ist modularer
- Pinned Tags (v1.2.3), niemals latest — Rollbacks funktionieren nur mit expliziten Tags
- Sync-Policy: selfHeal + prune — Drift wird automatisch korrigiert, kein Chaos-Admin kann den Cluster brechen
- Secrets verschlüsseln (Sealed Secrets / SOPS / ESO) — Plaintext-Secrets in Git = Kompromittierung
- ApplicationSets für Multi-Cluster — ein Repo, viele Cluster, automatisierte Reconciliation
- Pipeline updated Git, GitOps-Tool synced den Cluster — Pipeline pushet NIEMALS direkt auf Kubernetes
- Rollbacks = git revert + auto-sync — einfachste, sicherste Recovery-Methode