## Introduction

En production, les conteneurs se disputent souvent des ressources finies : CPU, mémoire et E/S. Sans limites explicites, un conteneur peut dégrader tout l'hôte, provoquant des pics de latence ou des pannes. Les limites de ressources Docker permettent aux opérateurs d'imposer des frontières, de protéger les services critiques et de maintenir des performances prévisibles.

Cet article fournit une liste de contrôle pratique pour planifier, configurer, vérifier et maintenir les limites de ressources Docker en production. Vous y trouverez des commandes concrètes, des exemples de configuration et les résultats attendus pour vous guider.

## Inventaire de version et d'environnement

Avant de définir des limites de ressources, documentez votre environnement. Confirmez la version du démon Docker, le pilote de stockage et la couche d'orchestration (le cas échéant). Le support des limites de ressources varie selon la version de Docker et le runtime. Par exemple, les limites CPU nécessitent Docker 1.13+, tandis que le support cgroup v2 est arrivé dans Docker 20.10.

Exécutez la commande suivante pour vérifier la version de Docker et la version de cgroup :

```bash
docker info --format '{{.ServerVersion}} | cgroup driver: {{.CgroupDriver}} | cgroup version: {{.CgroupVersion}}'
```

Résultat attendu (exemple) :

```
20.10.12 | cgroup driver: cgroupfs | cgroup version: 1
```

Vérifiez également le noyau hôte et le système d'exploitation : `uname -r`. Assurez-vous d'avoir la permission de modifier les paramètres cgroup (root ou sudo). Consignez ces détails dans votre runbook.

Si vous utilisez des orchestrateurs comme Kubernetes, sachez que les limites de ressources Docker peuvent être gérées par le planificateur. Ici, nous nous concentrons sur Docker Engine directement, mais de nombreux principes s'appliquent.

Ensuite, identifiez les charges de travail. Classez les conteneurs comme critiques, normaux ou best-effort. Les services critiques ont besoin de ressources garanties, tandis que les best-effort peuvent être fortement limités. Inventoriez l'utilisation actuelle avec `docker stats --no-stream` pour établir des références.

Exemple de sortie de `docker stats --no-stream` :

```
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O        PIDS
f1e2d3c4b5a6   web       5.00%     150MiB / 512MiB       29.30%    1.2MB / 800kB    4.1MB / 0B       12
b7c8d9e0f1a2   db        12.00%    800MiB / 1GiB         78.13%    2.5MB / 1.8MB    10MB / 20MB      25
```

Utilisez ces données pour définir des limites initiales et identifier les conteneurs qui nécessitent une attention.

## Chemin de configuration sécurisé

Appliquez les limites progressivement. Commencez par des conteneurs non critiques dans un environnement de préproduction, puis étendez. Définissez les limites au lancement du conteneur avec la commande `docker run` ou dans des fichiers Compose avec la section `deploy.resources` (pour Swarm) ou `resources` (pour les versions plus récentes de Compose).

### Étape 1 : Définir des limites CPU

Les limites CPU sont des poids relatifs lors de l'utilisation du planificateur par défaut. Pour des limites strictes, utilisez `--cpus` (par exemple, `--cpus=1.5` autorise 1,5 cœur). Si vous avez besoin d'un poids proportionnel, `--cpu-shares` existe, mais la documentation Docker actuelle se concentre sur `--cpus` et ne couvre pas `--cpu-shares`.

**Exemple de limite CPU stricte :**

```bash
docker run -d --name web --cpus=1.5 --memory=512m nginx
```

Ce conteneur peut utiliser au maximum 1,5 cœur CPU. Pour confirmer, exécutez `docker inspect web --format '{{.HostConfig.NanoCpus}}'` et vous verrez `1500000000` (1,5 * 1e9 nanocpus).

**Exemple de limite CPU pour plusieurs conteneurs :**

```bash
docker run -d --name app1 --cpus=0.5 monimage
docker run -d --name app2 --cpus=1.0 monimage
```

