Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Encadrer ressources et redémarrage d'une stack

Par mis à jour le

Améliorer cette page

Logo Docker

Un service Compose sans limite peut avaler toute la mémoire de la machine, et un service qui plante reste arrêté tant que personne ne le relance. Cette leçon s'adresse à un débutant qui sait écrire une stack : vous y posez des limites de mémoire, de CPU et de processus, puis vous choisissez ce que Docker fait quand un conteneur s'arrête.

Vous allez surtout constater, sur des conteneurs qui s'arrêtent de quatre façons différentes, lesquels la politique de redémarrage relance et lesquels elle laisse arrêtés. Vous finissez par le délai de grâce, le temps que Docker laisse à un service pour s'arrêter proprement avant de le tuer.

  • Limiter la mémoire, le CPU et le nombre de processus d'un service Compose, et vérifier que Docker applique ces limites.
  • Reconnaître un conteneur tué faute de mémoire avec OOMKilled.
  • Choisir une politique de redémarrage avec restart.
  • Distinguer ce que la politique relance (un processus qui meurt de lui-même) de ce qu'elle ne relance pas sur le moment (docker stop, docker kill).
  • Redémarrer ou relancer un service à la main.
  • Raccourcir ou allonger le délai de grâce avec stop_grace_period.

Il vous faut un hôte Linux avec Docker Engine 29.9.2 et son plugin Compose 5.6.1, installés depuis le dépôt officiel comme le montre la leçon d'installation de Docker. Les images sont épinglées par leur empreinte. Les fichiers du noyau lus plus bas (/sys/fs/cgroup/...) sont ceux de cgroup v2, la version que Debian 13 utilise.

Vous savez écrire une stack avec la leçon Décrire une application avec Compose, et lire l'état de santé d'un service avec la leçon précédente, Démarrage, disponibilité et santé. Cette leçon-ci reprend docker compose ps et docker inspect sans les réexpliquer.

Deux leçons voisines détaillent ce que cette page ne fait que résumer. Les quatre politiques de redémarrage de docker run sont dans Lire un échec de démarrage Docker, et les signaux d'arrêt dans Cycle de vie et signaux. Si vous voulez comprendre comment le noyau applique les limites, Brider un conteneur l'explique, et Sous le capot détaille les cgroups ; vous n'en avez pas besoin ici.

Vous travaillez dans un dossier ~/budgets, que la leçon vous fait créer. Son nom devient celui du projet : les conteneurs s'appellent budgets-....

Comment limiter la mémoire, le CPU et les processus d'un service ?

Section intitulée « Comment limiter la mémoire, le CPU et les processus d'un service ? »

Avec trois clés au niveau du service : mem_limit pour la mémoire, cpus pour le CPU et pids_limit pour le nombre de processus. Une limite est un plafond dur : le conteneur ne peut pas dépasser la mémoire vive ou le CPU qu'elle fixe, quoi que fasse le programme. Pour la mémoire, le swap (mémoire d'échange sur disque) s'ajoute à ce plafond tant que memswap_limit ne le borne pas, comme le montre la section sur le dépassement de mémoire.

Créez le dossier et un premier compose.yaml avec un seul service, indexeur, qui ne fait qu'attendre.

Fenêtre de terminal
mkdir -p ~/budgets
cd ~/budgets
services:
indexeur:
image: alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
command: ["sleep", "infinity"]
mem_limit: 48m
cpus: 0.25
pids_limit: 20

mem_limit: 48m fixe la mémoire maximale à 48 Mio, cpus: 0.25 autorise le quart d'un cœur et pids_limit: 20 autorise vingt processus au plus. Ces valeurs sont petites pour que la démonstration les atteigne vite. Dans une vraie stack, vous les choisissez d'après la consommation relevée du service.

Avant de lancer, demandez à Compose la version résolue du fichier. C'est ce que Docker va réellement recevoir.

Fenêtre de terminal
docker compose config | grep -E 'mem_limit|cpus|pids_limit'
cpus: 0.25
mem_limit: "50331648"
pids_limit: 20

Regardez que les trois limites sont présentes, et que la mémoire est convertie en octets (48 Mio valent 50 331 648 octets).

