## Introduction

Les conteneurs Docker partagent le noyau et les ressources de l'hôte. Sans limites explicites, un seul conteneur peut épuiser la mémoire ou le CPU, provoquant une instabilité pour les autres services ou l'hôte lui-même. Les limites de ressources sont un élément essentiel pour exécuter des conteneurs en toute sécurité, que ce soit en développement ou en production.

Cependant, définir ces limites conduit souvent à des erreurs déroutantes : conteneurs tués avec le code de sortie 137, processus incapables d'allouer de la mémoire, ou limitation du CPU entraînant des performances médiocres. Des limites mal configurées peuvent également empêcher les conteneurs de démarrer ou les faire se comporter de manière imprévisible.

Ce guide couvre les erreurs les plus courantes liées aux limites de ressources Docker, comment les diagnostiquer et les corriger. Nous nous concentrerons sur les limites de mémoire et de CPU, avec des notes sur les limites PID. Vous apprendrez à inspecter les paramètres actuels, à appliquer des configurations sûres, à les vérifier et à récupérer après un échec.

## Inventaire des versions et de l'environnement

Avant de modifier les limites de ressources, confirmez votre environnement.

### Vérifier la version de Docker

Les fonctionnalités de limites de ressources dépendent de la version de Docker et du système d'initialisation sous-jacent. Utilisez :

```bash
docker version
```

La sortie attendue inclut les versions client et serveur. Par exemple :

```
Client: Docker Engine - Community
 Version:           24.0.7
 API version:       1.43
 Go version:        go1.20.10
 Git commit:        311b9ff
 Built:             Thu Oct 26 09:08:15 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.7
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.10
  Git commit:       311b9ff
  Built:            Thu Oct 26 09:08:15 2023
  OS/Arch:          linux/amd64
  Experimental:     false
```

### Vérifier la prise en charge du noyau

Les limites de mémoire reposent sur les cgroups. Vérifiez que cgroup v2 ou v1 est actif :

```bash
docker info | grep -i cgroup
```

Sur les systèmes modernes, vous pourriez voir :

```
Cgroup Driver: systemd
Cgroup Version: 2
```

Si cgroup v2 n'est pas pris en charge, les limites de mémoire risquent de ne pas être appliquées correctement.

### Identifier les conteneurs en cours d'exécution

Listez les conteneurs et leur utilisation actuelle des ressources :

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

Cela donne un aperçu rapide.

### Prérequis

Vous aurez besoin de :

- Docker Engine 20.10 ou plus récent (pour une prise en charge fiable de cgroup v2)
- Des autorisations appropriées pour exécuter les commandes Docker (généralement root ou le groupe docker)
- Une image de conteneur de test (par exemple, `alpine` ou `ubuntu`)

## Chemin de configuration sécurisé

Les limites de ressources peuvent être définies au moment de l'exécution du conteneur, dans les fichiers Docker Compose, ou en mettant à jour des conteneurs existants. Nous couvrirons chaque cas avec des exemples.

### Options d'exécution

La commande `docker run` accepte `--memory` (ou `-m`) pour les limites de mémoire et `--cpus` pour les limites CPU.

Exemple de limite de mémoire :

```bash
docker run -d --name limited-nginx --memory 256m nginx
```

Cela limite le conteneur à 256 Mo de RAM. Si le conteneur dépasse cette limite, le noyau peut tuer le processus à l'intérieur du conteneur, et le conteneur se terminera avec le code 137.

Exemple de limite CPU :

```bash
docker run -d --name limited-cpu --cpus 1.5 nginx
```

Cela permet au conteneur d'utiliser jusqu'à 1,5 cœur CPU. Si le conteneur tente d'utiliser davantage, il sera limité.

Limites combinées :

```bash
docker run -d --name limited-app --memory 512m --memory-swap 1g --cpus 2 myapp
```

L'option `--memory-swap` définit le total mémoire+swap. Si vous définissez `--memory` et `--memory-swap` à la même valeur, le swap est désactivé.

Limite de PID :

```bash
docker run -d --name limited-pids --pids-limit 100 nginx
```

Cela limite le nombre de processus à l'intérieur du conteneur.

### Configuration Docker Compose

Dans un fichier `docker-compose.yml`, utilisez la section `deploy.resources` pour Docker Swarm ou les options de niveau supérieur `resources` (dans Compose v2.x avec `docker compose` et le mode non-swarm) ou les anciens champs `mem_limit`, `cpus`.

Exemple moderne Compose (v2.x) :

```yaml
services:
  web:
    image: nginx
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M
```

Remarque : La section `deploy` est utilisée par Docker Swarm et par `docker compose` avec le drapeau `--compatibility` ou dans les versions récentes avec `docker compose up`. Pour le développement local, vous pouvez utiliser les anciennes options plus simples :

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