App1 est limité à 0,5 CPU et app2 à 1,0 CPU. En cas de contention CPU, app2 peut utiliser jusqu'à deux fois plus de temps CPU que app1. Si vous avez besoin d'un poids relatif, `--cpu-shares` existe toujours, mais il n'est pas couvert dans la documentation Docker actuelle.

**Quota et période CPU (avancé) :**

Pour définir directement un quota CPU, utilisez `--cpu-quota=50000 --cpu-period=100000` pour 50 % d'un cœur.

```bash
docker run -d --name batch --cpu-quota=50000 --cpu-period=100000 montravailbatch
```

Cela équivaut à 0,5 CPU.

### Étape 2 : Définir des limites mémoire

Définissez toujours des limites mémoire pour prévenir les incidents OOM (Out-Of-Memory). Utilisez `--memory` (pour la RAM) et `--memory-swap` (pour la mémoire totale + swap). Si `--memory-swap` est omis, il prend par défaut le double de la limite mémoire.

**Limite mémoire avec swap désactivé :**

```bash
docker run -d --name api --memory=1g --memory-swap=1g monapp
```

Cela empêche le conteneur d'utiliser le swap, c'est-à-dire que la mémoire totale est plafonnée à 1 Go. Si le conteneur tente de dépasser cela, il sera tué par OOM.

**Limite mémoire avec swap autorisé :**

```bash
docker run -d --name cache --memory=512m --memory-swap=1g redis
```

Cela autorise 512 Mo de RAM plus 512 Mo de swap. L'utilisation du swap peut dégrader les performances ; à utiliser avec prudence.

**Réservation mémoire (limite douce) :**

```bash
docker run -d --name web --memory=1g --memory-reservation=750m nginx
```

Sous pression mémoire, le noyau tente de récupérer de la mémoire jusqu'à la réservation, mais ne tuera pas par OOM au-dessus de cette réservation à moins que la limite stricte ne soit dépassée.

### Étape 3 : Définir des limites Blkio (facultatif)

Pour les E/S disque, utilisez `--device-read-bps`, `--device-write-bps`, `--device-read-iops`, `--device-write-iops`.

**Limiter le débit d'écriture sur un périphérique spécifique :**

```bash
docker run -d --name db --device-write-bps /dev/sda:10mb postgres
```

Cela limite les écritures à 10 Mo/s sur /dev/sda. Utile pour empêcher un voisin bruyant de saturer les E/S disque.

**Limiter les IOPS en lecture :**

```bash
docker run -d --name logger --device-read-iops /dev/sda:100 monappdelog
```

Cela limite les lectures à 100 IOPS.

### Étape 4 : Utiliser Compose pour la répétabilité

**Pour Compose v3 (Swarm), utilisez la section `deploy` :**

```yaml
version: "3.8"
services:
  web:
    image: nginx
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M
```

Remarque : les `reservations` ne sont honorées qu'en mode Swarm.

**Pour Compose v2 (non-Swarm), utilisez les clés de service de niveau supérieur :**

```yaml
version: "2.4"
services:
  web:
    image: nginx
    mem_limit: 256m
    memswap_limit: 512m
    cpus: 0.5
    cpu_shares: 512
```

Pour Compose v1 avec l'outil autonome `docker-compose`, vous pouvez également utiliser `mem_limit` et `cpus`.

**Exemple complet de `docker-compose.yml` pour une pile de type production :**

```yaml
version: "3.8"
services:
  frontend:
    image: nginx:1.21
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
    restart: always
  backend:
    image: monapp:2.0
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    environment:
      - DB_HOST=db
    restart: always
  db:
    image: postgres:13
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2.0'
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always
volumes:
  pgdata:
```

Lors du déploiement sur Swarm, utilisez `docker stack deploy -c docker-compose.yml mapile`. Pour non-Swarm, utilisez `docker compose up -d` avec une version qui prend en charge la clé `deploy` (ignorée) ou utilisez directement `mem_limit`/`cpus`.