Cette commande sert aussi de garde-fou. Une limite écrite au mauvais niveau d'indentation n'est pas ignorée en silence : avec Compose 5.6.1, docker compose config s'arrête avec le code de sortie 1 et un message qui nomme la clé fautive. Par exemple, mem_limit glissée sous deploy donne le message suivant, précédé du mot validating et du chemin du fichier.

services.indexeur.deploy additional properties 'mem_limit' not allowed

Lisez le chemin avant le mot additional : il désigne l'endroit où la clé a été rangée à tort, ici services.indexeur.deploy. Une clé posée au niveau de services donne services.mem_limit must be a mapping, et une clé qui déborde de sa ligne donne une erreur de syntaxe YAML (did not find expected key). Corrigez l'indentation avant d'aller plus loin.

Oui : la section deploy.resources.limits. Elle vient de la spécification Compose, que d'autres orchestrateurs lisent aussi, et vous la croiserez dans des fichiers écrits pour eux. Avec Compose 5.6.1 sur Docker installé sur votre machine, mem_limit au niveau du service et deploy.resources.limits.memory sont tous deux appliqués. Les deux écritures suivantes donnent donc la même limite de mémoire.

# Forme courte, au niveau du service
mem_limit: 48m
# Forme de la spécification, sous deploy
deploy:
resources:
limits:
memory: 48M

La même section accepte aussi cpus: "0.25" et pids: 20 sous limits. Un service écrit ainsi, lancé avec Compose 5.6.1, a donné les mêmes valeurs que la forme courte dans docker inspect (mémoire=50331648 cpu=250000000 processus=20) et dans les fichiers du noyau (50331648, 25000 100000 et 20), que vous apprenez à lire juste après. Une limite mal placée sous deploy est refusée avec un message, comme pour la forme courte.

Choisissez l'une ou l'autre, pas les deux, et gardez la même dans tout le fichier. Cette leçon utilise la forme courte, plus facile à lire. Une clé deploy.resources.reservations réserve un minimum, et un poids relatif comme cpu_shares répartit le CPU quand la machine est chargée : ni l'un ni l'autre ne plafonne le conteneur.

Comment vérifier que Docker applique les limites ?

Section intitulée « Comment vérifier que Docker applique les limites ? »

En lisant deux endroits : la configuration que Docker a enregistrée, puis ce que le noyau applique réellement. Le noyau Linux applique les limites avec ses cgroups (groupes de contrôle) et les expose dans le conteneur sous forme de fichiers, dans /sys/fs/cgroup/. Lancez le service et comparez.

Fenêtre de terminal
docker compose up -d indexeur
docker inspect -f 'mémoire={{.HostConfig.Memory}} cpu={{.HostConfig.NanoCpus}} processus={{.HostConfig.PidsLimit}}' budgets-indexeur-1
docker compose exec indexeur sh -c 'cat /sys/fs/cgroup/memory.max /sys/fs/cgroup/cpu.max /sys/fs/cgroup/pids.max'
mémoire=50331648 cpu=250000000 processus=20
50331648
25000 100000
20

Regardez la première ligne : c'est ce que Docker a retenu (NanoCpus compte en milliardièmes de cœur, donc 250 000 000 pour un quart de cœur). Regardez ensuite les trois suivantes, lues dans le conteneur : memory.max donne la mémoire, pids.max les processus, et cpu.max contient deux nombres, le temps de calcul autorisé (25 000 microsecondes) sur chaque période de 100 000. Les deux lectures doivent dire la même chose. Si seule la première est juste, Docker n'a pas appliqué la limite.

Une limite de CPU ne se constate qu'en chargeant le conteneur. Le fichier cpu.stat compte les fois où le noyau a retenu le conteneur parce qu'il dépassait son quota, dans nr_throttled. Lisez ce compteur, faites tourner une boucle active pendant trois secondes, puis relisez-le.

Fenêtre de terminal
docker compose exec indexeur sh -c 'grep nr_throttled /sys/fs/cgroup/cpu.stat; timeout 3 sh -c "while :; do :; done"; grep nr_throttled /sys/fs/cgroup/cpu.stat'
nr_throttled 0
Terminated
nr_throttled 31

