Een paar jaar lang draaide mijn homelab cluster zonder veel drama. Nodes kwamen en gingen, workloads schoven rond, en het ergste waar ik mee te maken kreeg was af en toe een pod die vastzat in CrashLoopBackOff. Het soort stabiliteit waarbij je stopt met nadenken over wat er gebeurt als het allemaal verdwijnt.

Toen runde ik op een avond een terraform destroy tegen de verkeerde workspace. Tegen de tijd dat ik het doorhad, was de control plane weg. Niet degraded. Weg.

Dat is het moment waarop disaster recovery ophoudt een planningssheet te zijn en een vraag wordt die je nu meteen moet beantwoorden: het cluster is weg, de hardware is kapot, de regio is down, of je hebt een destroy verkeerd ingetypt. Wat doe je hierna?

Als je GitOps goed hebt gedaan, is het antwoord bijna saai. Je provisiont een vers cluster, wijst ArgoCD naar Git, en wacht. Je infrastructuur bouwt zichzelf opnieuw op vanuit de repository. Dat is de hele belofte: Git is je backup.

Ik wil doorlopen waarom dat werkt, wat waar moet zijn om het te laten werken, en waar het je stilletjes in de steek laat. Want de eerste keer dat ik dit echt testte, leerde ik dat “Git is je backup” waar is voor infrastructuur en een gevaarlijke leugen voor data.

Waarom GitOps disaster recovery verandert

Traditionele DR is een tweede kopie van iets primairs. Je neemt het cluster, je snapshot etcd, je draait Velero, je verstuurt die backups ergens veilig, en je houdt een restore procedure aan die je hopelijk hebt getest. Recovery tijd wordt gemeten in uren, en de meeste van die uren gaan naar het terugkopiëren van state.

GitOps draait om welk ding primair is. Het cluster stopt de bron van waarheid te zijn en wordt een afgeleid artefact. De echte state leeft in Git. Het cluster is gewoon de huidige rendering van die state, en je kunt het weggooien en opnieuw renderen.

flowchart TD
    subgraph traditioneel["Traditioneel"]
        TC["Cluster<br/>(primair)"] -->|backup| TB["Backup<br/>(secundair)"]
    end

    subgraph gitops["GitOps"]
        GG["Git<br/>(primair)"] -->|sync| GC["Cluster<br/>(afgeleid)"]
    end

Zodra je dat internaliseert, verandert recovery van vorm. Je herstelt geen cluster. Je bootstrapt een nieuwe en laat die reconcilen. Recovery tijd daalt naar minuten, omdat er geen aparte backup is om terug te kopiëren. Dit is resilience by design in plaats van resilience er achteraf op geschroefd: het systeem dat je cluster bouwt is hetzelfde systeem dat het herbouwt.

Het past ook bij hoe ik mijn infrastructuur wil begrijpen. Als het hele ding gedeclareerd staat in een repo die ik kan lezen, zijn er geen verborgen stukjes state die alleen bestaan omdat iemand om 2 uur ’s nachts een kubectl apply draaide en het nooit opschreef.

Wat er in Git moet staan om dit te laten werken

Hier zit de adder, en het is waar de meeste GitOps setups stilletjes hun eigen DR test falen: dit werkt alleen als alles echt in Git staat. Alles wat alleen in het draaiende cluster bestaat, ben je kwijt en moet je met de hand opnieuw aanmaken, onder druk, terwijl productie down is.

Dus voordat we recovery doorlopen, lopen we eerst door wat er in de repo hoort te leven.

Application manifests

Al je workload definities. Deployments, Services, ConfigMaps, Helm charts met hun values files, Kustomize overlays. Dit is het makkelijke deel, en de meeste teams hebben dit al. Als je überhaupt GitOps doet, staan je apps hier.

Infrastructuur configuratie

De cluster-level resources die makkelijk te vergeten zijn omdat niemand ze als onderdeel van een release deployt:

  • Namespaces
  • RBAC (Roles, RoleBindings, ServiceAccounts)
  • NetworkPolicies
  • ResourceQuotas
  • PodSecurityPolicies / Pod Security Standards

Deze definiëren je security grenzen en isolatie. Een cluster dat je herbouwde zonder zijn NetworkPolicies is een cluster met de blast doors open. Behandel ze als eersterangs GitOps content, niet als bijzaak.

Platform componenten

De tools waarvan je applicaties aannemen dat ze er al zijn:

  • Ingress controllers
  • Cert-manager
  • Monitoring stack (Prometheus, Grafana)
  • Logging (Loki, Fluentd)
  • Service mesh, als je er een draait

Staan deze niet in Git, dan komen je apps terug maar routeert er niets naartoe, heeft niets een certificaat, en vlieg je blind omdat je observability stack nooit deployde.

Secrets strategie