### Étape 5 : Mettre à jour les conteneurs existants

Les conteneurs existants nécessitent une recréation pour appliquer de nouvelles limites. Utilisez `docker update` pour certains paramètres (parts CPU, limites mémoire) si le conteneur est en cours d'exécution, mais pas pour tous (par exemple, `--cpus`). Pour des résultats cohérents, recréez le conteneur avec les drapeaux souhaités.

**Mettre à jour la limite mémoire sur un conteneur en cours d'exécution :**

```bash
docker update -m 512m monconteneur
```

**Mettre à jour les parts CPU :**

```bash
docker update --cpu-shares 512 monconteneur
```

**Remarque :** `docker update` ne peut pas modifier `--cpus`, `--memory-swap` ou les paramètres blkio après la création. Pour ceux-ci, vous devez arrêter, supprimer et recréer le conteneur. Utilisez toujours une configuration versionnée pour la recréation.

## Vérification et diagnostics

Après avoir appliqué les limites, vérifiez qu'elles sont actives et observez le comportement sous charge.

### Inspecter les limites du conteneur

Utilisez `docker inspect` pour confirmer les paramètres :

```bash
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}' nom_conteneur
```

Exemple de sortie :

```
524288000 1500000000
```

Cela indique 512 Mo de mémoire (en octets) et 1,5 CPU (en nanocpus, 1,5 * 1e9).

**Inspecter tous les paramètres liés aux ressources :**

```bash
docker inspect --format 'Memory: {{.HostConfig.Memory}}, MemorySwap: {{.HostConfig.MemorySwap}}, CpuShares: {{.HostConfig.CpuShares}}, NanoCpus: {{.HostConfig.NanoCpus}}' web
```

Exemple de sortie :

```
Memory: 536870912, MemorySwap: 1073741824, CpuShares: 1024, NanoCpus: 500000000
```

### Surveiller l'utilisation à l'exécution

Utilisez `docker stats` pour voir la consommation actuelle et les limites :

```bash
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
```

Résultat attendu (hypothétique) :

```
NAME      CPU %     MEM USAGE / LIMIT     MEM %
web       15.00%    120MiB / 512MiB       23.44%
api       0.50%     300MiB / 1GiB         29.30%
db        45.00%    1.2GiB / 2GiB         60.00%
```

Notez que la colonne "MEM USAGE / LIMIT" montre la limite appliquée. Le % CPU est relatif au nombre de cœurs disponibles sur l'hôte. Avec une limite de 1,5 CPU, le % CPU maximum affiché serait de 150 %.

### Simuler une charge pour vérifier l'application

Pour la mémoire, exécutez une commande de stress à l'intérieur du conteneur :

```bash
docker exec -it web stress --vm 1 --vm-bytes 600M
```

Si la limite mémoire est de 512 Mo, le conteneur devrait être tué par OOM. Observez que le conteneur est tué ou limité lorsqu'il dépasse la limite. Vérifiez le code de sortie et les journaux : `docker inspect web --format '{{.State.OOMKilled}}'` devrait afficher `true` après un kill OOM.

Pour le CPU, exécutez une tâche intensive en CPU et vérifiez que l'utilisation ne dépasse pas la limite avec `docker stats`. Avec `--cpus=1.5`, le % CPU devrait plafonner à 150 % sur un hôte multicœur.

**Exemple de test de stress CPU :**

```bash
docker exec -it web bash -c "yes > /dev/null &"
```

Ensuite, observez `docker stats web` :

```
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O        PIDS
f1e2d3c4b5a6   web       150.00%   150MiB / 512MiB       29.30%    1.2MB / 800kB    4.1MB / 0B       3
```

Le % CPU sera limité exactement à 150 %.

### Vérifier les paramètres cgroup

Inspectez les fichiers cgroup de l'hôte (nécessite root) :

```bash
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
```

Devrait afficher `536870912` pour 512 Mo.