Regardez la différence entre les deux compteurs : elle prouve que le quota freine bien le conteneur. Le nombre exact change à chaque essai (0 puis 31 ici, 1 puis 31 à l'essai suivant), c'est la hausse qui compte. Le mot Terminated vient de timeout, qui a arrêté la boucle comme prévu.

Que se passe-t-il quand un service atteint sa limite de processus ?

Section intitulée « Que se passe-t-il quand un service atteint sa limite de processus ? »

Le noyau refuse de créer le processus suivant : le programme qui voulait le lancer reçoit une erreur, et rien n'est tué. Pour le voir, demandez au conteneur de lancer trente petits processus d'affilée alors qu'il n'en admet que vingt.

Fenêtre de terminal
docker compose exec indexeur sh -c 'for i in $(seq 1 30); do sleep 5 & done; wait'
sh: can't fork: Resource temporarily unavailable

Le message vient du shell du conteneur, qui n'a pas pu créer un nouveau processus et a abandonné sa boucle. Le piège vient ensuite : le conteneur reste inutilisable. Sept secondes après l'essai, docker compose exec indexeur echo ok échouait encore, avec OCI runtime exec failed ... unable to spawn stage-1: Resource temporarily unavailable. Vous ne pouvez plus ouvrir de shell dedans tant qu'il n'est pas recréé, ce que vous ferez dans un instant.

Retenez-en ceci : une limite de processus trop basse ne se contente pas de refuser un lancement, elle peut aussi vous empêcher de vous connecter au conteneur pour comprendre ce qui s'est passé.

Qu'advient-il d'une limite posée avec docker update ?

Section intitulée « Qu'advient-il d'une limite posée avec docker update ? »

Avant de recréer le conteneur, posez une limite à la main avec docker update, puis lisez-la. Les deux options --memory et --memory-swap reçoivent ici la même valeur.

Fenêtre de terminal
docker update --memory 96m --memory-swap 96m budgets-indexeur-1
docker inspect -f 'mémoire={{.HostConfig.Memory}} cpu={{.HostConfig.NanoCpus}} processus={{.HostConfig.PidsLimit}}' budgets-indexeur-1
budgets-indexeur-1
mémoire=100663296 cpu=250000000 processus=20

Docker a bien pris la nouvelle valeur (96 Mio valent 100 663 296 octets). Le conteneur ne répond plus à exec depuis l'essai précédent, donc memory.max ne se relit pas ici ; sur un conteneur sain, il a rendu 100663296. Recréez maintenant le conteneur et lisez de nouveau les limites.

Fenêtre de terminal
docker compose up -d --force-recreate indexeur
docker inspect -f 'mémoire={{.HostConfig.Memory}} cpu={{.HostConfig.NanoCpus}} processus={{.HostConfig.PidsLimit}}' budgets-indexeur-1
mémoire=50331648 cpu=250000000 processus=20

Les limites écrites dans compose.yaml sont revenues, parce que Compose les applique à chaque création : les 48 Mio ont remplacé les 96 Mio posés à la main. Cette recréation prend une dizaine de secondes (10,4 s mesurées) : sleep infinity ignore la demande d'arrêt (il est le processus 1 du conteneur, sans gestionnaire de signal, comme l'explique la section sur kill -9 1), et Docker attend la fin du délai de grâce, expliqué dans la section « Pourquoi un arrêt peut-il durer plus longtemps que prévu ? », avant de le tuer.

Une limite posée avec docker update ne survit pas à une recréation, et le fichier ne la contient pas. Si elle doit durer, écrivez-la dans compose.yaml.

Que se passe-t-il quand un service dépasse sa limite de mémoire ?

Section intitulée « Que se passe-t-il quand un service dépasse sa limite de mémoire ? »

Le noyau tue le processus qui consomme trop, et Docker note la cause dans l'état du conteneur : OOMKilled vaut alors true. OOM veut dire out of memory, plus de mémoire. Seule la limite de mémoire fait tuer le service par le noyau : un dépassement de CPU le ralentit seulement, et un dépassement de processus fait échouer la création du suivant.

Ajoutez un service qui consomme de la mémoire sans jamais s'arrêter : la commande tail /dev/zero lit un flux sans fin de zéros, sans saut de ligne, et garde tout en mémoire. Le service a une limite de 32 Mio, et memswap_limit égal à mem_limit interdit d'utiliser le swap, la mémoire d'échange sur disque, pour contourner la limite. Sa politique de redémarrage on-failure:2 est expliquée plus bas.

gourmand:
image: alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
command: ["tail", "/dev/zero"]
mem_limit: 32m
memswap_limit: 32m
restart: "on-failure:2"

