GitOps met Argo CD: declaratieve Kubernetes-deployments

Argo CD maakt van een Git-repository de enige bron van waarheid voor je clusterstatus. Je commit manifests, Argo CD reconcilieert ze. Deze tutorial loopt door een complete GitOps-opzet: Argo CD installeren met Helm, een applicatie deployen via de Application CRD, sync policies configureren, resources ordenen met sync waves, een cluster bootstrappen met het app-of-apps pattern, secrets veilig afhandelen, en meerdere clusters beheren vanuit een enkel controlplane.

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
  • kubectl geconfigureerd 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

GitOps is bedacht door Weaveworks in 2017 en geformaliseerd door het OpenGitOps-project (een CNCF-werkgroep) in vier principes:

  1. Declaratief. De volledige gewenste systeemstatus staat als declaraties in Git (Kubernetes-manifests, Helm-charts, Kustomize overlays).
  2. Versiebeheerd en onveranderlijk. Elke wijziging is een Git-commit. Rollbacks worden git revert. Audittrails worden git log.
  3. Automatisch gepulled. Een agent in het cluster haalt wijzigingen op uit Git. De CI-pipeline heeft nooit clustercredentials nodig.
  4. 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.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.