Sur les systèmes cgroup v2, le chemin peut différer : `/sys/fs/cgroup/system.slice/docker-<container_id>.scope/memory.max`.

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

Comprendre les modes de défaillance aide à la planification de la récupération. Les problèmes courants incluent :

- **Kills OOM** : Le conteneur dépasse la limite mémoire, le noyau tue un processus. Le conteneur peut redémarrer (si la politique de redémarrage le permet) ou rester arrêté. Atténuation : définissez des limites mémoire avec une marge, surveillez l'utilisation de la mémoire et mettez à l'échelle horizontalement.
- **Famine CPU** : Le conteneur est excessivement limité, provoquant une réponse lente. Atténuation : ajustez `--cpus` ou supprimez les limites CPU pour les services critiques.
- **Goulot d'étranglement E/S** : Des limites Blkio trop basses ralentissent les écritures de la base de données. Atténuation : augmentez les limites ou séparez les volumes de données.
- **Limites incorrectes dues à une mauvaise configuration** : Des fautes de frappe dans les drapeaux ou les fichiers Compose peuvent conduire à aucune limite ou à des restrictions involontaires. Atténuation : utilisez `docker inspect` pour vérifier après la création.

### Exemple de kill OOM

Supposons qu'un conteneur web ait `--memory=512m` et `--memory-swap=512m`. Un pic soudain amène l'application à allouer plus de 512 Mo. Le tueur OOM du noyau met fin au processus à l'intérieur du conteneur. Docker enregistre cela dans l'état du conteneur :

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

Sortie :

```
true 137
```

Le code de sortie 137 indique que le processus a été tué par SIGKILL (OOM). Le conteneur peut redémarrer si `--restart=always` est défini. Pour diagnostiquer, vérifiez `docker logs web` et les journaux dmesg de l'hôte pour les événements OOM.

### Étapes de retour en arrière

Pour annuler les modifications :

- Supprimez ou ajustez les limites en recréant le conteneur sans les drapeaux de limite.
- Si le conteneur a été tué, redémarrez-le avec la configuration précédente.
- Dans Compose, revenez à la section ressources précédente et exécutez à nouveau `docker compose up -d`.

**Exemple : Supprimer la limite mémoire d'un conteneur en cours d'exécution**

Vous pouvez utiliser `docker update -m 0 nom_conteneur` mais notez que tous les paramètres ne peuvent pas être mis à jour. Plus sûr : recréez à partir de l'image avec les drapeaux d'origine. Voici une recette :

```bash
# Arrêter et supprimer le conteneur
docker stop web && docker rm web
# Recréer sans limite mémoire
docker run -d --name web --cpus=1.5 nginx
```

### Vérifications de récupération

Après le retour en arrière, vérifiez :

- Le conteneur est en cours d'exécution : `docker ps` montre le conteneur.
- L'utilisation des ressources est normale : `docker stats` montre des valeurs attendues.
- Les points de terminaison de santé de l'application répondent (par exemple, `curl http://localhost/health` renvoie 200).
- Les métriques au niveau de l'hôte (CPU, mémoire, E/S disque) sont dans la plage normale.

De plus, surveillez les métriques au niveau de l'hôte pour vous assurer qu'aucun autre conteneur n'est affecté.

## Liste de contrôle des opérations

Utilisez cette liste de contrôle pour les opérations de routine et les revues :

- [ ] Confirmer la version Docker et le support cgroup (`docker info`).
- [ ] Inventorier tous les conteneurs de production et leurs utilisations de ressources de référence.
- [ ] Définir des niveaux de ressources (garanties, burst, best-effort) et les limites correspondantes.
- [ ] Appliquer les limites en utilisant les drapeaux `docker run` documentés ou les fichiers Compose.
- [ ] Vérifier les limites avec `docker inspect` et `docker stats`.
- [ ] Tester les limites sous charge pour assurer aucune terminaison inattendue.
- [ ] Mettre en place la surveillance et les alertes pour l'utilisation des ressources (par exemple, mémoire > 80 % de la limite).
- [ ] Documenter les procédures de récupération pour les kills OOM et la limitation.
- [ ] Planifier des revues périodiques pour ajuster les limites en fonction des charges de travail changeantes.
- [ ] Maintenir les images de conteneurs et Docker Engine à jour pour bénéficier des améliorations de gestion des ressources.
- [ ] Former les membres de l'équipe sur les configurations des limites de ressources et le dépannage.