Secrets zijn het ene ding dat je echt niet in plaintext kunt committen, dus ze hebben een strategie nodig die het verlies van het cluster overleeft:

  • Sealed Secrets: versleuteld in Git, ontsleuteld in-cluster door een controller
  • External Secrets: tijdens runtime opgehaald uit Vault of een cloud secrets manager
  • SOPS: bestanden versleuteld met keys die jij beheert

De eigenschap die telt voor DR is reproduceerbaarheid. Een secret dat alleen binnen het dode cluster bestaat, ben je kwijt. Welke tool je ook kiest, de versleutelde of gerefereerde vorm moet in Git staan en de decryptie key moet ergens leven waar je nog bij kunt nadat het cluster weg is.

Persistent data

Hier is de harde waarheid die ik op de vervelende manier leerde: GitOps backupt je data niet.

Databases, object storage, alles met state, niets daarvan staat in Git en niets daarvan komt terug wanneer ArgoCD synct. GitOps herbouwt de infrastructuur die je data draait. De data zelf heeft een aparte, geteste backup strategie nodig, en als je die stap overslaat herbouw je een perfect, leeg cluster en word je er misselijk van.

Het recovery proces

Met dat alles in Git wordt recovery een sequentie die je daadwerkelijk kunt draaien. Dit is het pad dat ik volg.

Stap 0: heb een recovery playbook

Voordat er iets breekt, schrijf op hoe je terugkomt:

  • Hoe je een vers cluster provisiont
  • De bootstrap commands voor ArgoCD
  • Waar Vault leeft en waar zijn backups zijn
  • Externe dependencies (DNS, load balancers)

En draai het dan. Een playbook dat je nooit hebt uitgevoerd is fictie, en je wilt de gaten niet ontdekken terwijl productie down is. De eerste keer dat ik de mijne “testte”, vond ik drie ongedocumenteerde handmatige stappen die me een uur hadden gekost op het slechtst denkbare moment.

Stap 1: provision een nieuw cluster

Met welke methode dan ook, zorg voor een werkend Kubernetes cluster:

# Terraform
cd terraform/clusters/production
terraform apply

# Of managed Kubernetes
gcloud container clusters create production-recovery \
  --region europe-west1 \
  --num-nodes 3

# Of bare metal
talosctl apply-config --nodes 10.0.0.10 --file controlplane.yaml

Hoe je aan het cluster komt staat bewust los van GitOps. GitOps neemt het over zodra je een werkende API server hebt om mee te praten.

Stap 2: installeer ArgoCD

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wacht tot het ready is
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s

Stap 3: herstel secrets toegang

Verbind External Secrets terug met Vault zodat de rest van de sync de credentials heeft die het nodig heeft:

# Configureer Vault authenticatie voor het nieuwe cluster
kubectl create secret generic vault-token \
  -n external-secrets \
  --from-literal=token=$VAULT_TOKEN

Stap 4: verbind ArgoCD met Git

# Voeg je GitOps repository toe
argocd repo add https://github.com/yourorg/gitops-repo.git \
  --username git \
  --password $GIT_TOKEN

# Of via Secret
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: gitops-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  url: https://github.com/yourorg/gitops-repo.git
  username: git
  password: ${GIT_TOKEN}
EOF

Stap 5: apply de root application

Als je het App-of-Apps pattern gebruikt (dat zou je moeten), trekt één manifest al het andere binnen:

kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/gitops-repo.git
    targetRevision: main
    path: clusters/production
  destination:
    server: https://kubernetes.default.svc
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF

Stap 6: wacht

Dit is het deel dat voor mij nog steeds een beetje magisch voelt. ArgoCD reconcilet alles op eigen houtje:

  1. Ontdekt alle applicaties onder de root app
  2. Maakt namespaces en RBAC aan
  3. Deployt platform componenten
  4. Deployt je applicaties

Kijk het aan het werk:

argocd app list
argocd app get root --refresh

Of in de UI: kubectl port-forward svc/argocd-server -n argocd 8080:443

Stap 7: herstel data

Dit is waar je aparte data backup strategie zich terugverdient:

# Herstel database
pg_restore -d myapp < /backups/myapp-latest.dump

# Of Velero restore
velero restore create --from-backup production-daily-latest

Stap 8: update DNS en load balancers

Wijs extern verkeer naar het nieuwe cluster:

  • Update DNS records
  • Herconfigureer CDN origins
  • Update load balancer targets

Hoe snel kun je echt herstellen

Het eerlijke antwoord is “het hangt ervan af, en de afhankelijkheid is je data”. Voor de infrastructuur helft is dit wat ik meestal zie:

FaseTijd
Cluster provisioning5-15 min
ArgoCD installatie2-3 min
Secrets setup1-2 min
GitOps sync (klein cluster)5-10 min
GitOps sync (groot cluster)15-30 min
Data restoreVarieert sterk
DNS propagatie5-60 min

Infrastructuur totaal: 20-60 minuten.

Data restore is de wildcard. Het schaalt met je data volume en je backup ontwerp, en het is bijna altijd het langste stuk van de recovery. Het schone, snelle deel is het GitOps deel. Het trage, angstige deel is het data deel, precies het deel dat GitOps niet aanraakt.

