## Introduction

Les limites de ressources Docker sont un contrôle opérationnel essentiel pour exécuter des conteneurs de manière fiable et économique. Lorsque vous définissez une limite de mémoire de 512 Mo, Docker l'applique via les cgroups du noyau Linux, mais le comportement de cette application dépend du type de ressource, du runtime de conteneur et de la configuration de l'hôte. Cet article explique l'architecture derrière les limites de ressources Docker et vous fournit des commandes pratiques pour les observer, les configurer et les vérifier.

Nous aborderons :

- Comment Docker interagit avec les cgroups v1 et v2 pour appliquer les limites.
- Les limites CPU, y compris les parts, les quotas et les ensembles de CPU.
- Les limites de mémoire, y compris le comportement OOM et le swap.
- Les limites d'I/O disque et de PID.
- Comment inspecter les limites actuelles et les configurer via la CLI Docker et Compose.
- Les modes de défaillance, les étapes de récupération et une liste de contrôle opérationnelle.

Tout au long, nous utilisons des commandes concrètes et des sorties attendues pour que vous puissiez suivre sur votre propre hôte.

## Prérequis et inventaire de l'environnement

Avant de modifier toute limite de ressource, connaissez votre environnement.

### Version de Docker et pilote cgroup

Le comportement des limites de ressources dépend de la version de Docker et du pilote cgroup configuré sur l'hôte. Exécutez :

```bash
docker version --format '{{.Server.Version}}'
```

Exemple de sortie attendue : `24.0.5`

Vérifiez le pilote cgroup :

```bash
docker info --format '{{.CgroupDriver}} {{.CgroupVersion}}'
```

Exemple de sortie attendue : `systemd 2`

Si vous voyez `cgroupfs` et la version 1 des cgroups, certaines fonctionnalités plus récentes comme `memory.high` peuvent ne pas être disponibles. La plupart des distributions modernes utilisent par défaut la version 2 des cgroups avec le pilote systemd.

### Noyau hôte et distribution

Confirmez que le noyau prend en charge les cgroups v2 :

```bash
grep cgroup /proc/filesystems
```

La sortie attendue inclut `nodev cgroup2`.

### Identifier les conteneurs en cours d'exécution et leurs limites actuelles

Listez les conteneurs en cours d'exécution :

```bash
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
```

Inspectez ensuite les limites d'un conteneur spécifique, par exemple `web` :

```bash
docker inspect web --format '{{json .HostConfig.Resources}}'
```

Exemple de sortie attendue :

```json
{"CpusetCpus":"","CpuShares":1024,"Memory":0,"NanoCpus":0,"MemorySwap":0,"PidsLimit":0}
```

Ici, `Memory: 0` signifie aucune limite de mémoire, et `NanoCpus: 0` signifie aucun quota CPU.

### Vérifier les fichiers cgroup d'un conteneur

Trouvez le chemin cgroup du conteneur :

```bash
docker inspect web --format '{{.HostConfig.CgroupParent}} {{.Id}}'
```

Ensuite, en utilisant l'ID du conteneur, consultez son répertoire cgroup (exemple pour cgroup v2) :

```bash
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/memory.max
```

Si aucune limite n'est définie, ce fichier contient `max`.

## Chemin de configuration sûr

### Étape 1 : Capturer les métriques de base

Avant de modifier les limites, mesurez l'utilisation actuelle. Utilisez `docker stats` :

```bash
docker stats --no-stream web
```

Exemple de sortie attendue :

```
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O        PIDS
c2f2...        web       0.15%     120MiB / 1.945GiB     6.03%     1.2kB / 0B       0B / 0B          7
```

Notez la colonne `MEM USAGE / LIMIT`. Si LIMIT indique la mémoire totale de l'hôte, le conteneur est illimité.

### Étape 2 : Choisir le bon type de limite

- **CPU** : Utilisez les parts CPU pour une pondération relative lorsque plusieurs conteneurs sont en concurrence. Utilisez un quota (NanoCpus) pour une limite absolue. Utilisez cpuset pour épingler à des cœurs spécifiques.
- **Mémoire** : Définissez une limite stricte (`--memory`) et éventuellement une limite de swap (`--memory-swap`). Comprenez le comportement OOM.
- **I/O disque** : Utilisez les limites blkio sur cgroup v1 ; sur cgroup v2, utilisez `--device-read-bps` et `--device-write-bps` pour les périphériques bloc.
- **PID** : Limitez le nombre de processus pour prévenir les bombes à fork avec `--pids-limit`.