### Mise à jour des conteneurs existants

Vous ne pouvez pas modifier les limites de ressources d'un conteneur en cours d'exécution directement. Vous devez arrêter le conteneur, le recréer avec de nouvelles limites, ou utiliser `docker update` pour certains paramètres.

Utilisation de `docker update` :

`docker update` peut modifier la mémoire, le CPU et d'autres limites sur un conteneur en cours d'exécution sans redémarrage. Par exemple :

```bash
docker update --memory 512m --cpus 1 mycontainer
docker update --memory-swap -1 mycontainer
```

Le `-1` pour memory-swap le réinitialise à un swap illimité (mais la limite de mémoire reste).

Cependant, certains changements peuvent nécessiter un redémarrage si le processus d'initialisation du conteneur doit voir les nouvelles limites.

## Vérification et diagnostic

Après avoir défini les limites, vérifiez qu'elles sont appliquées et testez leur comportement.

### Inspecter les limites du conteneur

Utilisez `docker inspect` pour voir les limites effectives :

```bash
docker inspect limited-nginx --format '{{.HostConfig.Memory}}'
```

La sortie sera le nombre d'octets, par exemple `268435456` pour 256 Mo.

Pour le CPU :

```bash
docker inspect limited-cpu --format '{{.HostConfig.NanoCpus}}'
```

La sortie sera `1500000000` pour 1,5 CPU (puisque 1 CPU = 1e9 NanoCpus).

### Vérifier les statistiques d'exécution

Utilisez `docker stats` pour observer l'utilisation en temps réel :

```bash
docker stats limited-nginx --no-stream
```

Sortie :

```
CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT   MEM %     NET I/O     BLOCK I/O   PIDS
abc123def456   limited-nginx   0.00%     2.5MiB / 256MiB     0.98%     0B / 0B     0B / 0B     5
```

La colonne `MEM USAGE / LIMIT` montre l'utilisation actuelle par rapport à la limite.

### Tester les limites de mémoire

Pour voir comment un conteneur se comporte lorsqu'il dépasse les limites de mémoire, exécutez un conteneur avec une limite de mémoire, puis exécutez une commande consommatrice de mémoire à l'intérieur.

```bash
docker run -d --name mem-test --memory 128m alpine sleep 300
docker exec mem-test sh -c "tail /dev/zero"
```

La commande `tail /dev/zero` consommera rapidement de la mémoire. Après quelques secondes, vérifiez l'état du conteneur :

```bash
docker ps -a | grep mem-test
```

Vous devriez voir que le conteneur s'est terminé avec le code 137.

Vérifiez les journaux :

```bash
docker logs mem-test
```

Il se peut qu'ils ne montrent rien car le processus a été tué par le tueur OOM. Sur l'hôte, vous pouvez vérifier `dmesg` pour les messages OOM.

### Tester les limites CPU

Pour observer la limitation du CPU, exécutez une tâche intensive en CPU avec une limite CPU basse :

```bash
docker run -d --name cpu-test --cpus 0.5 alpine sh -c "while true; do :; done"
```

Ensuite, surveillez l'utilisation du CPU :

```bash
docker stats cpu-test --no-stream
```

Vous devriez voir un pourcentage CPU autour de 50 % (0,5 CPU). Si vous vérifiez les statistiques CPU du cgroup du conteneur, vous verrez des compteurs de limitation.

## Modes d'échec et récupération

Comprendre les erreurs courantes et comment récupérer est essentiel.

### Le conteneur se termine avec le code 137

Le code de sortie 137 indique que le conteneur a été tué, généralement à cause d'un OOM (mémoire insuffisante) ou parfois d'une suppression manuelle. Si votre conteneur est tué de manière inattendue, vérifiez les limites de mémoire et l'utilisation.

Diagnostic :

- Vérifiez le code de sortie : `docker inspect <container> --format '{{.State.ExitCode}}'`
- Vérifiez les journaux pour les messages OOM : `docker logs <container>` peut ne pas montrer grand-chose ; vérifiez `dmesg` ou `journalctl` sur l'hôte pour les événements OOM.
- Inspectez la limite de mémoire : `docker inspect <container> --format '{{.HostConfig.Memory}}'`
- Vérifiez l'utilisation maximale de la mémoire avant la mort si disponible.

Correctif :

- Augmentez la limite de mémoire si l'application a légitimement besoin de plus de mémoire : `docker update --memory 512m <container>` ou recréez avec une limite plus élevée.
- Optimisez l'utilisation de la mémoire de l'application (par exemple, corrigez les fuites de mémoire).
- Ajoutez de l'espace swap si approprié : autorisez le swap en définissant `--memory-swap` supérieur à `--memory`.