Lancez-le, laissez-lui quelques secondes, puis lisez son état.

Fenêtre de terminal
docker compose up -d gourmand
sleep 8
docker inspect -f 'statut={{.State.Status}} code={{.State.ExitCode}} oom={{.State.OOMKilled}} relances={{.RestartCount}}' budgets-gourmand-1
statut=exited code=137 oom=true relances=2

Regardez oom=true : le conteneur est mort faute de mémoire, pas parce qu'on l'a arrêté. Le code 137 seul ne suffirait pas à le dire, car c'est aussi celui d'un conteneur tué par docker kill (le programme est alors tué par le signal 9, et 128 + 9 = 137). OOMKilled=true le signale. Notez aussi relances=2 : Docker a relancé le service deux fois, puis a abandonné. Le manque de mémoire est donc une panne comme une autre pour la politique de redémarrage, comme le montre la section suivante.

Comment choisir la politique de redémarrage d'un service ?

Section intitulée « Comment choisir la politique de redémarrage d'un service ? »

Avec la clé restart du service. Elle prend les mêmes quatre valeurs que l'option --restart de docker run, détaillées dans la leçon de dépannage citée plus haut.

ValeurDocker relance le conteneur…
"no" (défaut)jamais
on-failure[:n]quand le programme sort avec un code non nul, n fois au plus ; au redémarrage de Docker, seulement si la dernière sortie était en erreur (137 compris), jamais après une sortie en 0
alwaysà chaque sortie, y compris le code 0 ; un conteneur que vous aviez arrêté repart quand Docker redémarre
unless-stoppedcomme always, mais un conteneur que vous avez arrêté vous-même reste arrêté, même quand Docker redémarre

Pour un service qui doit tourner en permanence, unless-stopped convient bien : il remet le service en route après une panne ou un redémarrage de la machine, sans le relancer si vous l'avez arrêté exprès. Les démonstrations qui suivent l'utilisent.

Écrivez "no" entre guillemets. Avec Compose 5.6.1, restart: no sans guillemets est accepté sans aucun message : docker compose config l'affiche en restart: 'no' et docker inspect rend la politique no. Les guillemets restent la bonne habitude, car en YAML 1.1 le mot no désigne le booléen faux, et d'autres outils le liraient ainsi.

Qu'est-ce qu'une politique relance, et qu'est-ce qu'elle laisse arrêté ?

Section intitulée « Qu'est-ce qu'une politique relance, et qu'est-ce qu'elle laisse arrêté ? »

Une politique relance un processus qui meurt de lui-même, et elle ne touche pas à un conteneur que vous arrêtez ou tuez par une commande. Pour le voir, il faut donc deux services : un qui plante tout seul, et un qui vit jusqu'à ce que vous le tuiez. Ajoutez-les au fichier, tous deux sous unless-stopped.

fragile:
image: alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
command: ["sh", "-c", "echo départ; sleep 4; exit 1"]
restart: unless-stopped
veilleur:
image: alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
command: ["sleep", "infinity"]
restart: unless-stopped
stop_grace_period: 3s

Le service fragile affiche un message, attend quatre secondes, puis sort avec le code 1. Le service veilleur attend indéfiniment ; sa ligne stop_grace_period: 3s ne sert pas encore, la section « Pourquoi un arrêt peut-il durer plus longtemps que prévu ? » l'explique. Lancez les deux, attendez une quinzaine de secondes et lisez leurs compteurs.

Fenêtre de terminal
docker compose up -d fragile veilleur
sleep 15
docker inspect -f '{{.Name}} statut={{.State.Status}} relances={{.RestartCount}}' budgets-fragile-1 budgets-veilleur-1
/budgets-fragile-1 statut=running relances=3
/budgets-veilleur-1 statut=running relances=0

Regardez relances pour fragile : il vaut 3 ici, et 2 ou plus en général, parce que chaque sortie avec le code 1 a été suivie d'un redémarrage. Le nombre exact dépend du moment de la lecture. Le statut peut aussi afficher restarting si vous lisez entre deux essais.

Tuez maintenant veilleur avec docker kill, qui envoie SIGKILL aussitôt, puis relisez son état. La commande docker compose ps -q veilleur donne l'identifiant du conteneur du service, que docker kill attend.