### Étape 3 : Appliquer les limites via Docker Run

Exemple : exécutez un conteneur avec 512 Mo de mémoire, un quota CPU de 0,5 et 100 PID :

```bash
docker run -d --name web \
  --memory 512m \
  --memory-swap 1g \
  --cpus 0.5 \
  --pids-limit 100 \
  nginx:alpine
```

Vérifiez :

```bash
docker inspect web --format '{{json .HostConfig.Resources}}'
```

La sortie attendue inclut `"Memory":536870912`, `"NanoCpus":500000000`, `"PidsLimit":100`.

### Étape 4 : Appliquer les limites via Docker Compose

Dans `docker-compose.yml` :

```yaml
services:
  web:
    image: nginx:alpine
    deploy:
      resources:
        limits:
          cpus: '0.50'
          memory: 512M
        reservations:
          memory: 128M
    pids_limit: 100
```

Remarque : la clé `deploy` n'est honorée que par Docker Swarm. Pour `docker compose` (hors Swarm), utilisez la clé `resources` au niveau du service (Compose v2.24+ prend en charge `limits` et `reservations` sous `resources`). Alternativement, utilisez `mem_limit` et `cpus` pour une compatibilité plus ancienne :

```yaml
services:
  web:
    image: nginx:alpine
    mem_limit: 512m
    cpus: 0.5
    pids_limit: 100
```

Appliquez avec `docker compose up -d`.

## Vérification et diagnostics

Après avoir défini les limites, vérifiez leur application depuis l'intérieur et l'extérieur du conteneur.

### À l'intérieur du conteneur : vérifier la mémoire visible

Le conteneur voit la mémoire de l'hôte, pas la limite, sauf si vous définissez une réservation de mémoire. Cela perturbe beaucoup de monde. Exécutez :

```bash
docker exec web cat /proc/meminfo | head -3
```

La sortie attendue montre la mémoire totale de l'hôte, pas 512 Mo. Pour voir la limite cgroup, lisez :

```bash
docker exec web cat /sys/fs/cgroup/memory.max
```

Sortie attendue : `536870912` (512 Mo en octets).

### Test de stress de la limite de mémoire

Utilisez un outil comme `stress` à l'intérieur du conteneur :

```bash
docker exec web sh -c "apk add --no-cache stress-ng && stress-ng --vm 1 --vm-bytes 600M --timeout 30s"
```

Après quelques secondes, vérifiez si le processus a été tué par OOM :

```bash
docker inspect web --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
```

Sortie attendue : `true 137` (137 = 128 + 9, SIGKILL).

### Inspecter les événements cgroup

Sur cgroup v2, surveillez memory.events :

```bash
docker exec web cat /sys/fs/cgroup/memory.events
```

Recherchez les compteurs `oom` et `oom_kill` qui augmentent.

### Vérification de la limitation CPU

Pour tester le quota CPU, exécutez une tâche gourmande en CPU et observez la limitation :

```bash
docker run --rm --cpus 0.5 alpine sh -c "time crunch 100000000000"
```

Vérifiez que le pourcentage CPU de `docker stats` reste proche de 50 %.

Pour cgroup v2, lisez `cpu.stat` :

```bash
docker exec web cat /sys/fs/cgroup/cpu.stat | grep throttled
```

La sortie attendue inclut `nr_throttled` et `throttled_time`.

## Modes de défaillance et récupération

### OOM kill à l'intérieur du conteneur

Si l'application à l'intérieur du conteneur est tuée, le conteneur peut se terminer ou redémarrer. Vérifiez :

```bash
docker inspect web --format '{{.State.OOMKilled}}'
```

Si vrai, augmentez la limite de mémoire ou réduisez l'empreinte de l'application. Note : si `--memory-swap` n'est pas défini, le swap est désactivé et la limite est égale à `--memory`. Pour autoriser le swap, définissez `--memory-swap` > `--memory`.

