← Projecten

GitOps in de praktijk: Flux op het werk, Gitea Actions thuis

Een werkwijze waarin Git de enige weg naar productie is: Flux voor Kubernetes op het werk, Gitea Actions voor het homelab en deze website. Met de valkuilen die je pas tegenkomt als je het echt doet.

Het principe

Productie verandert alleen via Git. Op het werk en thuis werk ik volgens hetzelfde patroon:

  1. Wijzigingen gaan naar een ontwikkelbranch, met validatie in CI.
  2. Een merge request naar main beschrijft wat er verandert, wat de impact is en hoe je terugdraait.
  3. Een mens merget. Pas daarna rolt het uit.
  4. Een controller (Flux op het werk, een reconciler of runner thuis) brengt de werkelijkheid in lijn met main.

Geen losse feature-branches die wekenlang openstaan, geen handmatige kubectl apply die niemand kan terugvinden.

Op het werk: Flux

Meerdere repositories per domein: platform-infrastructuur, observability, message brokers, beheer. Elk met een eigen Flux-Kustomization die main volgt. Helm-charts komen via HelmReleases, dashboards als ConfigMaps uit Git, en alertregels met promtool-tests in de pipeline.

Valkuilen die ik tegenkwam

  • Een ontbrekende ConfigMap blokkeert de hele HelmRelease. Verwijst valuesFrom naar iets dat niet bestaat, dan wordt geen enkele wijziging aan die release meer uitgerold, ook niet de wijzigingen die er niets mee te maken hebben. valuesFrom en de bestanden waarnaar het verwijst, wijzig je dus samen.
  • pending-upgrade is een stille blokkade. Flux reconcilet zichtbaar, maar er gebeurt niets. Kijk in de Helm-storage en vergelijk de digests.
  • Immutable velden. Een StatefulSet met een ander volumeClaimTemplate kan Flux niet patchen. kubectl delete sts --cascade=orphan laat de pods en volumes staan, en Flux maakt de StatefulSet opnieuw aan.
  • Prune tegen een beschermde namespace. Een ValidatingAdmissionPolicy beschermt kritieke namespaces tegen verwijderen. Haal je een namespace uit Git terwijl het label er nog op staat, dan faalt de prune, en blijft de Kustomization op Ready=False staan. Nieuwe wijzigingen worden dan wel toegepast, maar de status liegt niet: eerst het label weghalen, dan prunen.
  • Grafana-dashboards via een sidecar schreven op een nieuwe Grafana-versie het bestand na de eerste provisioning opnieuw. Dashboards als ConfigMap mounten werkt voorspelbaar.
  • Istio: wie eerst komt, wint. Twee VirtualServices voor dezelfde host op dezelfde gateway worden samengevoegd op volgorde van aanmaken. Een nieuwe route naast een bestaande prefix: / verliest dus. Zet specifieke routes in dezelfde VirtualService, bovenaan.
  • Branches die uit elkaar lopen. Als development en main uit elkaar zijn gegroeid, trek ik ze gelijk zonder force-push en zet ik oude branches als tag in een archief. Zo gaat er niets verloren, en is de geschiedenis leesbaar.

Productiewijzigingen

Een merge request voor productie bevat altijd: wat er verandert, wat helm template of kustomize build laat zien, en hoe je terugdraait. Een server-side dry-run tegen het cluster vangt schemafouten vóór de merge. Het model dat mij helpt, mag MR's aanmaken, maar niet mergen.

Thuis: Gitea Actions

Alles in het homelab staat in een eigen Gitea:

RepositoryUitrol
Monitoring (Prometheus, Alertmanager, regels, runbooks)Merge naar main → validatie → deploy via SSH
Reverse proxy (een vhost per dienst)Merge naar main → nginx -t → deploy
Agents op alle hostsAnsible vanuit de repository (uitrol wordt opnieuw ingericht)
MediastackDocker Compose in Git
Deze websiteReconciler op de server

Branch protection op development: geen force-push. Validatie draait op elke pull request.

De website als voorbeeld

Deze site laat zien hoe ver je kunt gaan op een thuisserver:

  • CI valideert broncode, content, dependencies (npm audit), runtimegedrag met een rooktest, en de container (Trivy).
  • De container draait als non-root met een read-only root filesystem.
  • Op de server haalt een pull-gebaseerde reconciler alleen gevalideerde main-commits op, bouwt een eigen image per commit, activeert die, en doet interne en publieke nacontroles. Mislukt er iets, dan gaat hij automatisch terug naar de vorige release.

Wat het oplevert

  • Elke wijziging is terug te vinden: wie, wat, wanneer en waarom.
  • Terugdraaien is een revert.
  • Validatie vangt fouten vóór productie, niet erna.
  • Ook een AI-assistent werkt binnen dezelfde vangrails: hij kan voorstellen, maar niets uitrollen.