Operations
Over-engineering bij email-agents: gids voor ops-leads
Een interne email-agent met 200 rpm heeft geen drie Kubernetes-clusters en Argo CD nodig. Wel een VPS van 4 euro, Caddy en een systemd-timer. De rest is belasting.

De operations-manager van een Utrechts logistiekbedrijf had vier browsertabs open toen we gingen zitten. Twee Argo CD-dashboards, één voor staging, één voor productie. Een Grafana-bord dat geel oplichtte. En de Hacker News-thread over een telefoon met gebarsten scherm in een wc-stortbak, die een website serveerde over het publieke internet.
"We zijn aan het moderniseren," zei ze. Wat ze aan het moderniseren was: een interne email-triage-agent. Die doet 200 requests per minuut op een rustige dag. En draait verspreid over drie Kubernetes-clusters.
De Hetzner CX22 die dezelfde workload had kunnen draaien, kost €4,51 per maand. De stack die zij had gebouwd kostte ergens boven de €600 per maand, plus drie uur per week van een engineer die ook product had kunnen bouwen.
Dit is de veldgids. Achttien fouten, gerangschikt op hoeveel pijn het doet om ze terug te draaien.
Wat de wc-telefoon-post eigenlijk zei
De thread stond deze week op de voorpagina van HN. Iemand monteert een telefoon met gebarsten scherm in een porseleinen stortbak, draait er een kleine webserver op, wijst DNS aan een thuisverbinding, en serveert wekenlang de voorpagina. De reacties splitsten in twee kampen. Het eerste kamp zei: "Leuk, maar daar runt je geen bedrijf op." Het tweede kamp, dat gelijk had, zei dat de demo nou juist het punt is: infrastructuur is wat overleeft, niet wat schaalt.
De meeste mensen in dat tweede kamp werken aan systemen met honderd miljoen gebruikers. De ops-manager in Utrecht las het van buiten dat gesprek en vatte de post op als toestemming om door te bouwen. Haar workload is tenslotte "echte software". Dat ding met die telefoon in de wc is voor hobbyisten. Kubernetes is voor volwassenen.
De wiskunde trekt zich er niets van aan. Een interne email-triage-agent op 200 rpm zit feitelijk dichter bij een telefoon in een stortbak dan bij een hyperscaler. De taak is: ontvang een mail, classificeer 'm, route 'm, beantwoord als je zeker bent, escaleer als niet. Eén binary. Eén queue, optioneel. Eén persistente store ter grootte van een medium-format JPG.
De wc-telefoon-post is geen hobbyistenanekdote. Het is een bovengrens voor hoeveel infrastructuur de meeste interne workloads daadwerkelijk nodig hebben.
De referentiestack om tegen te meten
Voor we de achttien noemen, eerst het ding waar ze tegen over-engineeren. Een Hetzner CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) voor €4,51 per maand. Caddy als reverse proxy, die TLS automatisch regelt en vernieuwt. Eén Python- of Go-binary waar de agent in draait. Een systemd-timer als je een periodieke sweep wilt. SQLite voor state. Back-ups via borg naar een tweede bak van €4,51. Een deploy-script van twee regels dat de binary scp't en systemctl restart draait.
Totale kosten: rond de €10 per maand. Totaal aantal bewegende delen: vijf. Herstel na volledig verlies: bouw de bak opnieuw vanuit een Hetzner-snapshot, restore borg, herstart. Twintig minuten als je het al eens hebt gedaan.
Observability voor een systeem van deze omvang is één node-exporter, een heartbeat-teller die de agent na elke succesvolle sweep wegschrijft, en een shellscript van vijf regels dat de operator een mail stuurt als de heartbeat ouder is dan tien minuten. Het dashboard in Utrecht volgde zestig metrics. De ops-manager keek naar drie ervan, en alleen tijdens incidenten. De andere zevenenvijftig hielden zichzelf gezelschap.
# /etc/caddy/Caddyfile — the entire reverse-proxy story
triage.intern.example.nl {
basic_auth {
ops $2a$14$Cn... # bcrypt hash
}
rate_limit {
zone agent { key {remote_host} events 60 window 1m }
}
header {
Strict-Transport-Security "max-age=63072000"
X-Frame-Options DENY
Referrer-Policy no-referrer
}
reverse_proxy 127.0.0.1:8080
}
Nu de achttien, gegroepeerd op hoe omkeerbaar de fout is.
Tier één: zeven fouten die je met een Caddyfile terugdraait
Deze zijn het goedkoopst te repareren. Ze leven volledig in de request-handling-laag. Je gooit de YAML weg, schrijft vijftien regels Caddy-config, en de engineer die de change doorzet zit om 13:00 aan tafel voor de lunch.
1. De nginx-ingress-controller die alleen voor TLS staat
Caddy levert automatische HTTPS. Eén hostnaamregel en TLS staat aan. Geen ingress controller. Geen custom resource definitions.
2. cert-manager die vecht met de rate limits van Let's Encrypt
cert-manager is prima. En tegelijk overbodig als Caddy de listener al doet. Twee systemen die om hetzelfde domein ruziën, is hoe teams aan storingen van vierentwintig uur komen die niemand kan reproduceren.
3. De externe rate-limiter-service
Je hebt geen Envoy plus Redis nodig om een interne agent te throttlen. Caddy heeft de rate_limit-handler. Eén stanza, één getal, klaar.
4. De OAuth2-proxy voor een intranet-service
Als de agent alleen vanaf de kantoor-VPN wordt aangeroepen, is Caddy's basic_auth-directive met een sterk wachtwoord in een password manager precies de juiste hoeveelheid beveiliging. De OAuth2-proxy was logisch bij het vorige bedrijf. Hier is het papierwerk.
5. Security headers via ConfigMaps
HSTS, CSP, X-Frame-Options. Caddy levert een header-directive. Zes regels. Je raakt ze de komende twee jaar niet meer aan.
6. De CDN voor assets die niemand buiten de VPN te zien krijgt
Een interne agent serveert een statuspagina en een handvol CSS-bestanden. CloudFront is indrukwekkend. En tegelijk verbonden met niemand die er iets om geeft.
7. De bare-metal load balancer voor één pod
Als je de tweede pod niet op een whiteboard kunt tekenen, is de load balancer een single point of failure met een statuspagina. Eruit. Wijs DNS naar de bak.
Alle zeven zijn in een middag te verwijderen, omdat het request-pad het meest vervangbare onderdeel van elke stack is. Verkeer gaat waar je het naartoe wijst.
Het is ook de tier waar je de discussie het makkelijkst wint. De engineer die de OAuth2-proxy heeft geïnstalleerd, mag de YAML in een eigen repo bewaren en volgende sprint de nieuwe tool leren, zonder iets te verliezen. De politieke kosten zijn laag, omdat de technische kosten van omschakelen laag zijn. Begin hier, zelfs als de echte verspilling dieper zit. Wins stapelen op.
Tier twee: zes fouten die je met één binary en een systemd-unit terugdraait
Deze zijn lastiger. Ze hebben vorm binnen de applicatie. Je bent een week kwijt, geen middag. Maar de ingreep is lokaal — er hoeft geen platformteam aan tafel.
8. Celery + Redis + RabbitMQ om elke minuut een job te draaien
Een systemd-timer met OnCalendar=*:0/1 en een unit-file zijn samen veertien regels. Celery is een prima keuze voor een workload met echte queue-dynamiek. Email-triage op 200 rpm heeft geen queue-dynamiek.
# /etc/systemd/system/triage.timer
[Unit]
Description=Run the email-triage sweep every minute
[Timer]
OnCalendar=*:0/1
AccuracySec=1s
Persistent=true
[Install]
WantedBy=timers.target
9. Horizontal Pod Autoscaler-drempels bij 200 rpm
200 rpm is ruwweg drie requests per seconde. Een Raspberry Pi trekt dat. Niets gaat schalen, omdat niets hoeft te schalen. De HPA is decoratie.
10. PersistentVolumeClaims voor bestanden die in /var/lib passen
Als de dataset onder een gigabyte zit en met megabytes per maand groeit, woont 'ie in een directory op de VPS en wordt 'ie geback-upt door borg. PVC's zijn hoe je op zondag om 02:00 CSI-drivers staat te debuggen.
11. Postgres-in-een-cluster voor een SQLite-vormig probleem
Eén writer, één reader, geen noemenswaardige concurrente transacties, queries die in microseconden terugkomen. SQLite is het antwoord. Een aparte Postgres-pod met een sidecar en een backup-operator is theater.
12. Een Helm-chart met veertien values.yaml-files voor één binary
Helm is templating. Als het ding dat je template één Deployment, één Service, één Ingress en één ConfigMap is, dan is de template langer dan de applicatie. Vervang door een shellscript.
13. De sidecar log shipper naar een hosted ELK
journalctl plus een bashscript van vijf regels dat de operator mailt bij FAIL dekt negentig procent van wat het ops-team ooit echt gaat bekijken. Bewaar ELK voor de workloads die het verdienen.
Tier drie: vijf fouten waarvoor je Argo CD eruit moet slopen
Dit zijn de fouten die zeer doen. Ze zitten verweven in de platformlaag. Ze terugdraaien betekent het team ervan overtuigen dat het platform zelf de verkeerde keuze was, wat een ander gesprek is dan "laten we de Caddyfile opruimen". Engineers verdedigen deze tier het hardst, omdat ze er echt tijd in hebben gestoken om het te leren. De tool verdedigen tegen de workload, dat is het faalpatroon. De workload is de klant.
14. Argo CD dat een repo met één Deployment erin staat te reconciliëren
GitOps is prachtig bij zestig workloads, acht teams en een compliance officer die een audit trail nodig heeft. Voor één binary is het een continue reconciliatieloop die bestaat om zichzelf iets te reconciliëren te geven.
15. Multi-cluster federation "voor HA"
Je hebt drie clusters omdat iemand erover gelezen heeft. De agent draait in één ervan. De andere twee zijn warm spares voor een failover die nooit is getest. Multi-cluster is logisch boven een bepaald aantal workloads. Daar zit je niet boven.
16. Vault + External Secrets Operator voor twee API-keys
De key van de modelprovider en het SMTP-wachtwoord. Twee strings. Vault is correct op een bepaalde schaal. Bij twee secrets is een EnvironmentFile=, eigendom van de service-user, met mode 0400, correct. Roteer elk kwartaal. Documenteer het pad.
17. Istio voor east-west-verkeer dat geen east heeft
De service mesh zit tussen de agent en zichzelf. De mTLS zit tussen twee helften van dezelfde binary. De control plane gebruikt meer geheugen dan de applicatie. Weghalen.
18. Aparte dev/staging/prod-clusters terwijl een branch-deploy-script het ook doet
Een interne tool van 200 rpm heeft geen staging-omgeving nodig die productie exact spiegelt. Wel een tweede CX22 waar je de staging-branch naartoe pusht. €4,51 per maand, en een deploy-script dat op branchnaam differt.
Wat je maandag in productie zet
Als dit jouw stack is en je herkent meer dan drie van de achttien, probeer dan niet alles tegelijk te migreren. De volgorde die werkt:
- Zet de referentiebak op. Hetzner CX22, Caddy, systemd, jouw agent-binary. Laat 'm een week parallel draaien tegen de productieload die je daadwerkelijk hebt.
- Schakel eerst read-only verkeer om. Triage-beslissingen mag je twee keer nemen en eenmaal weggooien, tot je de nieuwe bak vertrouwt.
- Zodra de pariteit vaststaat, sloop je het tier-drie-platform als laatste. De Argo CD-rip-out is de makkelijkste beslissing als de referentiebak dertig dagen zonder incident heeft gedraaid.
Twee specifieke valkuilen in de parallelle periode. De eerste: koppel de nieuwe bak niet aan de productie-SMTP-relay tot de read-only-matches minstens een week stabiel zijn. Een verkeerd geclassificeerd antwoord gaat twee keer de deur uit, en iemand aan de klantkant ziet dat eerder dan jij. De tweede: schrijf de vergelijkings-harness voordat een van de stacks die dag echt werk doet. De output van de parallelle run is het verschil, niet de demo. Als je niet in dertig seconden "hoe waren de twee stacks het afgelopen dinsdag oneens" kunt beantwoorden, is de harness niet af, en vlieg je blind in een mist die je zelf hebt opgetrokken.
De verleiding is om de parallelle run over te slaan en "gewoon te migreren". Niet doen. Het hele punt van de referentiestack is dat 'ie €4,51 kost om als bewijsmateriaal te bewaren.
De rekening onderaan
Het Utrechtse logistiekbedrijf draaide de parallelle referentiebak tweeënveertig dagen. De triage-beslissingen kwamen op 99,4 procent van de mail overeen. De 0,6 procent die niet matchte, waren vooral gevallen waarin de oude stack stilletjes een header had laten vallen. In maart zijn ze omgeschakeld. De clusterrekening ging van €630 per maand naar €11. De on-call-belasting ging in zeventig dagen van één rotatie per week naar nul.
Toen wij die email-triage-AI-agent bouwden, was het ding waar we tegenaan liepen niet technisch — het was dat het engineering-team zich emotioneel aan het platform had verbonden voordat ze de workload hadden gemeten. We hebben het opgelost door twee dagen lang beide stacks parallel te draaien en de cijfers het pleit te laten beslechten.
Open een terminal, draai kubectl get pods -A, en tel de regels die bestaan om één binary van 200 rpm te bedienen. Dat getal is je over-engineering-belasting. De Caddyfile is twee schermen lang. Schrijf 'm vanavond.
Kern
Als de workload één binary is die 200 rpm doet, is de juiste hoeveelheid infrastructuur een VPS van €4, Caddy en een systemd-timer.
FAQ
Wanneer is Kubernetes wél logisch voor een interne agent?
Wanneer je tien of meer onafhankelijke workloads draait, een platformteam hebt om het te onderhouden, en geplande bin-packing over nodes nodig hebt. Niets daarvan geldt voor één agent van 200 rpm.
En high availability op één VPS?
Twee CX22's achter een kleine load balancer met health checks geeft je een zeer hoge uptime voor onder de €12 per maand. Echte geo-gedistribueerde HA is een ander gesprek en zelden gerechtvaardigd voor interne tools.
Hoe audit je een single-VPS-setup?
Wekelijkse snapshots, een log shipper naar een tweede machine, en het deploy-script in git. Auditeerbaarheid komt uit proces, niet uit het platform waar je toevallig op draait.
En als het verkeer vertienvoudigt?
2000 rpm zit nog altijd ruim binnen de speelruimte van één CX22. Als je 10k sustained rpm passeert, kijk je er opnieuw naar. Tot dat moment is verticaal schalen op één bak goedkoper dan horizontaal op drie.