· · 11 min leestijd Advanced

Internal Developer Platform bouwen: waar begin je?

Bouw een Internal Developer Platform dat developers echt gebruiken. Begin klein, los echte problemen op, en groei organisch. Praktische gids met voorbeelden.

Een foodtruck met drie knoppen en een rij goblins naast een leeg glimmend platformpaleis

TL;DR Een Internal Developer Platform is een dunne laag tussen wat een developer wil en wat het cluster eist. Bij mij is dat een Helm base chart, ArgoCD en een handvol Kyverno policies, zo’n 500 regels eigen code. Begin met die drie, en voeg observability en een portal pas toe als de tickets erom vragen.

Moet je een Internal Developer Platform bouwen? Meestal wel. Het is de hóe waar teams op stuklopen.

Ik heb een platform gezien dat een klein fortuin kostte, live ging en daarna stof stond te happen omdat niemand het wilde. Ik heb ook een paar Helm charts en één Kyverno policy de manier zien veranderen waarop een heel team software uitrolt. Het budget maakte het verschil niet. De toolkeuze ook niet. Het ene team begon bij een probleem dat developers hadden. Het andere bouwde het platform waarvan het dacht dat developers het zouden moeten willen.

Dus geen referentie-architectuur om over te tikken. Ik bouw dit op vanaf het kleinste ding dat het deployen waard is. Stop met lezen waar je wilt, en je hebt nog steeds iets dat je kunt uitrollen.

Wat een IDP is

Een Internal Developer Platform is een self-service laag die het infrastructuur-loodgieterswerk weghoudt bij de mensen die applicaties bouwen. Developers beschrijven wat ze nodig hebben. Het platform zoekt uit hoe dat er komt.

Met Platform

Developer

Platform API

Kubernetes

CI/CD

Security

Monitoring

Zonder Platform

Developer

Kubernetes YAML

CI Pipeline

Security Config

Monitoring Setup

Het platform zit tussen wat een developer wil en wat het cluster eist. Alles in deze post gaat over die laag dun houden, en makkelijk te verlaten op de dag dat-ie in de weg zit.

Waarom zoveel platforms doodbloeden

Eerst de manieren waarop het misgaat, dan pas de bouwstappen. De meeste zijn voorspelbaar en je kunt eromheen ontwerpen.

De klassieker is bouwen in isolatie. Het platformteam sluit zich op in een kamer, levert wat het dénkt dat mensen nodig hebben, en is beledigd als niemand het gebruikt. De developers deden niet moeilijk. Ze kregen een tool voor problemen die ze niet hadden.

Vlak daarachter komt te veel te snel. Een servicecatalogus, een eigen UI, self-service voor elke denkbare resource, allemaal vooraf gepland. Zes maanden later is er een indrukwekkende demo en niks in productie.

Dan het verkeerde abstractieniveau. Te laag en het platform is Kubernetes met extra stappen, dus waarom de moeite. Te hoog en er is geen nooduitgang op de dag dat iemand een geval raakt dat de abstractie nooit zag aankomen. En die dag komt.

En dan de fout die meer platforms de kop heeft gekost dan welke technische misser ook. Het platform werkt alleen voor greenfield, bestaande services kunnen er niet op, en nu draai je voor altijd twee systemen naast elkaar.

Begin bij problemen, niet bij een oplossing

Dit is het stuk dat mensen overslaan. Voordat je één template schrijft, ga je uitzoeken waar de pijn zit.

Waar verliezen developers tijd? Wachten op infrastructuur-requests, dezelfde boilerplate-config kopiëren, deployments debuggen die stukgaan om redenen die niemand opschreef, achter productietoegang aan lopen. Waar vragen ze steeds om? Je ticketsysteem weet het. Je Slack-kanalen weten het. En, revolutionair idee, je kunt het ze gewoon vragen.

Kijk daarna waar de incidenten vandaan komen. Verkeerd geconfigureerde services, ontbrekende network policies, resource limits die niemand instelde. Die automatiseer je als eerste weg, want elk daarvan is een page die je niet meer krijgt.

De antwoorden zeggen wat je moet bouwen. Bouw je iets voordat je ze hebt, dan gok je.

