Ah, software-infrastructuur en servicesbeheer… een onuitputtelijke bron van spirituele wijsheid en verrijking. Het kan je zóveel leren over het leven, over liefde… over de mensheid. Eén van de vele parallellen met infrastructuurwerk is dat het in het begin allemaal eenvoudig lijkt; de monsters en uitdagingen die onder de oppervlakte sluimeren laten zich pas zien wanneer je oud en vermoeid bent geworden (wat in de softwarewereld gelukkig vrij snel gaat: werk je meer dan een jaar aan hetzelfde, dan ben je al bijna een fossiel).
Neem eens een moment om een veelvoorkomende reis in dit vakgebied te overwegen. Je komt bij een startup binnen als “DevOps-medewerker”, begint te knutselen met een homelab, of zet een website op voor de lokale schaakclub. Alles lijkt aanvankelijk heerlijk eenvoudig: je zet een oude laptop naast een vergeten ethernetpoort of huurt een VPS voor een paar cent per dag, en flasht het meest veelbelovende Google-resultaat voor “goed gratis serverbesturingssysteem alsjeblieft help geen Chinese scams geen Microsoft”. Je installeert een paar pakketten, legt je collega’s uit hoe ze het systeem kunnen bereiken, en wacht af.
Uiteindelijk gaat er iets mis: de server raakt door zijn geheugen heen, een knaagdier knaagt door het moederbord, of je server heeft het ongeluk een “gewone gebruiker” te ontmoeten (zie Figuur 1). Op dat moment besef je dat je server en zijn gebruikers misschien toch monitoring nodig hebben. Je zit een nacht lang te surfen over het internet en hebt de volgende dag een paar componenten samengevoegd tot een prachtige observability-stack. Misschien is het een SystemD drop-in, misschien ElasticSearch, Grafana en Metricbeat. Misschien heb je Prometheus en een paar exporters geïnstalleerd. Hoe dan ook, alles lijkt prima… tot op een dag…

Figuur 1: Een doorsnee gebruiker die je zorgvuldig geconfigureerde VPS volledig negeert en onderuit haalt.

Figuur 2: Scheiding van configuratie- en runtime-omgevingen door het gebruik van tools zoals Ansible.
Versiebeheer
Wat betreft versiebeheer krijg je, wanneer je je infrastructuur door deze lens bekijkt, een aantal nieuwe keuzes. In combinatie met Ansible is het vrij gebruikelijk om je configuratie te versioneren via Git en GitOps (bijvoorbeeld met GitLab-runners) te gebruiken om je systemen te deployen op basis van die configuratie.
Doordat je de configuratie hebt gescheiden van de onderliggende engine, kun je uitsluitend de onderliggende systeemcode die daadwerkelijk door engineers beheerd moet worden in Git versioneren, terwijl de systeemstatus zelf op een andere plek wordt opgeslagen.
Voor sommigen lijkt dit zinloos of onnodig eigenzinnig. En toch gebruikt zelfs Kubernetes een etcd-database voor het opslaan en beheren van de configuratie van de services waarvoor het verantwoordelijk is. De systeemstatus en objecten worden in een database opgeslagen, zodat het systeem gebruik kan maken van replicatie en kan schalen tot ver buiten wat een set GitLab-runners kan bieden, terwijl tegelijkertijd de daadwerkelijke broncode van Kubernetes afzonderlijk kan worden aangepast en geversioneerd.
Als je doel is om een paar services op te zetten, is de “gebruikelijke manier” prima. Maar als je werkt aan het mogelijk maken dat je bedrijf of klanten zelfstandig systemen kunnen opzetten, vergelijkbaar met Kubernetes of Azure, dan kan de extra flexibiliteit van het opslaan van configuratie “op je eigen manier” de extra moeite ruimschoots waard worden.
Pre-flight configuratievalidatie
Wat betreft pre-flight validatie van configuratie: wanneer je infrastructuur vanuit dit perspectief bekijkt, ontstaat opnieuw een belangrijk ontwerpkeuzegebied.
In klassieke Ansible- of Terraform-workflows wordt validatie vaak impliciet uitgevoerd tijdens de uitvoering zelf: je draait een plan, voert een playbook uit, en ontdekt tijdens de deploy-fase of iets wel of niet klopt. Dit werkt prima op kleinere schaal, maar wordt fragiel zodra de omgeving complexer wordt en veranderingen sneller en door meer teams worden doorgevoerd.
Door configuratie, deployment en runtime expliciet van elkaar te scheiden, kun je validatie naar voren trekken in de keten. In plaats van te wachten tot een systeem daadwerkelijk wordt uitgerold, kun je een “pre-flight check” uitvoeren op de geprojecteerde configuratie: een gecontroleerde evaluatie waarin wordt bepaald of de gewenste toestand überhaupt consistent, compleet en uitvoerbaar is.
Dit kan variëren van eenvoudige schema-validatie tot semantische checks die controleren of resources niet conflicteren, afhankelijkheden kloppen, of policies worden nageleefd voordat er ook maar één wijziging richting productie gaat. Op grotere schaal wordt dit essentieel: fouten die pas tijdens deployment zichtbaar worden, zijn niet alleen duur, maar kunnen ook zich horizontaal verspreiden door gekoppelde systemen.
In moderne platforms zie je dit principe terug in verschillende vormen. Kubernetes bijvoorbeeld voert een declaratieve reconciliatie uit waarbij gewenste toestand eerst wordt gevalideerd en daarna continu wordt vergeleken met de werkelijke toestand in etcd. Andere systemen bouwen hier extra lagen bovenop, zoals policy engines of admission controllers, die configuraties blokkeren of aanpassen vóórdat ze überhaupt worden toegepast.
Het resultaat van zo’n pre-flight laag is niet alleen betrouwbaarheid, maar ook een verschuiving in verantwoordelijkheid: van “we hopen dat de deploy werkt” naar “we weten vooraf dat deze wijziging coherent is binnen het systeemmodel”.
Wanneer je je infrastructuur koppelt aan een publieke API, een intern developerportaal of een team dat verantwoordelijk is voor service delivery, neemt de kans aanzienlijk toe dat fouten hun weg naar je backend vinden. Zodra je vastzit aan één specifieke manier van het opslaan, beheren en representeren van je configuratie, wordt het bovendien moeilijk om die configuratie te valideren op compliance, veiligheid en correctheid.
Sta je jezelf echter toe om zelf te bepalen hoe configuratie er voor jouw platform uitziet, dan kun je gebruikmaken van een breed scala aan tools — zoals OpenAPI-specificaties — of zelfs eigen, op maat gemaakte code om configuraties te valideren, aan te passen of te weigeren op basis van wat je überhaupt wilt ondersteunen binnen je platform.
