Optimisation des conteneurs Docker : Résoudre les goulots d'étranglement de performance

Votre conteneur Docker tourne-t-il lentement ? Ce guide essentiel détaille comment identifier et résoudre les goulots d'étranglement de performance courants dans les applications conteneurisées. Apprenez à utiliser efficacement les outils de surveillance Docker comme `docker stats`, à diagnostiquer une utilisation élevée du CPU/mémoire, à optimiser les performances d'E/S grâce à la connaissance du pilote de stockage, et à appliquer des bonnes pratiques comme les builds multi-étapes pour un fonctionnement plus rapide et plus efficace.

Optimisation des conteneurs Docker : Résoudre les goulots d'étranglement de performance

Lorsqu'un conteneur Docker est lent, le conteneur lui-même est rarement la seule explication. Le problème se situe généralement une couche plus bas : limitation du CPU, pression mémoire, écritures disque lentes, délais DNS, voisins bruyants sur l'hôte, ou une application déjà inefficace avant d'être conteneurisée.

La façon la plus rapide de perdre du temps est de commencer à modifier les flags Docker avant de savoir quelle ressource est contrainte. Commencez par des preuves, isolez un goulot d'étranglement, modifiez une chose, et mesurez à nouveau.

Commencez par le triage, pas par le réglage

docker stats vous donne un aperçu rapide en direct :

docker stats
docker stats --no-stream

Utilisez-le pour répondre aux questions de base :

  • Le CPU est-il élevé et soutenu ?
  • La mémoire est-elle proche de la limite configurée ?
  • Les E/S bloc augmentent-elles pendant les requêtes lentes ?
  • Le nombre de processus est-il anormalement élevé ?
  • Les E/S réseau sont-elles alignées sur la charge de travail ?

Vérifiez ensuite l'état du conteneur et les logs :

docker logs --tail 100 <nom_ou_id_du_conteneur>
docker inspect <nom_ou_id_du_conteneur> --format 'OOM={{.State.OOMKilled}} Exit={{.State.ExitCode}} Restarting={{.State.Restarting}}'

Examinez également l'hôte. Un conteneur peut sembler innocent alors que l'hôte est en train de swapper ou que le disque est saturé :

top
free -m
vmstat 1
iostat -xz 1

iostat peut nécessiter le paquet sysstat. Si vous êtes sur Docker Desktop, rappelez-vous qu'il y a une machine virtuelle entre votre OS hôte et les conteneurs Linux, ce qui modifie le comportement des fichiers et du réseau.

Goulots d'étranglement CPU

Un CPU élevé peut signifier que l'application est occupée, sous-dimensionnée ou limitée. Ce sont des problèmes différents.

Vérifiez les paramètres CPU configurés :

docker inspect <conteneur> --format '{{json .HostConfig.NanoCpus}} {{json .HostConfig.CpuQuota}} {{json .HostConfig.CpuPeriod}}'

Si le conteneur est trop limité, les requêtes peuvent s'accumuler même si l'hôte a encore du CPU inactif. Essayez une augmentation contrôlée :

docker run -d --name api --cpus="2" my-api:latest

Si le CPU reste élevé après avoir augmenté la limite, profilez l'application. Par exemple, un service Node peut être bloqué dans la sérialisation JSON, un worker Python peut être lié au CPU sous le GIL, et un service Java peut passer du temps dans le garbage collection. Docker ne peut pas résoudre cela par lui-même.

Pression mémoire et OOM Kills

Les problèmes de mémoire se manifestent souvent par des redémarrages, des pics de latence, ou la disparition du processus sous charge.

Vérifiez si Docker a détecté un OOM kill :

docker inspect <conteneur> --format '{{.State.OOMKilled}}'

Si la mémoire augmente lentement et ne diminue jamais, recherchez une fuite ou un cache non borné. Si la mémoire augmente lors de requêtes spécifiques, reproduisez ce chemin sous charge. Si un runtime de langage a sa propre limite de heap, alignez-la avec le conteneur. Une JVM qui ne comprend pas le budget réel du conteneur peut se comporter mal ; les JVM modernes sont conscientes des conteneurs, mais les paramètres de heap méritent toujours une révision.

Les limites de mémoire doivent laisser une marge. Un conteneur utilisant 950 Mo sur une limite de 1 Go pendant un trafic normal n'est pas sain. Le garbage collection, les tampons temporaires, TLS, la compression et les pics de requêtes ont tous besoin d'espace.