Waar het je bijt

Ik ben de meeste van deze op de harde manier tegengekomen, dus hier is wat er daadwerkelijk misgaat.

Ontbrekende secrets

Als Vault onbereikbaar is, kan External Secrets niets ophalen en starten je workloads nooit. Je herbouwt het hele cluster en kijkt dan toe hoe elke pod wacht op een secret dat niet aankomt.

Mitigatie: draai Vault in HA op infrastructuur die los staat van het cluster dat het bedient, back het regelmatig op, en overweeg het te repliceren naar een tweede locatie.

Externe dependencies

Je cluster leunt waarschijnlijk op dingen die helemaal niet in Git staan:

  • Cloud IAM roles
  • DNS zones
  • SSL certificaten van Let’s Encrypt
  • Externe databases

Mitigatie: documenteer elke externe dependency, en beter nog, beheer ze met Terraform of Crossplane in dezelfde repo zodat ze samen met al het andere herbouwen.

Circulaire dependencies

ArgoCD moet draaien voordat het ArgoCD kan deployen. Het klassieke bootstrap probleem, en het laat je struikelen als je enige kopie van “hoe installeer ik ArgoCD” een door-ArgoCD-beheerde applicatie was.

Mitigatie: houd een bootstrap script aan dat werkt met kale kubectl, geen ArgoCD vereist. Zodra ArgoCD draait, laat het zichzelf vanaf daar beheren.

Data verlies

GitOps herbouwt infrastructuur, geen data. Een verouderde database backup betekent dat je het cluster herstelt en alsnog uren of dagen aan data verliest.

Mitigatie: een aparte, geteste data backup strategie met echte restore tests, niet alleen backup jobs waarvan je aanneemt dat ze werken.

Het recovery manifest

Na mijn eigen bijna-ongeluk begon ik één bestand bij te houden dat alles documenteert wat recovery nodig heeft, op één plek. Als de boel in brand staat, wil je niet door een repo grep-en op zoek naar het Vault adres.

# recovery-manifest.yaml
recovery:
  cluster:
    provisioner: terraform
    path: terraform/clusters/production

  secrets:
    method: external-secrets
    vault-address: https://vault.internal.company.com
    vault-backup: s3://backups/vault-snapshots/

  gitops:
    repo: https://github.com/yourorg/gitops-repo.git
    path: clusters/production
    root-app: apps/root.yaml

  data:
    databases:
      - name: postgres-main
        backup-location: s3://backups/postgres/
        restore-command: |
          pg_restore -d main /backups/latest.dump
    volumes:
      - name: uploads
        backup-location: s3://backups/uploads/

  external-dependencies:
    - DNS zone: beheerd in Route53, terraform/dns/
    - Load balancer: aangemaakt door terraform/clusters/
    - Vault: vault.internal.company.com (HA, aparte infra)

  contacts:
    - oncall: ops-team@company.com
    - escalation: platform-lead@company.com

Commit het naar Git. Review het per kwartaal. Test het minstens één keer per jaar.

De mindset die dit vereist

GitOps DR vraagt je om anders over je clusters te denken, en die verschuiving is het waard om hardop te zeggen:

  1. Clusters zijn vee, geen huisdieren. Als recovery goedkoop is, stop je met het krampachtig beschermen van elk individueel cluster.

  2. State hoort in Git. Elke kubectl apply die je draait zonder de wijziging te committen is DR schuld die op het slechtste moment vervalt.

  3. Data is zijn eigen probleem. GitOps handelt infrastructuur netjes af en doet nooit alsof het je data afhandelt. Die grens is het belangrijkste om te onthouden.

  4. Oefen recovery. De eerste keer dat je een echte recovery draait, zou niet tijdens een echte disaster moeten zijn.

Mijn recovery checklist

## Pre-Disaster (doe nu)
- [ ] Alle manifests in Git
- [ ] Secrets strategie gedocumenteerd en getest
- [ ] Vault gebackupt en HA geconfigureerd
- [ ] Recovery playbook geschreven
- [ ] Externe dependencies gedocumenteerd
- [ ] Data backup strategie geïmplementeerd
- [ ] Recovery getest in laatste 90 dagen

## Tijdens Recovery
- [ ] Provision nieuw cluster
- [ ] Installeer ArgoCD
- [ ] Herstel secrets decryptie capability
- [ ] Verbind ArgoCD met Git
- [ ] Apply root application
- [ ] Monitor sync voortgang
- [ ] Herstel data van backups
- [ ] Update DNS/load balancers
- [ ] Verifieer applicatie health
- [ ] Notificeer stakeholders

De beste disaster recovery is degene die je nooit nodig hebt. De op een na beste is degene die je vaak genoeg hebt geoefend dat wanneer het eindelijk echt gebeurt, het voelt als gewoon weer een doordeweekse dag.