Fenêtre de terminal
docker kill $(docker compose ps -q veilleur)
sleep 3
docker inspect -f 'statut={{.State.Status}} code={{.State.ExitCode}} relances={{.RestartCount}}' budgets-veilleur-1
b0615a52e79a1e37cc2c7c694c31e85d5fd6c29ccbfcfeab2d6fd14d748986fc
statut=exited code=137 relances=0

La première ligne est l'identifiant que docker kill affiche, il change à chaque essai. Le conteneur est resté arrêté (exited), avec le code 137, et son compteur n'a pas bougé : docker kill ne déclenche pas la politique de redémarrage sur le moment, même unless-stopped. Docker considère que vous avez voulu l'arrêter. Le tableau compare les quatre façons d'arrêter un conteneur de cette leçon. La première ligne a été mesurée sous unless-stopped, la deuxième sous on-failure:2, et les deux dernières sous unless-stopped.

Ce qui arrête le conteneurRelancé par la politique ?
Le programme sort de lui-même avec une erreur (fragile)oui
Le noyau le tue faute de mémoire (gourmand)oui, dans la limite fixée par on-failure:n
docker stop ou docker compose stopnon, sur le moment
docker killnon, sur le moment

Ce « non » vaut sur le moment. Au redémarrage de Docker, always relance un conteneur arrêté à la main, unless-stopped le laisse arrêté, et on-failure relance un conteneur dont la dernière sortie était en erreur, donc aussi un conteneur tué (code 137), mais pas un conteneur sorti en 0. Le détail est dans la leçon Lire un échec de démarrage.

Retenez la conséquence : tuer un conteneur avec docker kill ne simule pas une panne. Pour tester une politique de redémarrage, il faut un programme qui plante de lui-même, comme fragile.

Peut-on simuler une panne en tuant le processus principal depuis l'intérieur ?

Section intitulée « Peut-on simuler une panne en tuant le processus principal depuis l'intérieur ? »

Pas avec kill -9 1. Le processus principal du conteneur porte le numéro 1 dans son propre espace de processus, et le noyau le protège des signaux envoyés depuis cet espace : la commande ne fait rien. Essayez sur veilleur, après l'avoir relancé.

Fenêtre de terminal
docker compose start veilleur
docker compose exec veilleur kill -9 1
docker inspect -f 'statut={{.State.Status}} relances={{.RestartCount}}' budgets-veilleur-1
statut=running relances=0

Le conteneur tourne toujours et le compteur n'a pas bougé : le signal a été ignoré. Ce traitement particulier du processus 1 est le même qui lui fait ignorer SIGTERM, décrit dans Cycle de vie et signaux. Retenez que ce geste, souvent proposé pour « simuler un crash », ne prouve rien.

Avec docker compose restart <service> pour un service qui tourne, et docker compose start <service> pour un service arrêté. La commande restart arrête le conteneur puis le redémarre, en respectant le délai de grâce expliqué dans la section « Pourquoi un arrêt peut-il durer plus longtemps que prévu ? ». Elle ne relit pas le fichier : un changement de limite ou de politique n'est pris en compte que par docker compose up -d, qui recrée le conteneur.

Comment constater que restart ne relit pas le fichier ?

Section intitulée « Comment constater que restart ne relit pas le fichier ? »

Prenez un réglage facile à lire, le délai de grâce de veilleur. Changez-le dans le fichier (de 3s à 5s), puis lisez le délai que le conteneur a retenu après un restart, puis après un up -d. Remettez ensuite 3s. Les lignes de progression de Compose ne sont pas reproduites.

Fenêtre de terminal
sed -i 's/stop_grace_period: 3s/stop_grace_period: 5s/' compose.yaml
docker compose restart veilleur
docker inspect -f 'délai={{.Config.StopTimeout}}' budgets-veilleur-1
docker compose up -d veilleur
docker inspect -f 'délai={{.Config.StopTimeout}}' budgets-veilleur-1
sed -i 's/stop_grace_period: 5s/stop_grace_period: 3s/' compose.yaml
docker compose up -d veilleur
délai=3
délai=5

Après restart, le conteneur garde son ancien délai de 3 secondes ; seul up -d le recrée avec les 5 secondes du fichier. La dernière commande remet le délai à 3 secondes, valeur dont la section « Pourquoi un arrêt peut-il durer plus longtemps que prévu ? » a besoin.