Het simpelste nuttige platform

Dit is het minimum waar ik m’n tijd in zou steken. Drie onderdelen, allemaal uit tools die je al draait.

Golden paths

Opinionated templates met de juiste defaults ingebakken, 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 service met network policy uit de doos, en raakt alleen de values aan die z’n service anders maken. De veilige configuratie is ook de configuratie met het minste typewerk, en dáárom gebruiken mensen hem.

Self-service deployment

Code pushen, deployment krijgen. 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 staat. Je hebt geen portal nodig om dit self-service te mogen noemen. Git is er al een.

Guardrails

De keerzijde van self-service is dat mensen dingen deployen waar ze zelf last van krijgen. Kyverno policies vangen de voor de hand liggende fouten af 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 het cluster niet in. Niemand hoeft die regel te onthouden, de admission controller onthoudt hem wel.

Dat is een echt platform. Templates, GitOps, guardrails. Lever alleen dit op en developers deployen veilig zonder de binnenkant van Kubernetes te hoeven kennen, en de meest voorkomende tickets zijn weg. Hier kun je een jaar blijven staan en het is prima.

De bouwstenen

Zodra de basis blijft plakken, werk je de onderdelen uit. Niks hiervan is exotisch. Het is vooral 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 hem met een klein 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 krijgt elke service die fix bij de volgende deploy. Daar is die base chart voor.

De namespace als grens

Geef elk team een namespace waar de guardrails al in zitten:

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

Binnen de namespace beslist het team. De quota en de default-deny policy zijn van het platform. Een op hol geslagen workload bij payments kan het cluster niet opeten, en verkeer zit dicht tot iemand het bewust openzet.

Self-service via pull requests

Teams wijzigen hun eigen config zoals ze code wijzigen:

infrastructure/
├── teams/
│   ├── payments/
│   │   ├── namespace.yaml
│   │   ├── applications/
│   │   │   ├── api.yaml
│   │   │   └── worker.yaml
│   │   └── secrets/
│   │       └── external-secret.yaml
│   ├── orders/
│   └── ...

Een pull request triggert een review, de merge triggert een ArgoCD sync, en de wijziging staat live. Elke infrastructuurwijziging heeft een auteur, een reviewer en een diff. Dat is een audit trail, en niemand heeft er een hoeven bouwen.

Het platform laten groeien

Dit schroef je er niet in één keer allemaal aan. Elke fase is een plek waar je kunt stoppen en een tijdje kunt blijven.

Fase één is wat ik net heb laten zien. Helm charts voor de gangbare patronen, Kyverno voor de veiligheid, GitOps voor de deployment, en genoeg docs om je weg te vinden. Mensen deployen veilig zonder diepe Kubernetes-kennis.

Fase twee is observability die niemand met de hand hoeft aan te sluiten:

Een service die via het platform is uitgerold staat zonder extra config in de dashboards. Gaat-ie stuk, dan is de data er al.

Fase drie is het moment dat een UI z’n plek begint te verdienen, of dat nu Backstage is of iets wat je zelf bouwt:

Developer Portal

Service Catalogus

Maak van Template

Documentatie

Service Status

Git Repository

ArgoCD

Kubernetes

De portal vervangt geen van de eerdere lagen. Hij maakt Git commits die door dezelfde ArgoCD stromen die je al vertrouwt. Dezelfde machinerie, een vriendelijkere voordeur.

Fase vier is de diepere self-service waar de meeste teams uiteindelijk uitkomen. Secret management met Vault, database provisioning, environments klonen, inzicht in kosten. Tegen die tijd handelt het platform de lange staart af van verzoeken die vroeger in jouw queue belandden.

Wat je niet moet bouwen

De dingen die je weigert tellen net zo zwaar als de dingen die je bouwt. Vier die ik elke keer weiger.

Verzin geen eigen YAML-DSL. Iemand wil het altijd, en het resultaat is een configuratietaal die alleen jouw team snapt, met slechtere tooling dan Helm, Kustomize of cdk8s je gratis geven. Zeg nee.

Maken developers nog steeds tickets aan om iets gedaan te krijgen, dan is de automatisering niet af. Elk soort ticket is een workflow die je nog niet hebt gebouwd.