### OOM au niveau de l'hôte

Si l'hôte manque de mémoire, le noyau peut tuer d'autres processus. Utilisez `dmesg` pour voir les OOM kills :

```bash
dmesg | grep -i oom
```

Récupération : ajoutez de la mémoire à l'hôte, réduisez les limites des conteneurs ou ajustez les paramètres d'overcommit.

### Famine CPU

Si un conteneur est excessivement limité, les performances de l'application se dégradent. Vérifiez `nr_throttled` dans `cpu.stat` ; s'il est élevé, envisagez d'augmenter `--cpus` ou de passer à cpuset.

### Limites d'I/O disque non appliquées

Sur certains pilotes de stockage, les limites blkio peuvent ne pas fonctionner. Vérifiez avec `docker info | grep Storage Driver`. Pour overlay2, les limites blkio peuvent être ignorées ; utilisez des limites spécifiques au périphérique ou passez à cgroup v2.

### Limite de PID dépassée

Si le conteneur atteint la limite de PID, le fork échoue : « Ressource temporairement indisponible ». Augmentez `--pids-limit` si c'est légitime, sinon corrigez l'application.

## Pièges courants et comment les éviter

1. **Supposer que le conteneur voit sa limite de mémoire** : De nombreuses applications lisent `/proc/meminfo` et pensent avoir la mémoire de l'hôte, provoquant des OOM. Solution : définissez des variables d'environnement ou utilisez des bibliothèques conscientes des cgroups.
2. **Définir des parts CPU au lieu d'un quota** : Les parts CPU sont relatives et ne plafonnent pas l'utilisation lorsque le CPU est inactif. Utilisez `--cpus` pour des limites strictes.
3. **Ne pas définir `--memory-swap`** : Si vous définissez `--memory` sans `--memory-swap`, le swap est désactivé et le conteneur ne peut pas utiliser de swap. Cela peut surprendre les applications qui s'attendent à du swap.
4. **Ignorer les limites de PID** : Une bombe à fork peut épuiser l'espace PID de l'hôte. Définissez toujours `--pids-limit` pour les conteneurs non fiables.
5. **Ne pas utiliser cgroup v2** : cgroup v2 offre une meilleure isolation et des fonctionnalités comme memory.high pour les limites souples. Migrez si possible.
6. **Surallouer les ressources de l'hôte** : La somme des limites des conteneurs peut dépasser la capacité de l'hôte. Surveillez l'utilisation globale et définissez des réservations appropriées.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle avant et après avoir modifié les limites de ressources Docker.

| Étape | Action | Propriétaire | Fréquence |
|------|--------|-------|-----------|
| 1 | Mesurer les métriques de base avec `docker stats` | Ingénieur DevOps (ex. Alex Johnson) | Avant chaque changement de limite |
| 2 | Examiner le profil de l'application et déterminer les limites requises | Propriétaire de l'application (ex. Priya Shah) | Pré-déploiement |
| 3 | Appliquer les limites dans l'environnement de préproduction d'abord | Ingénieur DevOps | À chaque changement |
| 4 | Exécuter des tests de charge pour vérifier les performances sous limites | Responsable QA | À chaque version |
| 5 | Surveiller les OOM kills et les métriques de limitation | Équipe SRE | En continu (alertes) |
| 6 | Documenter les limites dans le contrôle de version | Ingénieur DevOps | À chaque changement |
| 7 | Réviser les limites trimestriellement et ajuster selon les tendances | Responsable ingénierie | Trimestriel |

## Conclusion

Les limites de ressources Docker sont essentielles pour des environnements multi-locataires stables. En comprenant les mécanismes cgroup sous-jacents et en utilisant les commandes pratiques fournies ici, vous pouvez définir des limites CPU, mémoire, I/O et PID appropriées, diagnostiquer les problèmes d'application et récupérer après des défaillances. Commencez toujours par l'observation, apportez des modifications incrémentales et vérifiez avec des métriques concrètes.

Prochaine étape : sélectionnez un conteneur de votre environnement, suivez le chemin de configuration sûr pour définir une limite de mémoire, vérifiez avec un test de stress et documentez les résultats. Adoptez ensuite la liste de contrôle opérationnelle pour maintenir les limites au fil du temps.