Resource Limits en Performance Tuning voor Docker Compose in Productie
CPU en geheugen limieten instellen, logging drivers configureren en container performance monitoren in een Docker Compose productieomgeving.
Jean-Pierre Broeders
Freelance DevOps Engineer
Resource Limits en Performance Tuning voor Docker Compose in Productie
Een Docker Compose stack deployen zonder resource limits is als een auto zonder snelheidsmeter. Het werkt — tot het niet meer werkt. En dan is het meestal te laat.
Waarom resource limits niet optioneel zijn
Zonder expliciete limieten kan één container al het beschikbare geheugen opvreten. De OOM killer van Linux grijpt in, maar die maakt geen onderscheid tussen je database en je logging sidecar. Het resultaat: willekeurige containers die worden afgeschoten, vaak precies de verkeerde.
De deploy.resources sectie in Compose lost dit op:
services:
api:
image: myapp/api:latest
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
Hier zit een subtiel maar belangrijk verschil tussen limits en reservations. Limits zijn hard: de container mag er niet overheen. Reservations zijn zacht: Docker garandeert dat die resources beschikbaar zijn bij het starten. Op een server met 4GB RAM en vijf services wil je dat de som van alle reservations onder de 4GB blijft, terwijl de limits hoger mogen liggen voor piekmomenten.
CPU limits: shares vs quota
Bij CPU-limieten speelt er meer dan op het eerste gezicht lijkt. Docker kent twee mechanismen: CPU shares en CPU quota.
services:
worker:
image: myapp/worker:latest
deploy:
resources:
limits:
cpus: '0.5'
cpu_shares: 512
De cpus: '0.5' instelling beperkt de container tot maximaal 50% van één core. Altijd, ongeacht hoe druk de server het heeft. CPU shares werken anders — het is een relatief gewicht. Een container met 512 shares krijgt de helft van de CPU ten opzichte van een container met 1024 shares, maar alleen wanneer er daadwerkelijk contention is. Bij een rustige server maakt het niets uit.
Voor de meeste productie setups werkt een combinatie het best. Harde CPU limits op CPU-intensieve workers, shares voor services die af en toe een piek hebben maar niet structureel veel CPU gebruiken.
Geheugen: de stille killer
Geheugenproblemen zijn lastiger dan CPU-problemen. Een Node.js API die langzaam lekt, een Java service met een te grote heap, een Redis instance zonder maxmemory — het sluipt er geleidelijk in.
Een paar praktische instellingen die helpen:
services:
redis:
image: redis:7-alpine
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
deploy:
resources:
limits:
memory: 300M
api:
image: myapp/api:latest
environment:
- NODE_OPTIONS=--max-old-space-size=384
deploy:
resources:
limits:
memory: 512M
De truc zit erin om de applicatie-limiet iets lager te zetten dan de container-limiet. Redis krijgt 256MB als eigen limiet, maar de container mag 300MB gebruiken. Die marge is voor overhead van het Redis-proces zelf. Zonder die marge crasht de container voordat Redis netjes kan opruimen.
Logging: de vergeten resource vreter
Standaard stuurt Docker alle stdout/stderr naar JSON-bestanden op disk. Zonder configuratie groeien die oneindig door. Op een drukke API met debug logging enabled is het realistisch om gigabytes per dag te produceren.
services:
api:
image: myapp/api:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Drie bestanden van maximaal 10MB per container. Dat houdt de disk schoon zonder dat logs volledig verdwijnen. Voor serieuze monitoring is een externe logging driver beter:
services:
api:
logging:
driver: syslog
options:
syslog-address: "tcp://logserver:514"
tag: "api"
Of, als er een Loki of Elasticsearch stack draait, de juiste driver daarvoor. Het punt is: denk na over logging voordat de disk volloopt.
Health checks die ertoe doen
Een health check die alleen kijkt of de container draait, is nutteloos. De container kan prima draaien terwijl de applicatie erin volledig vasthangt.
services:
api:
image: myapp/api:latest
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Dat /health endpoint moet meer doen dan 200 OK teruggeven. Check de database connectie. Check of Redis bereikbaar is. Check of de event queue niet volloopt. Een health endpoint dat daadwerkelijk de gezondheid van de service reflecteert, maakt het verschil tussen een probleem dat automatisch hersteld wordt en een probleem dat om 3 uur 's nachts een telefoontje oplevert.
Performance monitoring zonder extra tools
Docker zelf biedt al meer inzicht dan de meeste teams benutten:
# Real-time resource gebruik per container
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
# Gedetailleerde inspect van resource gebruik
docker inspect --format='{{.HostConfig.Memory}}' container_naam
Voor iets meer structuur, een simpel script dat elke minuut de stats logt:
#!/bin/bash
while true; do
docker stats --no-stream --format \
"{{.Name}},{{.CPUPerc}},{{.MemPerc}},{{.MemUsage}}" \
>> /var/log/docker-stats.csv
sleep 60
done
Niet sexy, maar effectief. Een CSV die in Grafana of zelfs een spreadsheet te laden is, geeft na een paar dagen al een goed beeld van het werkelijke resource-gebruik. Die data is onmisbaar om limits goed af te stellen — te krappe limits veroorzaken OOM kills, te ruime limits verspillen resources.
De juiste restart policy
Nauw verwant aan resource management is de restart policy. Een container die OOM-killed wordt en met restart: always onmiddellijk weer opstart, kan in een crash loop terechtkomen die de hele server platlegt.
services:
api:
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
De delay en max_attempts voorkomen dat een falende container eindeloos opnieuw opstart. Na drie mislukte pogingen binnen twee minuten stopt Docker met herstarten. Dat geeft ruimte om het probleem te onderzoeken in plaats van het te maskeren.
Samenvatting
Resource limits instellen kost misschien een uur extra bij de eerste deployment. Dat uur verdien je dubbel en dwars terug bij de eerste keer dat een memory leak niet je hele server onderuit haalt. Begin met conservatieve limits, monitor het werkelijke gebruik, en stel bij op basis van data. Niet andersom.