Geen one-size-fits-all. Golden paths zijn defaults. Het team dat weet wat het doet moet naar ruwe manifests kunnen zakken zonder toestemming te vragen.

En houd het platform expres saai. Een platform dat elke week nieuwe features oplevert heeft meestal z’n echte problemen nog niet gevonden. Ik heb liever een platform waar niemand het over heeft.

Weten of het werkt

Je wilt weten of dit alles iets heeft opgeleverd. Een paar signalen waar ik naar kijk.

Tijd naar productie eerst. Hoe lang duurt het van “idee voor een nieuwe service” tot “draait in prod”? Weken betekent dat het platform z’n werk niet doet. Dagen is prima.

Het aantal tickets moet dalen naarmate het platform het routinewerk opslokt:

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 aanraden aan een collega? Dat antwoord zegt meer dan welk dashboard ook. En check of services die via het platform zijn uitgerold minder incidenten hebben dan de handgebouwde. Werken de guardrails, dan hoort dat zo te zijn.

Het team erachter

Een platform is een product met gebruikers, dus het heeft mensen nodig die het beheren. Een ruwe vuistregel is één platform engineer per tien tot vijftien applicatiedevelopers. Te weinig en het platform staat stil. Te veel en ze gaan features verzinnen waar niemand om vroeg, en zo krijg je die demo na zes maanden.

De verdeling van verantwoordelijkheid telt. Het platformteam onderhoudt de templates en tools, schrijft de policies, pakt incidenten op platformniveau op, en helpt teams met de migratie. Applicatieteams zijn van begin tot eind verantwoordelijk voor hun eigen services. Ze deployen hun eigen code, bepalen hun eigen resources, en krijgen de eerste page als hun service stukgaat.

Begin gecentraliseerd, zodat het platform een duidelijke eigenaar heeft. Wordt het volwassen, zet dan platform engineers part-time bij de productteams. Naast de mensen zitten die met het platform moeten leven is hoe je erachter komt wat pijn doet.

Hoe dit er in de praktijk uitziet

Mijn homelab-platform draait op 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 een namespace-wijziging en die merget vanzelf na review. Guardrails zijn Kyverno die limits, labels en security context afdwingt, plus een default-deny NetworkPolicy. Observability zit in de template, met een ServiceMonitor die automatisch wordt aangemaakt en een Grafana dashboard dat ernaast wordt gegenereerd.

Eigen code, alles bij elkaar, is ergens rond de 500 regels Helm templates en Kyverno policies. De rest is configuratie van tools die al bestonden. Die verhouding telt voor mij zwaarder dan welke feature ook. Hoe minder maatwerk het platform meesleept, hoe minder ik ervan hoef te snappen als het stukgaat, en ik wil m’n eigen platform van boven tot onder kunnen lezen.

De fouten die ik het meest zie

Een paar patronen komen steeds terug.

Beginnen met de portal is de meest voorkomende. De UI is het leuke stuk, dus bouwen mensen die eerst, nog voordat de workflows eronder bewezen zijn. Krijg GitOps en de CLI werkend, en zet er dáárna een gezicht op.

Bestaande services negeren is de dure. Een platform dat alleen greenfield aankan laat je voor altijd twee werelden naast elkaar draaien. Migratie is een feature, en die hoort in de eerste versie te zitten.

Security te vroeg dichttimmeren wurgt de adoptie. Blokkeer op dag één elke edge case en mensen lopen met een boog om het platform heen. Begin ruim en trek aan op basis van wat incidenten je leren.

Alle complexiteit verstoppen klinkt fijn, tot een senior developer moet zien wat eronder zit en dat niet kan. Sommige mensen willen de machinerie zien, en dat instinct is gezond. Geef ze nooduitgangen en echte documentatie.

Waarom de moeite

Developertijd is het duurste wat een team heeft, en elk uur gevecht met infrastructuur is een uur niet aan het product. Met een platform dat werkt kan een junior op dag één veilig deployen, en de senior die vroeger z’n tickets beantwoordde krijgt die uren terug.

Mijn eigen platform begon als één base chart en één Kyverno policy. 500 regels later is het nog steeds vooral dat.