Pourquoi docker compose ps -q ne rend-il rien après un arrêt ?

Section intitulée « Pourquoi docker compose ps -q ne rend-il rien après un arrêt ? »

Les scripts retrouvent l'identifiant d'un conteneur avec docker compose ps -q <service>. Ce que cette commande rend dépend de l'état du service : comparez-la avec et sans l'option -a, avant et après un arrêt.

Fenêtre de terminal
docker compose restart veilleur
docker compose ps -q veilleur
docker compose stop veilleur
docker compose ps -q veilleur
docker compose ps -aq veilleur
Container budgets-veilleur-1 Restarting
Container budgets-veilleur-1 Started
537690deee963829b589a652f700437e7837fcb860dad631a78d988bc357272f
[+] stop 1/1
✔ Container budgets-veilleur-1 Stopped 3.1s
537690deee963829b589a652f700437e7837fcb860dad631a78d988bc357272f

Regardez les identifiants. Le premier ps -q, lancé alors que le service tourne, rend l'identifiant du conteneur. Le second, lancé après stop, ne rend rien : l'option -q ne liste que les conteneurs en cours d'exécution, et l'absence de ligne n'est pas un oubli. L'option -aq ajoute les conteneurs arrêtés et retrouve l'identifiant. Si la sortie de votre script devient soudain vide, vérifiez d'abord que le service n'est pas simplement arrêté.

Pourquoi un arrêt peut-il durer plus longtemps que prévu ?

Section intitulée « Pourquoi un arrêt peut-il durer plus longtemps que prévu ? »

Parce que Docker laisse un délai de grâce au programme avant de le tuer. À l'arrêt, il envoie d'abord un signal de fin (SIGTERM), un message qui demande au programme de s'arrêter, puis il attend. Si le conteneur tourne encore à l'issue du délai, il envoie SIGKILL, un signal que le programme ne peut pas refuser. Le délai par défaut est de dix secondes. Le mécanisme complet, avec les cas où le programme ignore SIGTERM, est dans la leçon Cycle de vie et signaux.

Dans Compose, la clé stop_grace_period règle ce délai pour un service. Le service veilleur ignore SIGTERM : son processus principal, sleep, est le processus 1 du conteneur et n'a aucun gestionnaire pour ce signal. Son délai est réglé à 3 secondes. Mesurez la durée d'un arrêt.

Fenêtre de terminal
docker compose start veilleur
time docker compose stop veilleur
docker inspect -f 'code={{.State.ExitCode}}' budgets-veilleur-1
[+] stop 1/1
✔ Container budgets-veilleur-1 Stopped 3.2s
real 0m3.237s
code=137

Lancez start d'abord : sur un conteneur déjà arrêté, stop rend la main aussitôt (0,06 s mesurée) et ne démontre rien. Regardez la durée : trois secondes et quelques, c'est le délai réglé, et non les dix du défaut. Le code 137 confirme que Docker a dû envoyer SIGKILL, faute de réponse à SIGTERM. Un délai trop court expose un service qui a besoin de vider ses données, comme une base, à être tué en plein travail. Un délai trop long retarde chaque arrêt et chaque redéploiement.

Que faire quand ces réglages ne se comportent pas comme prévu ?

Section intitulée « Que faire quand ces réglages ne se comportent pas comme prévu ? »

Partez de ce que vous voyez, puis vérifiez la configuration que Docker a vraiment reçue avant de modifier le fichier.