Retour en arrière :

Si la limite augmentée cause des problèmes sur l'hôte, revenez à la limite précédente et réduisez l'application ou ajoutez plus de mémoire hôte.

### Le conteneur ne démarre pas en raison de limites invalides

Parfois, vous pouvez spécifier des limites invalides, comme `--memory 10g` sur un hôte avec moins de mémoire. Docker démarrera quand même le conteneur, mais la limite peut ne pas être réalisable. Plus fréquemment, vous pourriez obtenir une erreur comme :

```
docker: Error response from daemon: invalid memory limit: 0.
```

Ou si vous spécifiez une limite CPU trop basse, par exemple `--cpus 0.001`, Docker peut l'accepter mais le conteneur risque d'être affamé.

Diagnostic :

- Vérifiez le message d'erreur exact de `docker run`.
- Assurez-vous que la limite est un nombre positif et dans les capacités de l'hôte.

Correctif :

- Corrigez la valeur de la limite.
- Sur cgroup v2, évitez de définir des limites de swap sans limites de mémoire, car cela peut causer des erreurs.

### Limitation CPU et problèmes de performances

Si votre conteneur n'a pas les performances attendues, il se peut qu'il atteigne les limites CPU. Les symptômes incluent des temps de réponse lents et des métriques de limitation CPU élevées.

Diagnostic :

- Vérifiez les statistiques CPU du conteneur : `docker stats <container>` montre le % CPU ; comparez avec la limite.
- Inspectez la limitation CPU du cgroup : sur cgroup v2, vérifiez `cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/cpu.stat` et regardez `nr_throttled` et `throttled_time`.

Correctif :

- Augmentez la limite CPU si la charge de travail en a besoin.
- Optimisez l'application pour utiliser moins de cycles CPU.
- Ajustez la planification ou la parallélisation.

### Limite de PID dépassée

Si un conteneur tente de créer plus de processus que permis, il obtiendra des erreurs comme `fork: retry: Resource temporarily unavailable`.

Diagnostic :

- Vérifiez les journaux du conteneur pour les erreurs de fork.
- Inspectez la limite PID : `docker inspect <container> --format '{{.HostConfig.PidsLimit}}'` (la valeur par défaut est 0, signifiant illimité, mais vous avez peut-être défini une limite).
- Vérifiez le nombre actuel de PID : `docker exec <container> ps -e | wc -l`

Correctif :

- Augmentez `--pids-limit` si l'application a légitimement besoin de plus de processus.
- Réduisez le nombre de processus en optimisant l'application (par exemple, utilisez des threads plutôt que des processus).

## Pièges courants et comment les éviter

Les limites de ressources sont puissantes mais faciles à mal configurer. Voici les erreurs fréquentes et comment les éviter ou les récupérer.

### Définir des limites trop basses pour la charge de travail

De nombreuses équipes commencent avec des limites basses arbitraires sans profiler l'utilisation réelle, ce qui conduit à des tueries OOM fréquentes ou à une famine CPU.

Pourquoi cela arrive : Manque de compréhension des besoins en ressources de l'application.

Comment éviter : Établissez une base de référence de votre application dans un environnement de staging en utilisant des outils comme `docker stats` ou la surveillance des performances des applications. Définissez des limites avec au moins 20 à 30 % de marge au-dessus de l'utilisation maximale.

Exemple : Si votre application web atteint un pic à 700 Mo, définissez `--memory 1g` au lieu de `--memory 512m`.

Récupération : Augmentez les limites progressivement et surveillez la stabilité.

### Ignorer la configuration du swap

Par défaut, Docker peut ne pas autoriser le swap à moins que vous ne le définissiez explicitement. Si votre conteneur a occasionnellement besoin de plus de mémoire que la limite mais que vous voulez éviter les tueries OOM, activer le swap peut aider, mais cela peut ralentir le conteneur.

Pourquoi cela arrive : Mauvaise compréhension de la sémantique de `--memory-swap`.

Comment éviter : Comprenez que `--memory-swap` est le total mémoire+swap. Si vous définissez `--memory 512m --memory-swap 1g`, le conteneur a 512 Mo de RAM et 512 Mo de swap. Si vous définissez les deux à la même valeur, le swap est désactivé.

Récupération : Ajustez `--memory-swap` avec précaution et testez les performances.

### S'appuyer uniquement sur les options d'exécution

Lors de l'utilisation de l'orchestration ou de Compose, définir des limites uniquement via les drapeaux `docker run` peut conduire à des incohérences lorsque les conteneurs sont recréés.

Pourquoi cela arrive : Les équipes peuvent ne pas avoir de méthode standard pour définir les limites.

