Wat je gaat leren
Aan het einde van deze tutorial heb je een werkende Argo CD-installatie die een Kubernetes-applicatie beheert via Git. Je begrijpt de Application CRD, sync policies, sync waves, het app-of-apps pattern, hoe je secrets buiten Git houdt, en hoe je de setup uitbreidt naar meerdere clusters.
Vereisten
- Een Kubernetes-cluster (v1.21 of nieuwer; een lokaal kind- of minikube-cluster werkt prima voor de installatie en Application-stappen)
- Helm 3.x of 4.x geinstalleerd
kubectlgeconfigureerd tegen het cluster- Een Git-repository die je beheert (GitHub, GitLab, Bitbucket of self-hosted)
- Bekendheid met Kubernetes Deployments, Services en namespaces
Inhoudsopgave
- Wat GitOps betekent
- Argo CD installeren met Helm
- Je eerste Application
- Sync policies en opties
- Sync waves en hooks
- App-of-apps pattern
- Secrets in GitOps
- Multi-cluster beheer
- Wat je geleerd hebt
Wat GitOps betekent
GitOps is bedacht door Weaveworks in 2017 en geformaliseerd door het OpenGitOps-project (een CNCF-werkgroep) in vier principes:
- Declaratief. De volledige gewenste systeemstatus staat als declaraties in Git (Kubernetes-manifests, Helm-charts, Kustomize overlays).
- Versiebeheerd en onveranderlijk. Elke wijziging is een Git-commit. Rollbacks worden
git revert. Audittrails wordengit log. - Automatisch gepulled. Een agent in het cluster haalt wijzigingen op uit Git. De CI-pipeline heeft nooit clustercredentials nodig.
- Continu gereconcilieerd. De agent detecteert drift tussen Git en het live cluster en corrigeert die. Handmatige
kubectl-wijzigingen worden overschreven.
Het pull-model is de beveiligingskern. Bij traditioneel CI/CD pusht de pipeline naar het cluster en moet die dus clustercredentials bezitten. Bij GitOps initieert de agent alle verbindingen outbound; er wordt nooit van buitenaf ingeduwd.
Argo CD is de dominante tool die deze principes implementeert. Het project is in december 2022 gegradueerd bij de CNCF en draait volgens het 2025 CNCF End User Survey in bijna 60% van de Kubernetes-clusters voor applicatiedelivery.
Argo CD installeren met Helm
Argo CD v3 (GA april 2025) is de huidige stabiele release. De officiele Helm-chart is de standaard distributiemethode.
Voeg de repository toe en installeer:
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--version 7.8.0 # pin op een specifieke chartversie
Voor een productiecluster maak je argocd-values.yaml met HA-instellingen:
# argocd-values.yaml — Argo CD HA-configuratie (chart 7.x)
server:
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
controller:
replicas: 1 # StatefulSet; schakel sharding in voor meerdere replica's
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
repoServer:
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
redis-ha:
enabled: true # vervang enkele Redis door een Sentinel-gebaseerd HA-cluster
configs:
params:
server.insecure: "true" # termineer TLS op je Ingress, niet op de Argo CD-server
Installeer met het valuesbestand:
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
-f argocd-values.yaml
Checkpoint. Controleer of alle pods draaien:
kubectl get pods -n argocd
Verwachte output (HA-installatie): argocd-server, argocd-repo-server, argocd-application-controller, argocd-redis-ha-server, argocd-applicationset-controller en argocd-dex-server pods in Running-status.
Toegang tot de UI. Port-forward voor eerste toegang:
kubectl port-forward svc/argocd-server -n argocd 8080:443
Haal het initiele adminwachtwoord op en wijzig het meteen:
# Wachtwoord ophalen
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d && echo
# Na inloggen en wachtwoord wijzigen, verwijder het secret
kubectl -n argocd delete secret argocd-initial-admin-secret
Open https://localhost:8080, log in als admin met het opgehaalde wachtwoord en wijzig het via User Info > Update Password in de UI.
Je eerste Application
De Application CRD is het kernbouwblok. Die vertelt Argo CD wat het moet deployen, waar vandaan, en hoe het gesynchroniseerd moet blijven.
Push een simpele nginx Deployment en Service naar je Git-repository onder deploy/nginx/:
# deploy/nginx/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80
---
# deploy/nginx/service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Maak nu het Application-manifest:
# bootstrap/nginx-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # cascade-delete clusterresources bij app-verwijdering
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-demo.git
targetRevision: main
path: deploy/nginx
destination:
server: https://kubernetes.default.svc # lokaal cluster
namespace: demo
syncPolicy:
automated:
prune: true # verwijder resources die uit Git verdwenen zijn
selfHeal: true # draai handmatige kubectl-wijzigingen terug
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Pas het toe:
kubectl apply -f bootstrap/nginx-app.yaml
Checkpoint. Na 30 seconden (Argo CD pollt standaard elke 3 minuten, maar de eerste sync triggert direct bij aanmaak):
kubectl get application nginx -n argocd
Verwacht: STATUS: Synced, HEALTH: Healthy. De demo-namespace bevat nu 2 nginx-pods en een Service.
Argo CD volgt twee dimensies per Application. Sync-status (Synced, OutOfSync) vertelt of het cluster overeenkomt met Git. Health-status (Healthy, Progressing, Degraded) vertelt of resources daadwerkelijk werken. Een Application kan Synced maar Degraded zijn als manifests wel zijn toegepast maar pods crashen.
Helm- en Kustomize-sources
Het source-veld ondersteunt meer dan plain YAML. Voor een Helm-chart:
source:
repoURL: https://charts.bitnami.com/bitnami
chart: postgresql
targetRevision: 16.4.3 # semver-chartversie
helm:
releaseName: pg-primary
valueFiles:
- values.yaml
- values-production.yaml
parameters:
- name: auth.postgresPassword
value: "changeme" # overschrijf een enkele waarde
Voor Kustomize wijs je path naar een directory met een kustomization.yaml; Argo CD herkent dat automatisch.
Sync policies en opties
Het syncPolicy-blok regelt hoe Argo CD reconcilieert.
| Optie | Wat het doet | Standaard |
|---|---|---|
automated.prune |
Verwijdert resources die niet meer in Git staan | false |
automated.selfHeal |
Draait handmatige wijzigingen terug naar de Git-status | false |
automated.allowEmpty |
Staat sync toe als de source nul resources oplevert | false |
syncOptions.ServerSideApply |
Gebruikt Kubernetes Server-Side Apply (voor grote CRDs, managedFields-conflicten) |
false |
syncOptions.PruneLast |
Stelt verwijdering van orphans uit tot na succesvolle nieuwe resources | false |
syncOptions.ApplyOutOfSyncOnly |
Past alleen resources toe die daadwerkelijk afwijken | false |
syncOptions.CreateNamespace |
Maakt de doelnamespace aan als die ontbreekt | false |
ignoreDifferences voor externe controllers. Als een HorizontalPodAutoscaler replicas beheert, rapporteert Argo CD valse drift elke keer dat de HPA schaalt. Onderdruk dat:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
Voeg RespectIgnoreDifferences=true toe aan syncOptions zodat die velden ook tijdens sync worden overgeslagen, niet alleen bij de diff.
Sync waves en hooks
Sync waves bepalen de volgorde van resource-toepassing binnen een enkele sync. Hooks draaien op specifieke levenscyclusfasen.
Waves. Annoteer elke resource met argocd.argoproj.io/sync-wave. Resources zonder de annotatie vallen in wave 0. Argo CD past een wave toe, wacht tot alle resources erin Healthy zijn, en gaat dan door naar de volgende.
# Wave -1: namespace eerst
apiVersion: v1
kind: Namespace
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# Wave 0 (standaard): database
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
annotations:
argocd.argoproj.io/sync-wave: "0"
---
# Wave 1: applicatie (nadat database healthy is)
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
annotations:
argocd.argoproj.io/sync-wave: "1"
Hooks. Hooks zijn resources (meestal Jobs) geannoteerd met argocd.argoproj.io/hook:
| Fase | Wanneer | Typisch gebruik |
|---|---|---|
PreSync |
Voor het toepassen van manifests | Databasemigraties, schemacontroles |
PostSync |
Nadat alle resources Healthy zijn | Smoketests, cache warming |
SyncFail |
Alleen bij falen | Cleanup, rollbackscripts, alerting |
Een databasemigratie als PreSync hook:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 2
activeDeadlineSeconds: 300 # faal na 5 minuten; blokkeer nooit oneindig
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: liquibase/liquibase:4.31-alpine
args:
- --changeLogFile=changelog/db.changelog-master.xml
- --url=$(DATABASE_URL)
- update
envFrom:
- secretRef:
name: db-credentials
Stel altijd activeDeadlineSeconds in op hook Jobs. Een hangende PreSync-job blokkeert de hele deployment voor onbepaalde tijd.
App-of-apps pattern
Het app-of-apps pattern gebruikt een enkele parent Application die naar een Git-directory wijst met child Application-manifests. Als de parent synct, maakt, updatet of verwijdert die de child Applications. Zie het als een bootstrap: pas een manifest toe, en de volledige clusterstack komt tot leven.
Repositorystructuur:
gitops-repo/
├── bootstrap/
│ └── parent-app.yaml # eenmalig handmatig toepassen
├── apps/
│ ├── nginx-ingress.yaml # child Application
│ ├── cert-manager.yaml # child Application
│ ├── prometheus-stack.yaml # child Application
│ └── my-service.yaml # child Application
└── charts/
└── my-service/
├── Chart.yaml
└── values.yaml
De parent:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cluster-bootstrap
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated: null # houd parent handmatig voor veiligheid
Houd de sync van de parent handmatig (automated: null). Als de apps/-directory per ongeluk leeggemaakt wordt en de parent prune: true heeft, cascade-verwijdert die elke child Application. Child Applications zelf kunnen veilig automated: {prune: true, selfHeal: true} gebruiken.
Wanneer ApplicationSet gebruiken
ApplicationSet genereert Application-manifests dynamisch vanuit templates en generators. Gebruik het als de set Applications niet vaststaat: multi-cluster deployments, per-team namespaces, of elk scenario waar een nieuwe folder of clusterlabel automatisch een nieuwe Application moet opleveren.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-addons
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/yourorg/gitops-repo.git
revision: HEAD
directories:
- path: apps/* # elke subdirectory wordt een Application
template:
metadata:
name: ''
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: HEAD
path: ''
destination:
server: https://kubernetes.default.svc
namespace: ''
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Vuistregel: app-of-apps voor een vaste set platformcomponenten (ingress, monitoring, cert-manager). ApplicationSet voor dynamische workloads over omgevingen of clusters.
Secrets in GitOps
Git-history is permanent. Een Kubernetes Secret dat als plaintext gecommit is, blijft terugvindbaar, zelfs na verwijdering. De regel is absoluut: sla nooit plaintext secrets op in Git. Argo CD injecteert zelf ook geen secrets in manifests (die zouden dan in Redis gecachet worden). Gebruik een van deze benaderingen.
Sealed Secrets
Sealed Secrets (Bitnami) draait een controller in het cluster die een private key bewaart. Je versleutelt secrets lokaal met de public key. De resulterende SealedSecret CRD is veilig om te committen; alleen de controller in het cluster kan hem ontsleutelen.
# Maak een sealed secret aan
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=supersecret \
--dry-run=client -o yaml | \
kubeseal --format yaml > manifests/sealed-db-credentials.yaml
Maak een backup van de private key van de controller, los van Git. Zonder die key zijn sealed secrets onherstelbaar na een cluster-rebuild.
External Secrets Operator
External Secrets Operator (ESO) haalt secrets op uit externe stores (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault) tijdens runtime en maakt er standaard Kubernetes Secret-objecten van. Je Git-repository bevat alleen referenties:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: my-app/database
property: username
- secretKey: password
remoteRef:
key: my-app/database
property: password
ESO regelt automatische rotatie via refreshInterval. Als secrets roteren, rapporteert Argo CD mogelijk valse drift op het gegenereerde Secret. Onderdruk dat met ignoreDifferences op het /data-veld van het Secret.
Vergelijking
| Sealed Secrets | External Secrets Operator | |
|---|---|---|
| Complexiteit van setup | Laag | Gemiddeld |
| Externe afhankelijkheden | Geen | Secretprovider (AWS, Vault, etc.) |
| Secretrotatie | Handmatig opnieuw sealen | Automatisch |
| Git bevat | Versleutelde blob | Alleen referenties |
| Het beste voor | Kleine teams, simpele setups | Organisaties met bestaande secretstores |
Multi-cluster beheer
De aanbevolen architectuur: een dedicated managementcluster draait Argo CD en beheert doelclusters (dev, staging, productie, multi-regio). Het managementcluster bewaart clustercredentials maar draait zelf geen workloads.
Registreer een cluster:
argocd cluster add prod-us-context \
--name production-us-east \
--label env=production \
--label region=us-east-1
Gebruik een ApplicationSet met de cluster generator om over alle matchende clusters te deployen:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-production
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: 'my-app-'
spec:
project: production-project
source:
repoURL: https://github.com/yourorg/my-app.git
targetRevision: main
path: k8s/overlays/production
destination:
server: ''
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Een nieuw productiecluster toevoegen met het label env: production maakt automatisch een Application aan. Geen manifestwijzigingen nodig.
AppProject voor toegangscontrole
Gebruik bij multi-cluster setups AppProject om teamboundaries af te dwingen. Gebruik nooit het default-project voor productie-workloads; dat staat alle repositories en alle clusters toe.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production-project
namespace: argocd
spec:
sourceRepos:
- 'https://github.com/yourorg/production-manifests.git'
destinations:
- server: https://prod-cluster.example.com
namespace: '*'
clusterResourceWhitelist:
- group: '*'
kind: '*'
roles:
- name: deploy-pipeline
policies:
- p, proj:production-project:deploy-pipeline, applications, sync, production-project/*, allow
Wat je geleerd hebt
Je hebt Argo CD van scratch opgezet en een applicatie declaratief via Git gedeployd. De kernconcepten:
- GitOps pullt, pusht niet. De agent in het cluster initieert alle verbindingen. CI heeft nooit clustercredentials.
- De Application CRD koppelt een Git-source aan een clusterdestination met sync policies die pruning, self-healing en retrygedrag regelen.
- Sync waves ordenen resource-aanmaak (namespace voor database voor applicatie). Hooks draaien Jobs op levenscyclusfasen (PreSync voor migraties, PostSync voor smoketests).
- App-of-apps bootstrapt een cluster vanuit een enkel manifest. ApplicationSet automatiseert dynamische omgevingen.
- Secrets gaan nooit in Git. Sealed Secrets versleutelt ze; External Secrets Operator referenceert ze uit externe stores.
- Multi-cluster is een label en een ApplicationSet-generator verwijderd van single-cluster.
De repositorystructuur die de meeste teams goed dient: een Git-repo voor deploymentmanifests (gescheiden van applicatiebroncode), Kustomize overlays of Helm-valuebestanden per omgeving in directories (niet branches), en Argo CD dat de main-branch bewaakt.