Bei GitOps beschreibt ein Git-Repository den gewünschten Zustand deiner Umgebungen, und ein Werkzeug wie Argo CD sorgt dafür, dass der Cluster diesem Zustand entspricht. Das klingt einfach, in der Praxis stellen sich aber schnell zwei Fragen: Wie strukturierst du das Repository für mehrere Umgebungen? Und wie kommt ein neues Docker-Image automatisch in die richtige Umgebung? In diesem Leitfaden zeigen wir, wie wir beides mit Argo CD und Helm lösen.
Eine Umgebung pro Repository, Branch oder Verzeichnis?
GitOps-Projekte mit Argo CD lassen sich auf drei Arten aufsetzen:
| Strategie | Vorteile | Nachteile |
|---|---|---|
| Eine Umgebung pro Repository | Saubere Trennung, eigene Berechtigungen pro Umgebung | Änderungen über mehrere Umgebungen sind mühsam |
| Eine Umgebung pro Branch | Vertrautes Git-Modell | Branches driften auseinander, Merges zwischen Umgebungen werden fehleranfällig |
| Eine Umgebung pro Verzeichnis | Änderungen an mehreren Umgebungen schnell möglich, alles auf einen Blick | Berechtigungen müssen anders gelöst werden |
Für die meisten Anwendungsfälle ist eine Umgebung pro Verzeichnis der beste Kompromiss. Darauf konzentrieren wir uns im Folgenden.
Die Verzeichnisstruktur
Für jede Umgebung gibt es im Repository ein eigenes Verzeichnis:
.
├── argocd
│ ├── project.yaml
│ └── repo-secrets.yaml
├── Chart.yaml
├── environments
│ ├── asia
│ │ ├── dev
│ │ ├── test
│ │ └── prod
│ └── eu
│ ├── dev
│ ├── test
│ │ ├── argocd
│ │ │ └── application.yaml
│ │ └── values.yaml
│ └── prod
└── templates
└── _helpers.tpl
Arbeitest du mit Kustomize statt Helm, übernehmen overlays-Verzeichnisse dieselbe Rolle.
Sollen bestimmte Umgebungen aus Sicherheitsgründen getrennt sein, etwa Produktion vom Rest, verschiebst du deren Verzeichnisse in ein zweites Repository:
myapp-gitops-prod.git
myapp-gitops-dev-test.git
Mehrere Umgebungen in einem Commit
Mit einer Umgebung pro Verzeichnis kannst du mehrere Umgebungen in einem Commit ändern, etwa DEV und TEST gleichzeitig auf ein neues Image heben. Jede Umgebung ist eine eigene Application: Schlägt die Synchronisierung einer Umgebung fehl, bleiben die anderen davon unberührt, und ein Rollback ist ein einfaches git revert. Grundsätzlich spricht also nichts gegen solche Commits.
Willst du sie aus organisatorischen Gründen verhindern, geht das nur über einen Pre-Receive-Hook, der Änderungen auf ein Verzeichnis pro Commit beschränkt. GitHub Enterprise, Bitbucket Data Center und GitLab unterstützen solche Hooks, GitHub in der Cloud nicht.
Die Argo-CD-Application mit Helm
Helm kennt zwar Subcharts, in der Praxis ist es aber einfacher, ein einziges Helm-Chart für die Anwendung zu nutzen und pro Umgebung nur die values.yaml anzupassen. Diese liegt im Verzeichnis der jeweiligen Umgebung:
environments
└── eu
└── prod
├── argocd
│ └── application.yaml
└── values.yaml
Das Application-Manifest verweist auf das Chart im Wurzelverzeichnis und auf die values.yaml der Umgebung:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-eu-prod
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: myapp
source:
repoURL: git@github.com:my-org/myapp-gitops.git
targetRevision: HEAD
# das Helm-Chart liegt im Wurzelverzeichnis des Repositorys
path: .
helm:
# relativ zu spec.source.path
valueFiles:
- environments/eu/prod/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: myapp-prod
Zusätzliche Parameter über .argocd-source.yaml
Beim Deployment prüft Argo CD, ob im Verzeichnis spec.source.path die Dateien .argocd-source.yaml und .argocd-source-<appname>.yaml liegen. Ihre Parameter überschreiben die Werte aus der values.yaml, wobei die anwendungsspezifische Datei Vorrang hat:
helm:
parameters:
- name: image.tag
value: 2025110713512263c634
forceString: true
Diese Dateien nutzt zum Beispiel der Argo CD Image Updater, um den Image-Tag zu aktualisieren. Beachte: Sie liegen im Verzeichnis aus spec.source.path, in unserem Beispiel also im Wurzelverzeichnis, nicht im Verzeichnis der Umgebung.
Webhooks: nur die geänderte Umgebung aktualisieren
Löst GitHub den Webhook von Argo CD aus, prüft Argo CD standardmäßig alle Anwendungen, die auf das Repository zeigen. Mit der Annotation argocd.argoproj.io/manifest-generate-paths beschränkst du das auf die Pfade, die für eine Anwendung relevant sind:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-eu-prod
annotations:
argocd.argoproj.io/manifest-generate-paths: ./Chart.yaml;./templates;./environments/eu/prod;./.argocd-source-myapp-eu-prod.yaml
Jetzt wird myapp-eu-prod nur aktualisiert, wenn sich das Chart, die Umgebung oder ihre .argocd-source-Datei geändert hat.
Zwei Stolperfallen: Wenn du die Annotation nutzt, musst du die
.argocd-source-Datei ausdrücklich mit angeben. Und die Annotation wirkt nur beim Webhook. Ein Sync über Oberfläche oder CLI ignoriert sie und aktualisiert auf den neuesten Commit.
Neue Images automatisch ausrollen
Bleibt die zweite Frage: Wie landet ein neu gebautes Image in der richtigen Umgebung? Wir nutzen zwei Wege.
Weg 1: Argo CD Image Updater
Der Argo CD Image Updater beobachtet deine Container-Registry und aktualisiert den Image-Tag, sobald ein neues Image erscheint, entweder direkt in der Application oder, besser nachvollziehbar, per Commit zurück ins Git-Repository.
Dafür braucht er einen eigenen Argo-CD-Account mit API-Token, RBAC-Rechte zum Lesen und Aktualisieren von Applications, Lesezugriff auf die Registry und zum Zurückschreiben Schreibzugriff auf das GitOps-Repository.
Hinweis: Die Konfiguration des Image Updaters hat sich zwischen den Versionen deutlich verändert. Halte dich an die Dokumentation genau der Version, die du einsetzt.
Der Image Updater passt gut, wenn du Image-Tags nach festen Regeln übernehmen willst, etwa „immer die neueste Patch-Version“.
Weg 2: Promotion aus der CI-Pipeline
Oft willst du mehr Kontrolle: Ein Image mit dem Tag *-dev soll nur DEV aktualisieren, eines mit *-stable nur PROD. Dann lässt du die CI-Pipeline des Anwendungsrepositorys das GitOps-Repository benachrichtigen. Auf GitHub geht das mit dem Event repository_dispatch.
1. Zugriff einrichten. Das Anwendungsrepository braucht Schreibzugriff auf genau ein Repository: das GitOps-Repository. Nutze dafür einen Fine-grained Personal Access Token oder eine GitHub App, beschränkt auf dieses eine Repository mit den Rechten Contents: Read and write und Metadata: Read-only. Hinterlege Token und Repository-Namen als Secrets, zum Beispiel GITOPS_REPO und GITOPS_REPO_TOKEN.
2. Event auslösen. Nachdem die Pipeline das Image gebaut und in die Registry geschoben hat, schickt sie das Event mit Branch und Image-Tag:
- name: Notify GitOps repository
env:
GH_TOKEN: ${{ secrets.GITOPS_REPO_TOKEN }}
run: |
gh api repos/${{ secrets.GITOPS_REPO }}/dispatches \
-f event_type=update-gitops-repo \
-f "client_payload[git_branch]=${GITHUB_REF_NAME}" \
-f "client_payload[docker_image_tag]=${{ steps.publish.outputs.tag }}"
3. Im GitOps-Repository reagieren. Ein Workflow lauscht auf das Event, aktualisiert die passende values.yaml und committet die Änderung. Für die Zuordnung „welches Tag aktualisiert welche Umgebung“ haben wir die GitHub Action dreitier/conditional-regex-search-and-replace-action gebaut:
name: Update environment from upstream image
on:
repository_dispatch:
types: [update-gitops-repo]
jobs:
update-values:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dreitier/conditional-regex-search-and-replace-action@main
id: replace
with:
# -dev aktualisiert nur dev, -stable nur prod
mappings: "docker_image_tag==.*-dev {THEN_UPDATE_FILES} **dev/values.yaml=docker_image_tag_regex&git_branch_regex {NEXT_MAPPING} docker_image_tag==.*-stable {THEN_UPDATE_FILES} **prod/values.yaml=docker_image_tag_regex&git_branch_regex"
docker_image_tag: ${{ github.event.client_payload.docker_image_tag }}
docker_image_tag_regex: 'imageTag: "(?<docker_image_tag>.*)"'
git_branch: ${{ github.event.client_payload.git_branch }}
git_branch_regex: 'git_branch: "(?<git_branch>.*)"'
- name: Commit updated environment
if: ${{ steps.replace.outputs.total_modified_files >= 1 }}
uses: EndBug/add-and-commit@v9
with:
add: environments/*
message: "Update to ${{ github.event.client_payload.docker_image_tag }} from ${{ github.event.client_payload.git_branch }}"
Argo CD übernimmt die Änderung anschließend automatisch oder, wenn GitHub den Webhook von Argo CD auslöst, sofort.
Fazit
Mit einer Umgebung pro Verzeichnis, einem gemeinsamen Helm-Chart und gezielten Webhooks bleibt dein GitOps-Repository übersichtlich, auch wenn die Zahl der Umgebungen wächst. Ob neue Images über den Image Updater oder über die CI-Pipeline kommen, hängt davon ab, wie viel Kontrolle du über die Promotion brauchst.
Du baust gerade eine Plattform mit Kubernetes und GitOps auf oder willst eine bestehende Struktur prüfen lassen? Genau das machen wir in unserer DevOps- und Kubernetes-Beratung.
Ursprünglich veröffentlicht am 9. November 2022.