Résoudre les problèmes de performance d'entrée/sortie (E/S)

Un accès disque lent affecte les bases de données, les files d'attente, les moteurs de recherche, les préchauffages de cache et la journalisation bavarde. Commencez par déterminer si les écritures vont vers la couche inscriptible du conteneur, un volume nommé ou un point de montage.

docker inspect <conteneur> --format '{{json .Mounts}}'
docker info --format 'StorageDriver={{.Driver}}'

Sur les installations Docker Linux modernes, overlay2 est le pilote de stockage par défaut courant. C'est généralement un bon choix, mais écrire des données mutables lourdes dans la couche du conteneur reste une mauvaise pratique.

Utilisez des volumes nommés pour les données persistantes de l'application :

docker volume create app-data
docker run -d --name app -v app-data:/var/lib/app my-image

Utilisez des points de montage lorsque vous avez besoin d'un chemin hôte spécifique, mais testez-les. Les points de montage Docker Desktop sur macOS et Windows peuvent être beaucoup plus lents que l'accès natif au système de fichiers Linux car les opérations sur les fichiers traversent une frontière de virtualisation.

Pour les fichiers temporaires à haute vitesse, /dev/shm peut aider, mais il est basé sur la mémoire et limité :

docker run --shm-size=512m my-image

Ceci est courant pour les navigateurs, les exécuteurs de tests et les applications qui ont besoin de mémoire partagée. Ce n'est pas un remplacement pour un stockage réel.

Optimisation de la taille de l'image et des performances de construction

La taille de l'image affecte principalement le temps de construction, de tirage, d'analyse et de déploiement. Elle ne rend généralement pas le traitement des requêtes plus rapide une fois le conteneur en cours d'exécution, mais elle reste importante sur le plan opérationnel.

Utilisez des builds multi-étapes pour que les compilateurs, les caches de paquets et les outils de test ne se retrouvent pas dans l'image d'exécution :

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o app

FROM alpine:3.20
COPY --from=builder /app/app /app
CMD ["/app"]

Ordonnez les instructions du Dockerfile pour la réutilisation du cache :

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

La modification du code source ne doit pas forcer le téléchargement des dépendances, sauf si les fichiers de dépendances ont changé.

Considérations sur les performances réseau

Les ralentissements réseau ressemblent souvent à des lenteurs d'application. Testez depuis l'intérieur du conteneur :

docker exec -it <conteneur> sh
time getent hosts api.example.com
time wget -qO- https://api.example.com/health

Si la résolution DNS est lente ou instable, inspectez /etc/resolv.conf dans le conteneur et comparez-le avec l'hôte. Vous pouvez fournir des serveurs DNS au moment de l'exécution :

docker run -d --name web --dns 1.1.1.1 my-image

Faites cela comme un choix de diagnostic ou de politique, pas comme un correctif aléatoire. Dans les réseaux d'entreprise, le DNS interne peut être requis.

Le réseau bridge par défaut de Docker ajoute du traitement NAT et iptables. Pour la plupart des applications web, la surcharge est acceptable. Le réseau hôte peut réduire la surcharge sur Linux :

docker run --network host my-image

Cela supprime également l'isolation de l'espace de noms réseau et modifie la gestion des ports. Utilisez-le lorsque vous avez mesuré un besoin réel.

Une liste de vérification sur le terrain

Lorsqu'un conteneur est lent, parcourez cette liste :

  1. Reproduisez le ralentissement avec une requête, un travail ou une charge de travail spécifique.
  2. Capturez docker stats --no-stream, les logs et la sortie de l'inspection du conteneur.
  3. Vérifiez le CPU, la mémoire, le swap, les E/S disque et le réseau de l'hôte.
  4. Identifiez la ressource contrainte avant de modifier les limites.
  5. Déplacez les écritures persistantes ou lourdes vers des volumes.
  6. Comparez le comportement des points de montage sur Linux par rapport à Docker Desktop si le développement local est le seul endroit lent.
  7. Profilez l'application lorsque les métriques du conteneur n'expliquent pas le ralentissement.
  8. Modifiez un paramètre et mesurez à nouveau.

L'état d'esprit utile est simple : Docker vous offre l'isolation et l'empaquetage, mais il ne supprime pas le travail normal des systèmes. Le CPU, la mémoire, le disque et le réseau décident toujours de la rapidité du service.