Elk platform team stelt uiteindelijk dezelfde vraag: moeten we een Internal Developer Platform bouwen? Het eerlijke antwoord is meestal ja. Het deel dat teams sloopt is de hoe.
Ik heb platforms gezien die een klein fortuin kostten en daarna stilletjes verlaten werden omdat niemand ze wilde gebruiken. Ik heb ook een paar Helm charts en een Kyverno policy een heel team van manier zien veranderen waarop ze software shippen. Het gat tussen die twee uitkomsten heeft bijna niets met budget te maken, of met welke modieuze tool je koos. Het komt neer op de vraag of je begon met een echt probleem oplossen of met het platform bouwen dat jij dacht dat developers zouden moeten willen.
Dus in plaats van je een referentie-architectuur te geven om te kopiëren, wil ik dit opbouwen vanaf het kleinste nuttige ding. Je moet op elk punt kunnen stoppen met lezen en nog steeds iets hebben dat het deployen waard is.
Wat een IDP eigenlijk is
Een Internal Developer Platform is een self-service laag die het infrastructuur-loodgieterswerk verbergt voor de mensen die applicaties bouwen. Developers beschrijven wat ze nodig hebben. Het platform bedenkt hoe het echt wordt. Dat is de hele pitch.
flowchart TD
subgraph before["Zonder Platform"]
D1["Developer"] --> K8s["Kubernetes YAML"]
D1 --> CI["CI Pipeline"]
D1 --> Sec["Security Config"]
D1 --> Mon["Monitoring Setup"]
end
subgraph after["Met Platform"]
D2["Developer"] --> IDP["Platform API"]
IDP --> K8s2["Kubernetes"]
IDP --> CI2["CI/CD"]
IDP --> Sec2["Security"]
IDP --> Mon2["Monitoring"]
end
Het platform zit tussen wat een developer wil en wat de cluster vereist. Alles in deze post gaat over die laag dun, eerlijk en makkelijk te verlaten houden wanneer je dat nodig hebt.
Waarom zoveel platforms doodbloeden
Voordat we bij de bouwstappen komen is het de moeite waard de faalmodi te kennen, want de meeste zijn voorspelbaar en je kunt eromheen ontwerpen.
De klassieker is bouwen in isolatie. Het platform team sluit zich op in een kamer, shipt wat ze denken dat mensen nodig hebben, en raakt dan geïrriteerd als niemand het adopteert. De developers deden niet moeilijk. Ze kregen gewoon een tool toegeschoven die problemen oploste die ze niet hadden.
Vlak daarachter komt te veel te snel doen. Een service catalogus, een custom UI, self-service voor elke denkbare resource, allemaal vooraf gepland. Zes maanden later is er een indrukwekkende demo en niets in productie.
Dan is er het verkeerde abstractieniveau kiezen. Ga te laag en het platform is Kubernetes met extra stappen, dus waarom de moeite. Ga te hoog en er is geen escape hatch de dag dat iemand een geval raakt dat de abstractie nooit verwachtte, wat ze altijd doen.
En de stille moordenaar: geen migratiepad. Het platform werkt alleen voor greenfield, je bestaande services kunnen er niet op, en nu draai je twee systemen voor altijd. Die laatste heeft meer platform-pogingen gekelderd dan welke technische fout dan ook.
Begin met problemen, niet met een oplossing
Dit is het deel dat mensen overslaan, en het is het deel dat alles bepaalt. Voordat je één template schrijft, ga uitzoeken waar de pijn echt zit.
Waar verliezen developers tijd? Wachten op infrastructuur-requests, dezelfde boilerplate config copy-pasten, deployments debuggen die falen om redenen die niemand documenteerde, productie-toegang opjagen. Waar vragen ze steeds om? Je ticketing systeem weet het. Je Slack-kanalen weten het. En, revolutionair idee, je kunt het ze vragen.
Kijk daarna waar incidenten vandaan komen. Verkeerd geconfigureerde services, ontbrekende network policies, resource limits die niemand instelde. Dat zijn de dingen die je platform als eerste moet wegautomatiseren, want dat verdient zichzelf terug in minder 3-uur-’s-nachts-pages.
De antwoorden wijzen precies aan wat je moet bouwen. Bouw je iets voordat je ze hebt, dan ben je aan het gokken.
Het simpelste nuttige platform
Hier is het minimum dat het deployen waard is. Drie onderdelen, allemaal gebouwd uit tools die je al draait.
Golden paths
Opinionated templates die de juiste defaults inbakken zodat niemand ze hoeft te onthouden.
# service-template/
├── deployment.yaml # Standaard deployment patroon
├── service.yaml # Service met verstandige defaults
├── networkpolicy.yaml # Security by default
├── servicemonitor.yaml # Automatische monitoring
└── values.yaml # Customization punten
Een developer krijgt een werkende, gemonitorde, network-policied service uit de doos en raakt alleen de values aan die zijn service anders maken. De goede defaults zijn de weg van de minste weerstand, wat precies is waar je ze wilt hebben.
Self-service deployment
Push code, krijg een deployment. Geen ticket, geen wachten op iemand in een andere tijdzone.
Met GitOps:
# Application definitie in Git
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-service
namespace: argocd
spec:
source:
repoURL: https://gitlab.internal/my-service
path: deploy
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: my-service
syncPolicy:
automated:
prune: true
selfHeal: true
Merge naar main, ArgoCD pikt het op, de service ships. Je hebt geen mooie portal nodig om dit als self-service te laten voelen. Git is de interface.
Guardrails
De keerzijde van self-service is dat mensen dingen zullen deployen die ze pijn doen. Kyverno policies vangen de voor de hand liggende fouten voordat ze landen:
# Vereis resource limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-limits
spec:
validationFailureAction: Enforce
rules:
- name: require-limits
match:
resources:
kinds:
- Pod
validate:
message: "CPU en memory limits zijn vereist"
pattern:
spec:
containers:
- resources:
limits:
cpu: "?*"
memory: "?*"
Een pod zonder limits komt nooit in de cluster. Niemand hoeft de regel te onthouden omdat het platform hem niet laat vergeten.
Dat is een echt platform. Templates, GitOps, guardrails. Ship je alleen dit, dan kunnen developers veilig deployen zonder de diepe interne werking van Kubernetes te leren, en heb je de meest voorkomende tickets opgelost. Je zou hier een jaar kunnen stoppen en prima zitten.
Laag één: de bouwstenen
Zodra de basis blijft plakken, vul je de onderdelen verder in. Niets hiervan is exotisch, het is grotendeels configuratie van tools die je al hebt.
Service templates met Helm
Een base chart die teams uitbreiden in plaats van forken:
# base-service/Chart.yaml
apiVersion: v2
name: base-service
version: 1.0.0
description: Standaard service template
# base-service/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.name }}
spec:
replicas: {{ .Values.replicas | default 2 }}
selector:
matchLabels:
app: {{ .Values.name }}
template:
metadata:
labels:
app: {{ .Values.name }}
annotations:
prometheus.io/scrape: "true"
spec:
containers:
- name: {{ .Values.name }}
image: {{ .Values.image }}
ports:
- containerPort: {{ .Values.port | default 8080 }}
resources:
requests:
cpu: {{ .Values.resources.requests.cpu | default "100m" }}
memory: {{ .Values.resources.requests.memory | default "128Mi" }}
limits:
cpu: {{ .Values.resources.limits.cpu | default "500m" }}
memory: {{ .Values.resources.limits.memory | default "512Mi" }}
readinessProbe:
httpGet:
path: {{ .Values.healthPath | default "/health" }}
port: {{ .Values.port | default 8080 }}
Een team gebruikt het met een minuscuul values-bestand:
# my-service/values.yaml
name: my-service
image: registry.internal/my-service:v1.2.3
replicas: 3
port: 8080
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1
memory: 1Gi
Fix je een bug in de base chart, dan erft elke service de fix bij de volgende deploy. Dat is de hefboom waar je naar op zoek bent.
De namespace als grens
Geef elk team een namespace die binnenkomt met zijn guardrails al op hun plek:
apiVersion: v1
kind: Namespace
metadata:
name: team-payments
labels:
team: payments
environment: production
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: team-payments
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: team-payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Het team owned wat er in de namespace gebeurt. Het platform owned de muren eromheen. Een op hol geslagen workload in payments kan de cluster niet opeten, en een default-deny policy betekent dat verkeer dicht is tot iemand het bewust openzet.
Self-service via pull requests
Teams wijzigen hun eigen config op dezelfde manier waarop ze code wijzigen:
infrastructure/
├── teams/
│ ├── payments/
│ │ ├── namespace.yaml
│ │ ├── applications/
│ │ │ ├── api.yaml
│ │ │ └── worker.yaml
│ │ └── secrets/
│ │ └── external-secret.yaml
│ ├── orders/
│ └── ...
Een pull request triggert review, de merge triggert een ArgoCD sync, en de wijziging is live. Elke infrastructuur-wijziging heeft een auteur, een reviewer en een diff. Geen tickets, en een volledig audit trail dat je gratis kreeg.
Laag twee: het platform laten groeien
Je bout dit niet allemaal in één keer aan. Elke fase is een plek waar je comfortabel kunt stoppen en een tijdje kunt blijven hangen.
Fase één is wat we net bouwden: Helm charts voor de veelvoorkomende patronen, Kyverno policies voor veiligheid, GitOps voor deployment, en genoeg docs om je weg te vinden. Mensen deployen veilig zonder diepe Kubernetes-achtergrond.
Fase twee is observability die niemand met de hand hoeft te bedraden:
- Automatische Prometheus scraping
- Loki log aggregatie
- Tempo trace collectie
- Pre-built Grafana dashboards
Een service die via het platform wordt gedeployed verschijnt in dashboards zonder extra config. Als hij breekt, is de data er al.
Fase drie is wanneer een UI zijn plek begint te verdienen, of dat nu Backstage is of iets dat je zelf bouwt:
flowchart TD
Portal["Developer Portal"] --> Catalog["Service Catalogus"]
Portal --> Templates["Maak van Template"]
Portal --> Docs["Documentatie"]
Portal --> Status["Service Status"]
Templates --> Git["Git Repository"]
Git --> ArgoCD["ArgoCD"]
ArgoCD --> K8s["Kubernetes"]
Merk op dat de portal geen van de eerdere lagen vervangt. Hij genereert Git commits die door dezelfde ArgoCD stromen die je al vertrouwt. Het is een vriendelijkere voordeur op machinerie die al werkt.
Fase vier is de diepere self-service die de meeste behoeften uiteindelijk bereiken: secret management met Vault, database provisioning, environment cloning, kosten-visibility. Tegen die tijd handelt het platform de lange staart aan requests af die vroeger in je queue belandden.
Wat niet te bouwen
Net zo nuttig als weten wat je moet bouwen is weten wat je moet weigeren.
Verzin geen eigen YAML DSL. Iemand wil het altijd, en het resultaat is een configuratietaal die alleen jouw team begrijpt, met slechtere tooling dan Helm, Kustomize of cdk8s je al gratis geven. Weersta het.
Maken developers nog steeds tickets om dingen gedaan te krijgen, dan is de automatisering niet af. Een ticket is een teken van een workflow die je nog niet hebt gebouwd.
Weersta one-size-fits-all. Golden paths zijn defaults, geen wetten. Het team dat precies weet wat het doet moet kunnen afdalen naar ruwe manifests zonder toestemming te vragen.
En houd het platform bewust saai. Een platform dat constant nieuwe features shipt is meestal een platform dat de juiste problemen nog niet gevonden heeft. Stabiliteit is hier een feature.
Weten of het werkt
Je wilt weten of dit alles zich uitbetaalde. Een paar signalen zijn de moeite waard om in de gaten te houden.
Tijd naar productie is de grote. Hoe lang van “nieuw service-idee” naar “draait in prod”? Weken betekent dat het platform niet werkt. Dagen betekent dat je op de goede weg bent. Uren betekent dat je het hebt.
Ticket-volume moet dalen naarmate het platform het routinewerk absorbeert:
Voor: 50 infrastructuur tickets/week
Na: 10 infrastructuur tickets/week (alleen edge cases)
Stel developers af en toe een botte vraag: zou je dit platform aanbevelen aan een collega? Het antwoord vertelt je meer dan welk dashboard dan ook. En controleer of services die via het platform zijn gedeployed echt minder incidenten hebben dan de handgemaakte. Werken de guardrails, dan zou dat zo moeten zijn.
Het team erachter
Een platform is een product met gebruikers, wat betekent dat het mensen nodig heeft die het ownen. Een ruwe vuistregel is één platform engineer per tien tot vijftien applicatie-developers. Te weinig en het platform stopt met evolueren. Te veel en ze gaan features verzinnen waar niemand om vroeg, wat zijn eigen faalmodus is.
De verdeling van verantwoordelijkheid telt. Het platform team onderhoudt de templates en tools, schrijft de policies, handelt incidenten op platform-niveau af, en helpt teams migreren naar het ding. Applicatie-teams ownen hun services van begin tot eind: ze deployen hun eigen code, definiëren hun eigen resource-behoeften, en zijn first responders als hun service breekt.
Begin gecentraliseerd zodat het platform een duidelijke eigenaar heeft. Als het matuur wordt, embed platform engineers part-time in product teams. Er is geen betere manier om uit te vinden wat echt pijn doet dan naast de mensen zitten die ermee leven.
Hoe dit er in de praktijk uitziet
Mijn homelab platform draait op precies de onderdelen hierboven.
De golden path is een Helm dependency:
# Standaard service template
dependencies:
- base-service # Helm dependency
values:
name: my-app
image: registry/my-app:latest
Self-service is GitOps: push naar main en ArgoCD deployt, open een PR voor namespace-wijzigingen en die auto-merged na review. Guardrails zijn Kyverno die limits, labels en security context afdwingt, plus een default-deny NetworkPolicy. Observability komt gratis, met een ServiceMonitor die automatisch wordt aangemaakt en een Grafana dashboard dat ernaast wordt gegenereerd.
Totaal custom code: ergens rond de 500 regels Helm templates en Kyverno policies. Al het andere is configuratie van tools die al bestonden. Die verhouding is het hele punt. Hoe minder maatwerk-code je platform draagt, hoe minder ervan je hoeft te begrijpen om 3 uur ’s nachts als er iets breekt, en dat telt voor mij zwaar. Ik wil mijn eigen platform van boven tot onder kunnen lezen.
De fouten die ik het meest zie
Een paar patronen duiken keer op keer op, dus ga ze uit de weg.
Beginnen met de portal is de meest voorkomende. De UI is het leuke deel, dus bouwen mensen het eerst, voordat de workflows eronder zelfs maar bewezen zijn. Krijg GitOps en de CLI aan de praat, zet er daarna een gezicht op.
Bestaande services negeren is de dure. Een platform dat alleen greenfield aankan laat je voor altijd twee werelden parallel draaien. Migratie moet een first-class feature zijn, geen bijgedachte.
Security te vroeg over-engineeren wurgt de adoptie. Blokkeer elke edge case op dag één en mensen routen volledig om het platform heen. Begin permissive en verscherp op basis van wat incidenten je echt leren.
Alle complexiteit verbergen klinkt prettig tot een senior developer moet begrijpen wat eronder zit en dat niet kan. Sommige mensen willen de machinerie zien, en dat instinct is gezond. Geef ze escape hatches en echte documentatie.
Waarom de moeite
Developer-tijd is het duurste op het team, en elk uur besteed aan vechten met infrastructuur is een uur niet besteed aan het product bouwen. Een platform dat werkt verandert de vorm van het hele team: een junior kan veilig deployen op dag één, een senior mag zijn aandacht aan de echt moeilijke problemen besteden, en de operations-last verschuift van diezelfde saaie requests naar werk dat eigenlijk interessant is.
Het recept verandert niet als je opschaalt. Begin klein. Los een echt probleem op. Groei wanneer de feedback je dat vertelt. Het beste platform dat ik gebruikt heb registreerde nauwelijks als een ding, omdat deployen gewoon makkelijk voelde en niemand erover nadacht waarom.
