Dit wil ik nooit meer doen: een cluster vragen wat het draait en het antwoord niet vertrouwen. Jarenlang was dat normaal. Iemand draaide kubectl apply, misschien vanaf een laptop, misschien vanuit een CI-job die niemand meer kon vinden, en de live staat dreef langzaam weg van alles wat ergens opgeschreven stond. Toen ik op GitOps overstapte, verdween die hele categorie onzekerheid. Ik push naar Git, en het cluster convergeert tot het matcht. Wil ik weten wat gedeployed is, dan lees ik een bestand.
ArgoCD is de tool die ik daarvoor pak. Deze post loopt van de kleinst mogelijke versie tot de config die ik echt draai, zodat je kunt stoppen zodra je genoeg hebt.
Waarom Git als source of truth
Begin bij de oude manier:
Developer → kubectl apply → Cluster
De vraag die dit niet kan beantwoorden is juist de belangrijke: wat is er op dit moment echt gedeployed? Je queryt het cluster en hoopt dat niemand er met de hand aan heeft gezeten. Drift sluipt er stilletjes in. Rollbacks zijn handmatig, en de enige audit trail is “iemand heeft kubectl gedraaid, waarschijnlijk.”
GitOps verplaatst de waarheid een stap naar achteren:
Developer → Git Push → ArgoCD → Cluster
Nu houdt Git de gewenste staat vast, en ArgoCD’s taak is om de werkelijkheid daarop te laten matchen. Verander iets met de hand en ArgoCD zet het terug. Wil je weten wat draait? Lees de repo. Wil je terug? Revert de commit. Dit is hetzelfde instinct dat ik beschreef in Sovereign Infrastructure: ik wil systemen die ik kan inspecteren, niet systemen die ik moet ondervragen. Imperatieve commando’s verdwijnen op het moment dat ze klaar zijn. Declaratieve staat in Git blijft, met een geschiedenis eraan vast.
De simpelste versie
Voordat we het over concepten hebben, zet ik ArgoCD aan zodat je iets hebt om op te klikken. Eén namespace, één manifest.
# Maak namespace
kubectl create namespace argocd
# Installeer ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wacht tot pods ready zijn
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s
Klaar. ArgoCD draait. Haal nu het wachtwoord op en open de UI:
# Haal het initiële admin wachtwoord op
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# Port-forward de UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080, login als 'admin' met het wachtwoord hierboven
Genoeg om rond te kijken. Drie concepten maken duidelijk wat je ziet.
De drie concepten die je nodig hebt
Application
De Application is de unit waarmee je werkt. Het beantwoordt drie vragen: waar leven de manifests, waar moeten ze heen, en hoe streng moet ArgoCD ze in sync houden.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/yourrepo.git
targetRevision: main
path: manifests/my-app
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
Project
Een Project groepeert applicaties en zet access boundaries. Het default project staat alles toe, wat prima is zolang je leert. In productie wil je dingen afschermen zodat het ene team niet in de namespace van een ander team kan deployen.
Sync
Sync is de handeling van het cluster laten matchen met Git. Het komt in drie smaken:
- Handmatig: jij klikt op de knop
- Automated: ArgoCD handelt zodra Git verandert
- Self-healing: ArgoCD draait ook wijzigingen terug die direct tegen het cluster zijn gemaakt
Layer 1: je eerste echte deployment
Tijd om iets te deployen. Ik houd het expres saai: een nginx Deployment en Service, volledig beheerd via Git.
Stap 1: maak een Git repository
Zet een repo op (of hergebruik er een) met deze indeling:
my-gitops-repo/
└── apps/
└── nginx/
├── deployment.yaml
├── service.yaml
└── kustomization.yaml
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
Commit en push.
Stap 2: wijs ArgoCD ernaartoe
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/YOURUSER/my-gitops-repo.git
targetRevision: main
path: apps/nginx
destination:
server: https://kubernetes.default.svc
namespace: nginx
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
Apply het:
kubectl apply -f argocd-application.yaml
Stap 3: kijk hoe het convergeert
Open de ArgoCD UI en je applicatie verschijnt. Met automated sync aan deployt het direct. Anders klik je op “Sync.”
flowchart LR
subgraph dashboard["ArgoCD Dashboard - nginx"]
direction LR
DEP["Deployment<br/>nginx"] --> RS["ReplicaSet<br/>nginx"]
RS --> POD["Pod x2<br/>nginx"]
DEP --> SVC["Service<br/>nginx"]
end
De UI tekent de resource tree met health en sync status op elke node. Geen giswerk over wat er live staat.
De workflow die kubectl vervangt
Dit is het deel dat mijn gewoontes veranderde. Wil je het aantal replicas omhoog?
- Edit in Git: verander
replicas: 2naarreplicas: 3in deployment.yaml - Commit en push
- ArgoCD detecteert de wijziging (binnen 3 minuten standaard, of direct met webhooks)
- Cluster update vanzelf
Geen kubectl, geen scripts, geen knagende twijfel of ik het commando wel echt heb gedraaid. Git is het register.
Rollback is gewoon reverten
Iets kapot na een wijziging? Revert de commit, push, en ArgoCD loopt het cluster terug.
git revert HEAD
git push
# ArgoCD rollbackt automatisch het cluster
De hele geschiedenis van het cluster zit nu in git log:
git log --oneline apps/nginx/
# a1b2c3d Scale nginx to 3 replicas
# d4e5f6g Initial nginx deployment
Self-healing in actie
Zet selfHeal: true aan en probeer slimmer te zijn:
# Handmatig de deployment schalen
kubectl scale deployment nginx -n nginx --replicas=5
# Wacht even
kubectl get deployment nginx -n nginx
# NAME READY UP-TO-DATE AVAILABLE
# nginx 3/3 3 3
ArgoCD zag de drift en trok het terug naar wat Git zegt. Dit is de feature die de ergste faalmodus in infrastructuur de das om doet: de instelling die iemand vorig jaar veranderde en die niemand meer kent en niemand durft aan te raken. Staat het niet in Git, dan overleeft het niet.
Layer 2: sync policies in detail
De sync policy is waar je instelt hoe uitgesproken ArgoCD wordt:
syncPolicy:
automated:
prune: true # Verwijder resources die uit Git verwijderd zijn
selfHeal: true # Draai handmatige wijzigingen terug
allowEmpty: false # Sync niet als source leeg is
syncOptions:
- CreateNamespace=true
- PruneLast=true # Prune na andere syncs
- ApplyOutOfSyncOnly=true # Alleen gewijzigde resources applyen
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Mijn advies: begin met automated sync, prune en selfHeal allemaal aan. Dat geeft je de volledige ervaring, waar Git echt de waarheid is en het cluster geen geheimen heeft. Je kunt het altijd versoepelen voor een specifieke applicatie die echt handmatige handling nodig heeft, maar maak dat de uitzondering die je bewust kiest.
Private repositories
Echte repos zijn meestal private, dus ArgoCD heeft credentials nodig:
# Met de CLI
argocd repo add https://github.com/yourorg/private-repo.git \
--username git \
--password ghp_yourtoken
# Of als Kubernetes secret
kubectl create secret generic private-repo \
-n argocd \
--from-literal=url=https://github.com/yourorg/private-repo.git \
--from-literal=username=git \
--from-literal=password=ghp_yourtoken
kubectl label secret private-repo -n argocd \
argocd.argoproj.io/secret-type=repository
Voor SSH:
argocd repo add git@github.com:yourorg/private-repo.git \
--ssh-private-key-path ~/.ssh/id_rsa
Helm en Kustomize
ArgoCD spreekt zowel Helm als Kustomize native, dus je hoeft zelden iets met de hand plat te slaan.
Helm charts
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prometheus
namespace: argocd
spec:
source:
repoURL: https://prometheus-community.github.io/helm-charts
chart: prometheus
targetRevision: 25.0.0
helm:
values: |
server:
persistentVolume:
enabled: true
size: 10Gi
destination:
server: https://kubernetes.default.svc
namespace: monitoring
Kustomize overlays
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-production
spec:
source:
repoURL: https://github.com/yourorg/yourrepo.git
path: apps/my-app/overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: production
Wijs het naar een directory met een kustomization.yaml en het regelt de rest.
Fouten die ik mensen zie maken
Prune vergeten aan te zetten
Zonder prune: true blijft een live resource hangen nadat je het manifest uit Git verwijdert. Je haalt het bestand weg, gaat ervan uit dat het weg is, en weken later struikel je over de wees.
Secrets in Git zetten
Commit nooit plain secrets. Pak in plaats daarvan een van deze:
- Sealed Secrets
- External Secrets Operator met Vault erachter
- SOPS encrypted secrets
De “alleen deze keer” handmatige wijziging
Op het moment dat je iets met de hand fixt, heb je drift gemaakt. Of commit de fix naar Git, of laat selfHeal het ongedaan maken. Er is geen stil tussengebied dat schoon blijft.
Eén gigantische Application voor alles
Eén Application die je hele platform beheert wordt snel een zooi. Splits per service of team zodat een sync-fout op één plek niet alles blokkeert.
Waar je hierna heen gaat
Je hebt nu een werkende GitOps-loop. De interessante features bouwen erbovenop:
- App-of-Apps pattern: beheer applicaties met applicaties
- Drift detection: monitor op ongeautoriseerde wijzigingen
- Disaster recovery: herbouw hele clusters vanuit Git
- ApplicationSets: genereer applicaties dynamisch
- Notifications: Slack of email bij sync events
Mijn aanbeveling
- Begin met één applicatie en krijg de workflow in je vingers.
- Zet automated sync plus selfHeal aan zodat je voelt wat Git-als-waarheid echt doet.
- Gebruik een dedicated gitops repo, los van je applicatie broncode.
- Stel webhooks in zodat sync triggert op push in plaats van polling.
- Merk op wanneer je handmatige controle wilt en vraag wat het systeem mist in plaats van naar kubectl te grijpen.
Ga terug naar dat eerste gevoel dat ik beschreef, een cluster vragen wat het is en het antwoord niet vertrouwen. Zodra het antwoord in Git leeft en het cluster zichzelf eerlijk houdt, is die onzekerheid weg, en wil je er niet meer naar terug.
