Retour au cours

infra / docker

Limites de ressources (CPU/mémoire, cgroups)

Leçon 151 exercice

Explication

Ce que vous allez apprendre

  • Comprendre le rôle des cgroups Linux dans la limitation de ressources d'un conteneur
  • Distinguer une limite dure (--memory) d'un seuil souple (--memory-reservation)
  • Comprendre que --cpus est un plafond absolu alors que --cpu-shares n'est qu'une priorité relative
  • Protéger un conteneur contre un fork bomb avec --pids-limit
  • Diagnostiquer un conteneur tué par l'OOM killer et comprendre pourquoi

Dans quel contexte ?

Sur un serveur qui héberge plusieurs conteneurs sans aucune limite de ressources, une fuite mémoire dans un seul service applicatif finit par consommer toute la RAM disponible, entraînant le ralentissement puis le plantage de tous les autres conteneurs de la machine — y compris ceux qui n'ont rien à voir avec le bug initial. En fixant --memory=512m sur chaque conteneur, un conteneur défaillant est tué proprement par l'OOM killer dès qu'il dépasse sa limite, sans jamais affecter ses voisins.

Le danger d'un conteneur sans limite

Par défaut, un conteneur peut consommer autant de CPU et de mémoire que la machine hôte lui permet. C'est un vrai risque : une fuite mémoire ou un pic de charge dans un seul conteneur peut asphyxier TOUS les autres conteneurs qui tournent sur la même machine, voire la machine elle-même. Fixer des limites explicites, ce n'est pas seulement une bonne pratique de production, c'est une protection mutuelle entre applications qui partagent une infrastructure.

OptionType de limiteEffet au dépassement
--memoryDureLe conteneur est tué (OOM killer)
--memory-reservationSoupleDocker essaie de rester sous ce seuil, sans tuer
--cpusDure (plafond absolu)Le conteneur est bridé à cette puissance
--cpu-sharesRelative (priorité)Ne joue qu'en cas de compétition CPU réelle

Piège fréquent

Confondre limite dure et limite souple est une source fréquente d'incidents mal compris : un conteneur "tué sans raison apparente" est très souvent un dépassement de --memory (limite dure), alors qu'un --memory-reservation dépassé ne tue jamais le conteneur immédiatement.

Le mécanisme sous-jacent : les cgroups

Ces limites ne sont pas une invention de Docker : elles reposent sur les cgroups (control groups), une fonctionnalité du noyau Linux qui permet de délimiter la consommation de ressources d'un groupe de processus. Docker ne fait qu'exposer cette fonctionnalité noyau à travers des options simples comme --memory ou --cpus.

Limite dure ou limite souple : bien distinguer les deux

--memory fixe un plafond strict : si le conteneur le dépasse, le noyau intervient brutalement et tue le processus (l'OOM killer, pour "Out Of Memory killer"). --memory-reservation, en revanche, fixe un seuil plus souple : Docker essaie de garder le conteneur sous ce seuil quand la machine est sous pression, sans le tuer immédiatement s'il le dépasse ponctuellement. Confondre les deux est une source fréquente d'incidents mal compris — un conteneur "tué sans raison apparente" est très souvent un dépassement de la limite dure.

CPU : une limite n'est pas une priorité

--cpus fixe un plafond absolu de puissance de calcul utilisable. --cpu-shares, au contraire, ne fixe aucune limite : il définit seulement une priorité RELATIVE, qui ne joue un rôle que lorsque plusieurs conteneurs sont réellement en compétition pour le même CPU au même moment. Un conteneur seul avec un cpu-shares faible peut donc parfaitement utiliser 100% du CPU disponible s'il n'y a personne d'autre pour se le disputer.

Un garde-fou supplémentaire

--pids-limit protège contre un scénario particulier mais dangereux : un programme (buggé ou malveillant) qui crée des processus en boucle jusqu'à épuiser toutes les ressources de la machine, un phénomène appelé "fork bomb".

Commandes & code

Limites de ressources (CPU/mémoire, cgroups)

text
# Docker s'appuie sur les cgroups (control groups) du noyau Linux pour limiter/mesurer
# la consommation CPU, mémoire, et I/O de chaque conteneur, de façon isolée du reste de l'hôte.
bash
# --- Mémoire ---
docker run -d --memory=512m mon-api:1.0                  # limite dure : 512 Mo max
docker run -d --memory=512m --memory-swap=512m mon-api:1.0  # égal à --memory = désactive le swap
docker run -d --memory=512m --memory-reservation=256m mon-api:1.0
# --memory-reservation = seuil "soft" : Docker essaie de garder le conteneur sous ce seuil
# sous contention, sans le tuer immédiatement (contrairement à --memory, qui est une limite dure)

# Un conteneur qui dépasse --memory est tué par l'OOM killer (Out Of Memory)
docker inspect --format '{{.State.OOMKilled}}' mon-api    # true = c'est ce qui s'est passé

# --- CPU ---
docker run -d --cpus=1.5 mon-api:1.0            # jusqu'à 1.5 coeur CPU équivalent
docker run -d --cpu-shares=512 mon-api:1.0        # priorité RELATIVE (défaut 1024), pas une limite dure
docker run -d --cpuset-cpus="0,1" mon-api:1.0       # épingle le conteneur sur les coeurs 0 et 1 uniquement

# --- I/O disque ---
docker run -d --device-read-bps /dev/sda:10mb mon-api:1.0     # limite la lecture disque
docker run -d --device-write-bps /dev/sda:10mb mon-api:1.0      # limite l'écriture disque
docker run -d --blkio-weight=500 mon-api:1.0                       # priorité I/O relative (10-1000)

# --- Nombre de processus (évite un fork bomb dans un conteneur compromis) ---
docker run -d --pids-limit=100 mon-api:1.0

# --- Vérifier les limites appliquées et la consommation réelle ---
docker inspect --format '{{.HostConfig.Memory}}' mon-api
docker stats mon-api --no-stream
cat /sys/fs/cgroup/memory/docker/<id_conteneur>/memory.limit_in_bytes   # lecture directe des cgroups (v1)
yaml
# Limites dans docker-compose.yml (format "deploy", respecté aussi hors Swarm depuis Compose v2)
services:
  api:
    image: mon-api:1.0
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 512M
        reservations:
          cpus: "0.5"
          memory: 256M
    pids_limit: 100
bash
# Diagnostiquer un OOM récurrent : chercher les traces noyau
dmesg | grep -i "killed process"
journalctl -k | grep -i oom

# Charger/tester un conteneur pour valider ses limites avant la mise en prod
docker run --rm --memory=256m polinux/stress \
  stress --vm 1 --vm-bytes 300M --timeout 10s      # doit être tué par l'OOM killer si la limite tient

Résumé

  • --memory est une limite dure (OOM kill au dépassement), --memory-reservation un seuil souple sous contention.
  • --cpus fixe un plafond absolu, --cpu-shares ne fait que prioriser relativement entre conteneurs en compétition.
  • --pids-limit protège contre un fork bomb ; toujours définir des limites explicites avant la mise en production.

Exercices pratiques

1 disponible
1

Mission : isoler les conteneurs les uns des autres après un incident de saturation mémoire

Objectif : Diagnostiquer un incident lié à l'absence de limites cgroups et choisir les bonnes options de limitation.

Contexte

Un serveur héberge plusieurs conteneurs sans aucune limite de ressources. Une fuite mémoire dans un seul service a fini par ralentir puis planter tous les autres conteneurs de la machine.

Résoudre l’exercice →