### Table de définition des niveaux de ressources

| Niveau       | Services d'exemple     | Limite CPU (--cpus) | Limite mémoire | Parts CPU | Notes |
|--------------|------------------------|---------------------|----------------|-----------|-------|
| Critique     | PostgreSQL, Redis      | 2.0                 | 2 Go           | 2048      | Ressources garanties ; aucun compromis |
| Normal       | Frontend web, API      | 0.5                 | 512 Mo         | 1024      | Adéquat pour une charge typique |
| Best-effort  | Tâches batch, analytique | 0.25                | 256 Mo         | 512       | Peut être limité ; peut être tué sous pression |

### Configuration de la surveillance et des alertes

Utilisez un outil de surveillance comme Prometheus, Datadog ou cAdvisor pour collecter les métriques des conteneurs. Définissez des alertes basées sur des seuils :

- **Mémoire** : Alerter lorsque l'utilisation de la mémoire du conteneur > 80 % de la limite pendant plus de 5 minutes.
- **CPU** : Alerter lorsque le nombre de limitations CPU augmente (temps limité dans `cpu.stat` du cgroup) ou que l'utilisation CPU est proche de la limite pendant une période prolongée.
- **OOM** : Alerter sur tout événement de kill OOM (code de sortie 137 du conteneur ou compteur `oom_kill` dans `memory.events` du cgroup).
- **E/S disque** : Alerter lorsque le temps d'attente blkio est élevé ou que le débit approche de la limite.

Exemple de règle d'alerte Prometheus pour la mémoire :

```yaml
groups:
- name: docker
  rules:
  - alert: ContainerMemoryHigh
    expr: (container_memory_usage_bytes / container_spec_memory_limit_bytes) > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Conteneur {{ $labels.name }} utilisation mémoire supérieure à 80 %"
```

### Tâches de maintenance

- Revoir régulièrement `docker stats` pour identifier les conteneurs approchant des limites.
- Mettre à jour les fichiers Compose dans un contrôle de version, pas seulement en production.
- Après les déploiements, confirmer que les nouveaux conteneurs ont des limites appliquées automatiquement.
- Tester la restauration de la configuration depuis le contrôle de version.
- Tenir un journal d'audit des changements de limites de ressources.

### Sauvegardes

Les configurations de ressources doivent être stockées en tant qu'infrastructure-as-code (fichiers Compose ou scripts) et versionnées. Sauvegardez ces fichiers avec les autres configurations.

**Exemple de flux de travail Git :**

```bash
# Valider les modifications du fichier Compose
git add docker-compose.yml
git commit -m "Mettre à jour les limites de ressources pour le service db : mémoire 2g, cpus 2.0"
git push origin main
```

Stockez les sauvegardes dans un dépôt distant et envisagez des sauvegardes automatisées de tout le répertoire de configuration.

## Conclusion

Les limites de ressources Docker sont essentielles pour la stabilité en production. En suivant cette liste de contrôle, vous pouvez systématiquement appliquer, vérifier et maintenir des limites pour prévenir les conflits de ressources et les pannes. Commencez par un pilote sur un petit ensemble de conteneurs, mesurez l'impact et étendez progressivement. Documentez vos configurations et gardez-les sous contrôle de version. Avec ces pratiques, vos applications conteneurisées fonctionneront de manière prévisible et efficace.

Rappelez-vous : les limites de ressources ne sont pas à configurer et à oublier. Surveillez, examinez et ajustez en continu en fonction des modèles de charge de travail réels. Une stratégie solide de gestion des ressources est un élément clé d'une plateforme de conteneurs fiable.