Comment éviter : Définissez les limites dans votre fichier Docker Compose ou vos manifestes d'orchestration (Kubernetes, Swarm). Cela garantit que les limites sont versionnées et appliquées de manière cohérente.

Récupération : Refactorisez pour utiliser les configurations Compose ou d'orchestration.

### Ne pas surveiller après le déploiement

Définir des limites n'est pas une tâche ponctuelle. Les applications changent et les limites doivent être ajustées.

Pourquoi cela arrive : Manque de surveillance et d'alertes continues.

Comment éviter : Mettez en place une surveillance avec des outils comme Prometheus, Datadog ou cAdvisor. Alertez sur des métriques comme l'utilisation de la mémoire du conteneur approchant de la limite ou la limitation CPU.

Récupération : Examinez et ajustez les limites en fonction des tendances d'utilisation, au moins trimestriellement ou après des changements significatifs de l'application.

### Mal comprendre les limites CPU

L'option `--cpus` de Docker est une limite stricte sur le temps CPU, pas une réservation. Si vous définissez `--cpus 1` pour une charge de travail en rafale qui a occasionnellement besoin de 2 CPU, elle sera limitée même si l'hôte a des CPU inactifs.

Pourquoi cela arrive : Confusion entre les limites CPU et les réservations CPU. Docker n'a pas de drapeau de réservation CPU séparé, mais vous pouvez utiliser les parts CPU (`--cpu-shares`) pour influencer la planification en cas de contention.

Comment éviter : Comprenez le modèle CPU de votre charge de travail. Pour les charges en rafale, envisagez de définir une limite CPU plus élevée ou d'utiliser `--cpu-shares` pour lui donner plus de poids.

Récupération : Ajustez `--cpus` ou `--cpu-shares` en fonction des observations de performances.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle lorsque vous travaillez avec les limites de ressources Docker.

| Étape | Action | Commande / Notes | Responsable | Fréquence |
|------|--------|-----------------|-------------|-----------|
| 1 | Vérifier la version de Docker et la prise en charge des cgroups | `docker version`, `docker info \| grep -i cgroup` | Ingénieur DevOps | Mensuellement ou après des changements d'infrastructure |
| 2 | Identifier les conteneurs et l'utilisation actuelle des ressources | `docker ps`, `docker stats` | Ingénieur DevOps | Hebdomadaire |
| 3 | Décider des limites en fonction des besoins de la charge de travail | Basé sur le profilage et les tests de l'application | Propriétaire de l'application (par exemple, Priya Shah, responsable d'ingénierie) | À chaque version ou trimestriellement |
| 4 | Appliquer les limites à l'aide des drapeaux `docker run` ou de Compose | Utiliser `--memory`, `--cpus`, `--pids-limit` | Ingénieur DevOps | À chaque déploiement |
| 5 | Vérifier que les limites sont appliquées | `docker inspect` pour vérifier HostConfig | Ingénieur DevOps | Immédiatement après le déploiement |
| 6 | Surveiller l'utilisation des ressources et la limitation | `docker stats`, vérifier les statistiques cgroup | Équipe de surveillance | Continu |
| 7 | Tester les scénarios d'échec (OOM, limitation CPU) | Exécuter des tests de stress dans le staging | Responsable QA | Avant chaque version majeure |
| 8 | Documenter les limites et le raisonnement | Mettre à jour les procédures opérationnelles | Ingénieur DevOps | Lors des changements |
| 9 | Configurer des alertes pour l'utilisation des ressources | Utiliser des outils de surveillance | Équipe de surveillance | Configuration initiale, puis maintenance |
| 10 | Examiner et ajuster les limites périodiquement | Comparer les tendances d'utilisation | Propriétaire de l'application et responsable DevOps | Trimestriel |

## Conclusion

Les limites de ressources Docker sont essentielles pour des opérations de conteneurs stables et prévisibles. Les erreurs courantes telles que le code de sortie 137, la limitation CPU et l'épuisement des PID peuvent être diagnostiquées et corrigées avec les bonnes commandes et configurations. Commencez par un pilote restreint pour tester les limites avant un déploiement à grande échelle, et vérifiez toujours avec `docker inspect` et `docker stats`. Documentez vos limites et révisez-les à mesure que les besoins des applications évoluent.

En suivant le chemin de configuration sécurisé et en ayant un plan de retour en arrière, vous pouvez éviter la plupart des problèmes liés aux ressources et maintenir vos applications conteneurisées en bon état de fonctionnement.

Ressources supplémentaires :

- Documentation Docker : [Options d'exécution avec mémoire, CPU et GPU](https://docs.docker.com/config/containers/resource_constraints/)
- Contraintes de ressources Docker Compose : [Référence du fichier Compose](https://docs.docker.com/compose/compose-file/)