Ce que vous observezCause probableCe que vous faites
docker compose config ou up s'arrête sur additional properties '...' not allowed ou must be a mappingLa clé est au mauvais niveau d'indentationLisez le chemin du message, replacez la clé sous le service (ou sous deploy.resources.limits), puis relancez docker compose config
docker compose exec répond unable to spawn stage-1: Resource temporarily unavailableLa limite de processus est atteinte, des zombies restent comptésLancez docker compose up -d --force-recreate <service>, et relevez la limite si elle est trop basse
Le conteneur sort avec le code 137Tué par SIGKILL : manque de mémoire, docker kill ou délai de grâce dépasséLisez .State.OOMKilled : true signale un OOM ; false ne l'exclut pas (un 137 sans OOMKilled a été mesuré une fois sur cinq sur Docker 29.9.2). Sans OOMKilled ni événement oom dans docker events, on ne peut pas conclure ; une consommation proche du plafond relevée avant l'arrêt rend l'OOM plausible, comme le montre la leçon sur les plafonds mémoire et de tâches
Le conteneur consomme plus que mem_limit sans être tué, et OOMKilled reste à falseSans memswap_limit, il peut utiliser du swap en plus de sa mémoire, si l'hôte en aAjoutez memswap_limit avec la même valeur que mem_limit
Un service tué avec docker kill ne redémarre pasComportement normal : une commande d'arrêt ne relance rien sur le momentFaites planter le programme lui-même pour tester la politique
kill -9 1 dans le conteneur n'a aucun effetLe noyau protège le processus numéro 1Utilisez un programme qui sort de lui-même ; docker kill depuis l'hôte tue le conteneur mais ne déclenche pas la politique sur le moment
docker compose ps -q ne rend rienLe service est arrêté, -q ne liste que les conteneurs qui tournentAjoutez -a : docker compose ps -aq <service>
Un changement de restart ou de limite n'a pas d'effet après docker compose restartrestart ne relit pas le fichierLancez docker compose up -d
L'arrêt dure dix secondes, puis code 137Le programme ignore SIGTERM, le délai par défaut s'écouleCorrigez la gestion du signal, ou réglez stop_grace_period

La démonstration laisse quatre conteneurs, un réseau, une image et le dossier ~/budgets. La commande docker compose down supprime les conteneurs et le réseau, y compris ceux qui sont arrêtés.

Fenêtre de terminal
cd ~/budgets
docker compose down
docker rmi alpine:3.24@sha256:be2656061b536c1883c4034790e21d0a09c5aaaf51968b89368a3c0371ea2c9a
cd ~ && rm -r budgets

Après cela, docker ps -a ne montre plus de conteneur budgets-..., et ls ~/budgets répond que le dossier n'existe plus.

Le lab vous donne un hôte avec Docker et l'image Alpine épinglée, sans aucune charge. Vous écrivez le compose.yaml d'un seul service worker qui attend, avec des limites de mémoire, de CPU et de processus dont l'énoncé fixe les valeurs. Le service ne doit avoir ni montage, ni capacité en plus, ni privilège, et le nom du projet est imposé avec l'option -p. Le CPU doit être un quota dur, pas un poids relatif.

La validation compare ce que Docker a enregistré (HostConfig) avec ce que le noyau applique dans le conteneur (memory.max, cpu.max, pids.max). Elle lance ensuite une boucle active de trois secondes et exige que le compteur de bridage du CPU augmente, puis elle recrée le service de force et relit les trois limites. Elle ne vérifie ni le manque de mémoire ni une panne : ces deux cas restent des démonstrations de la leçon.

  1. Les clés mem_limit, cpus et pids_limit posent des plafonds durs sur un service. docker compose config montre ce que Docker va recevoir, et refuse avec un message une limite écrite au mauvais niveau.

  2. Une limite se vérifie à deux endroits : docker inspect (ce que Docker a enregistré) et les fichiers memory.max, cpu.max et pids.max dans le conteneur (ce que le noyau applique). La limite de CPU se prouve par la hausse de nr_throttled dans cpu.stat.

  3. Seule la limite de mémoire fait tuer le service par le noyau. OOMKilled à true signale un OOM ; à false, il n'est pas exclu.

  4. Une limite de processus trop basse ne tue rien, mais elle peut rendre le conteneur inutilisable (docker compose exec échoue) jusqu'à sa recréation.

  5. Les limites écrites dans compose.yaml survivent à une recréation. Une limite posée avec docker update n'y survit pas.

  6. La clé restart relance un programme qui meurt de lui-même. docker stop et docker kill ne la déclenchent pas sur le moment (au redémarrage de Docker, le résultat dépend de la politique), et kill -9 1 depuis l'intérieur ne fait rien : seul un programme qui plante tout seul démontre une politique.

  7. docker compose ps -q ne liste que les services qui tournent, -aq inclut les arrêtés. docker compose restart ne relit pas le fichier, docker compose up -d oui.

  8. À l'arrêt, Docker envoie SIGTERM puis, après stop_grace_period (dix secondes par défaut), SIGKILL. Réglez ce délai selon le temps dont le service a besoin pour s'arrêter proprement.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

8 questions
8 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn