Je begint met één ArgoCD Application. Dan vijf. Dan twintig. Voordat je het weet beheer je honderden Applications, en de handmatige overhead doodt je productiviteit.
Het App-of-Apps pattern lost dit op: één root application die alle andere applications beheert.
Dit is hoe ik elke GitOps repository structureer, en het schaalt van homelab tot enterprise.
Het Probleem: Application Sprawl
Wanneer je ArgoCD voor het eerst adopteert, maak je Applications handmatig aan:
kubectl apply -f apps/frontend.yaml
kubectl apply -f apps/backend.yaml
kubectl apply -f apps/database.yaml
# ... herhaal voor elke service
Dit werkt voor kleine deployments. Maar het creëert problemen:
- Handmatige creatie: Elke nieuwe service heeft handmatige Application creatie nodig
- Geen hiërarchie: Alle applicaties zijn peers, geen logische groepering
- Moeilijke onboarding: Nieuwe teamleden weten niet wat er bestaat
- Bootstrap probleem: Hoe recreëer je alle Applications na cluster verlies?
De ironie: je gebruikt GitOps voor applicaties maar niet voor de Applications zelf.
De Oplossing: App-of-Apps
App-of-Apps is simpel: Applications die Applications aanmaken.
flowchart TD
ROOT["Root Application<br/>(beheert alles)"]
ROOT --> INFRA["Infrastructure App"]
ROOT --> PLATFORM["Platform App"]
ROOT --> APPS["Apps App"]
INFRA --> NS["namespaces"]
INFRA --> RBAC["rbac"]
INFRA --> NP["netpols"]
PLATFORM --> MON["monitoring"]
PLATFORM --> LOG["logging"]
PLATFORM --> ING["ingress"]
APPS --> FE["frontend"]
APPS --> BE["backend"]
APPS --> WK["workers"]
De root Application synct een directory met Application manifests. Die Applications syncen de daadwerkelijke workloads.
De Repository Structuur
Dit is hoe ik GitOps repositories organiseer:
gitops-repo/
├── clusters/
│ ├── production/
│ │ ├── root.yaml # Root application
│ │ ├── infrastructure/
│ │ │ ├── app.yaml # Infrastructure app-of-apps
│ │ │ ├── namespaces.yaml
│ │ │ ├── rbac.yaml
│ │ │ └── network-policies.yaml
│ │ ├── platform/
│ │ │ ├── app.yaml # Platform app-of-apps
│ │ │ ├── monitoring.yaml
│ │ │ ├── logging.yaml
│ │ │ └── ingress.yaml
│ │ └── apps/
│ │ ├── app.yaml # Workloads app-of-apps
│ │ ├── frontend.yaml
│ │ ├── backend.yaml
│ │ └── workers.yaml
│ └── staging/
│ └── ... (vergelijkbare structuur)
├── base/
│ ├── monitoring/
│ │ ├── kustomization.yaml
│ │ └── ...
│ └── ...
└── apps/
├── frontend/
│ ├── base/
│ └── overlays/
│ ├── staging/
│ └── production/
└── backend/
└── ...
De Hiërarchie Bouwen
Level 1: De Root Application
De root is de enige Application die je handmatig aanmaakt:
# clusters/production/root.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: main
path: clusters/production
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Dit synct de clusters/production directory, die de volgende level apps bevat.
Level 2: Categorie Applications
Groepeer gerelateerde applicaties per categorie:
# clusters/production/infrastructure/app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: main
path: clusters/production/infrastructure
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Dit synct alle YAML bestanden in de infrastructure directory — inclusief de individuele Applications.
Level 3: Leaf Applications
De daadwerkelijke workload applicaties:
# clusters/production/apps/frontend.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: main
path: apps/frontend/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: frontend
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
Sync Waves: Volgorde Controleren
Niet alles kan tegelijk deployen. Databases voor apps. Namespaces voor pods.
ArgoCD sync waves controleren deployment volgorde:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: namespaces
annotations:
argocd.argoproj.io/sync-wave: "-10" # Deploy eerst
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-5" # Na namespaces
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
annotations:
argocd.argoproj.io/sync-wave: "0" # Default, na platform
Lagere nummers syncen eerst. Ik gebruik:
-10: Namespaces, RBAC-5: Platform componenten (monitoring, logging, ingress)0: Applicaties10: Post-deployment jobs
Health Checks en Dependencies
ArgoCD wacht tot applicaties healthy zijn voordat het doorgaat:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: database
spec:
# ... source en destination
syncPolicy:
automated:
selfHeal: true
syncOptions:
- CreateNamespace=true
# Health bepaalt wanneer "ready"
De database Application is healthy wanneer zijn pods draaien. Downstream applicaties in latere sync waves wachten hierop.
Multi-Cluster met App-of-Apps
Voor meerdere clusters, breid de hiërarchie uit:
gitops-repo/
├── clusters/
│ ├── production-eu/
│ │ └── root.yaml
│ ├── production-us/
│ │ └── root.yaml
│ └── staging/
│ └── root.yaml
Elk cluster heeft zijn eigen root. Ze kunnen base configuraties delen via Kustomize overlays.
Een Nieuwe Application Toevoegen
De kracht van App-of-Apps: een nieuwe applicatie toevoegen is gewoon een bestand:
# 1. Maak het Application manifest
cat > clusters/production/apps/new-service.yaml <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: new-service
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
path: apps/new-service/overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: new-service
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
EOF
# 2. Maak de applicatie manifests
mkdir -p apps/new-service/overlays/production
# ... voeg je Kustomization toe
# 3. Commit en push
git add .
git commit -m "Add new-service"
git push
ArgoCD detecteert het nieuwe Application manifest en deployt het automatisch. Geen kubectl. Geen UI klikken.
ApplicationSets: Het Volgende Level
Voor echt dynamische application generatie, overweeg ApplicationSets:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: apps
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/yourorg/gitops-repo.git
revision: main
directories:
- path: apps/*
template:
metadata:
name: '{{path.basename}}'
spec:
project: default
source:
repoURL: https://github.com/yourorg/gitops-repo.git
targetRevision: main
path: '{{path}}/overlays/production'
destination:
server: https://kubernetes.default.svc
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
selfHeal: true
Dit genereert een Application voor elke directory onder apps/. Voeg een directory toe, krijg een Application.
Veelvoorkomende Fouten
Fout 1: Te Diepe Nesting
Drie levels is meestal genoeg. Meer creëert verwarring:
✗ root → category → subcategory → team → service → component
✓ root → category → service
Fout 2: Finalizers Vergeten
Zonder finalizers laat het verwijderen van een app-of-apps orphan applications achter:
metadata:
finalizers:
- resources-finalizer.argocd.argoproj.io
Dit zorgt dat child applications verwijderd worden wanneer parent verwijderd wordt.
Fout 3: Circulaire Dependencies
Laat Application A niet afhangen van Application B die afhangt van Application A.
Fout 4: Alles in Één Sync Wave
Als alles tegelijk deployt, krijg je race conditions. Gebruik sync waves.
Mijn Standaard Structuur
Na jaren itereren, dit is mijn go-to structuur:
clusters/{env}/
├── root.yaml
├── infrastructure/ # Sync wave -10
│ ├── app.yaml
│ ├── namespaces.yaml
│ ├── rbac.yaml
│ └── network-policies.yaml
├── platform/ # Sync wave -5
│ ├── app.yaml
│ ├── argocd.yaml # ArgoCD beheert zichzelf
│ ├── monitoring.yaml
│ ├── logging.yaml
│ ├── ingress.yaml
│ └── cert-manager.yaml
├── data/ # Sync wave -2
│ ├── app.yaml
│ └── databases.yaml
└── apps/ # Sync wave 0
├── app.yaml
└── {service}.yaml
Deze volgorde zorgt:
- Namespaces bestaan voordat iets deployt
- Platform tools klaar voordat apps ze nodig hebben
- Databases draaien voordat apps connecten
Het Bootstrap Probleem Opgelost
Herinner je GitOps Disaster Recovery? App-of-Apps maakt recovery triviaal:
# 1. Nieuw cluster
# 2. Installeer ArgoCD
# 3. Apply ÉÉN bestand
kubectl apply -f clusters/production/root.yaml
# 4. Alles recreëert
Eén commando bootstrapt je hele cluster. Dat is de kracht van App-of-Apps.
App-of-Apps transformeert GitOps van “bestanden die dingen deployen” naar “zelf-beschrijvende infrastructuur.” Je Git repository wordt de complete specificatie van wat zou moeten bestaan.
