26 - Limiter les ressources de tes conteneurs
Ce que tu vas apprendre
- Comment limiter la mémoire et le CPU d'un conteneur
- Pourquoi le OOM killer tue tes conteneurs sans prevenir
- Comment surveiller la consommation avec
docker stats - Les options swap et ulimits
- Des guidelines concrets pour dimensionner en prod
Prerequisites
- Savoir lancer un conteneur Docker
- Connaitre Docker Compose pour les exemples en YAML
Un vendredi soir, mon conteneur Node.js a décidé de manger 8 Go de RAM sur un serveur qui en avait 8. Le kernel Linux a fait ce qu'il sait faire : il a tue le processus le plus gourmand. Mon API a disparu sans laisser de trace dans les logs. Pas d'erreur, pas de warning. Juste un conteneur marque "Exited (137)".
Ce jour-la j'ai appris qu'un conteneur sans limites de ressources, c'est une bombe a retardement.
Par défaut, aucune limite
Quand tu lances un conteneur Docker sans options, il peut utiliser toute la RAM et tous les CPU de la machine hote. Docker ne pose aucune restriction. Ton conteneur a acces a tout, exactement comme un processus natif.
Ca marche sur ton poste de dev. Ca explose en production quand trois conteneurs se battent pour les memes ressources.
Limiter la mémoire
La méthode directe avec docker run :
bashdocker run --memory=512m --memory-swap=1g mon-app
--memory=512m definit la limite de RAM. --memory-swap=1g definit la limite combinee RAM + swap. Si tu veux désactiver le swap, passe --memory-swap a la meme valeur que --memory.
En Compose, ca se configure dans deploy.resources :
yamlservices:
api:
image: mon-app:latest
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
limits est le plafond dur. reservations est ce que Docker garantit a ton conteneur. La reservation sert au scheduler pour décider ou placer les conteneurs. La limite sert au kernel pour tuer ceux qui depassent.
Le OOM killer, ton ennemi invisible
OOM signifie Out Of Memory. Quand un conteneur dépassé sa limite mémoire, le kernel Linux invoque le OOM killer. Il tue le processus principal du conteneur. Exit code 137.
Le problème : le OOM killer ne previent pas. Pas de log dans ton application, pas de signal propre. Le processus disparaît.
Pour vérifier si un conteneur a ete tue par le OOM killer :
bashdocker inspect mon-conteneur --format='{{.State.OOMKilled}}'
Si ca renvoie true, tu sais ce qui s'est passe.
Pour éviter ca, tu as deux options : augmenter la limite, ou corriger la fuite mémoire. Dans la vraie vie, c'est souvent un mix des deux.
Limiter le CPU
Docker permet de limiter le CPU de plusieurs facons :
bashdocker run --cpus=1.5 mon-app
Ca donne acces a 1.5 coeurs. Pas plus. Si ta machine a 4 coeurs, le conteneur en utilise au maximum 1.5.
bashdocker run --cpu-shares=512 mon-app
Les shares sont relatives. La valeur par défaut est 1024. Un conteneur a 512 shares recevra la moitie du CPU par rapport a un conteneur a 1024. Mais seulement quand il y a competition. Si la machine est tranquille, le conteneur peut utiliser plus.
En Compose :
yamlservices:
api:
image: mon-app:latest
deploy:
resources:
limits:
cpus: "1.5"
reservations:
cpus: "0.5"
Mon conseil : cpus pour une limite dure, cpu-shares pour une repartition equitable sans plafond strict.
Surveiller avec docker stats
bashdocker stats
Cette commande affiche en temps réel la consommation de chaque conteneur :
CONTAINER CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
api 2.34% 187MiB / 512MiB 36.52% 1.2kB/540B 0B/0B
db 0.89% 312MiB / 1GiB 30.47% 540B/1.2kB 8.2MB/4.1MB
Pour un snapshot sans flux continu :
bashdocker stats --no-stream
C'est la première chose que je regarde quand un serveur ralentit. Avant meme les logs. Sur paltemps.fr, j'ai un cron qui enregistre la sortie de docker stats toutes les 5 minutes dans un fichier. Rustique mais efficace.
Swap et ulimits
Par défaut, Docker autorise le swap. Un conteneur qui dépassé sa limite mémoire peut deborder sur le disque. C'est mieux qu'un crash, mais les performances s'effondrent.
Pour désactiver le swap :
bashdocker run --memory=512m --memory-swap=512m mon-app
Quand --memory et --memory-swap sont egaux, pas de swap.
Les ulimits controlent les limites système du conteneur :
yamlservices:
api:
image: mon-app:latest
ulimits:
nofile:
soft: 65536
hard: 65536
nproc:
soft: 4096
hard: 4096
nofile contrôle le nombre de fichiers ouverts. Essentiel pour les serveurs qui gerent beaucoup de connexions. nproc contrôle le nombre de processus. Une protection contre les fork bombs.
Guidelines de dimensionnement
Apres avoir déployé pas mal de conteneurs en production, voici mes regles :
- API Node.js/Express : 256-512 Mo de RAM, 0.5-1 CPU
- Application Java/Spring : 512 Mo-1 Go de RAM, 1-2 CPU
- PostgreSQL : 512 Mo-2 Go de RAM selon la taille de la base
- Redis : 128-256 Mo pour du cache, plus si persistence
- Nginx : 64-128 Mo de RAM, 0.25 CPU
Ces valeurs sont des points de depart. Demarre avec ca, surveille avec docker stats, ajuste. Ne surdimensionne pas par precaution. Un conteneur qui reserve 2 Go mais n'en utilise que 200 Mo, c'est du gaspillage qui empeche d'autres conteneurs de tourner.
La reservation (reservations) devrait correspondre a l'usage normal. La limite (limits) devrait correspondre a un pic raisonnable. Si ton app consomme habituellement 200 Mo et monte a 400 Mo en pic, mets une reservation a 256 Mo et une limite a 512 Mo.
Résumé
- Par défaut, un conteneur n'a aucune limite de ressources
--memoryet--cpusposent des limites dures- Le OOM killer tue sans prevenir les conteneurs qui depassent leur limite mémoire (exit code 137)
docker statsest ton outil de monitoring de base- Dimensionne en surveillant l'usage réel, pas en devinant
Precedent : 25 - Multi-platform | Suivant : 27 - Logs