tva
← Analyses

Une mise à niveau de Docker Engine sur un serveur en production

Mettre à niveau Docker Engine sur un serveur qui exécute plus d’une centaine de conteneurs ne laisse pas de place à l’improvisation. Lorsqu’est venu le moment de passer de Docker Engine 28.1 à 28.2 et Docker Compose 2.35 à 2.36 sur notre serveur de production Hetzner, nous avons abordé cette opération avec la même rigueur que nous appliquerions à une migration de base de données : une liste de contrôle de préparation écrite, une procédure testée de retour à la version précédente, une séquence d’exécution précise et une phase de vérification après la mise à niveau avant de clore la fenêtre de maintenance.

Ce qui suit est un compte rendu complet de notre approche — les raisons de chaque étape, les commandes que nous avons réellement exécutées, et ce qu’il faut surveiller lorsque votre nombre de conteneurs est suffisamment élevé pour qu’une politique de redémarrage mal configurée entraîne des pannes en cascade et complique le rétablissement des services.

Pourquoi cette mise à niveau nécessitait une procédure d’exploitation formalisée

Docker Engine 28.2 a ajouté des options de réseau bridge et une option du démon pour le routage direct vers les conteneurs, et a supprimé la prise en charge du téléchargement d’images héritées au format schema 1. Le plugin Compose est passé de 2.35.1 à 2.36.0 pendant la même fenêtre de maintenance. Il s’agit d’une version mineure, mais tout changement dans le démarrage d’une pile mérite d’être testé, en particulier les blocs depends_on avec condition: service_healthy.

Sur un serveur où un seul docker-compose.yml peut gérer douze services interdépendants — bases de données, proxys inverses, conteneurs d’application, workers en arrière-plan — un changement de comportement discret dans la résolution des dépendances est exactement le type de problème qui ne se manifeste pas immédiatement. Il se manifeste à 03:00 quand un contrôle de santé ne se déclenche jamais et qu’un conteneur dépendant boucle indéfiniment.

La bonne réponse est de se préparer, sans faire de la prudence une fin en soi.

Liste de contrôle de préparation

Avant de toucher un seul paquet, nous avons passé en revue la liste de contrôle suivante. Chaque élément a une raison d’être.

1. Inventorier tous les conteneurs en cours d’exécution et leurs politiques de redémarrage.

docker ps --format "table {{.Names}}	{{.Status}}	{{.Image}}" | sort
docker inspect $(docker ps -q) --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}}' | sort

Cela fournit une base de référence. Les conteneurs avec restart: always tenteront de redémarrer automatiquement après le redémarrage du démon Docker — ce qui est souhaitable, mais seulement si les fichiers Compose sous-jacents et les images sont dans un état connu et correct. Tout conteneur pris dans une boucle de plantages au moment de la mise à niveau le sera toujours après ; mieux vaut le savoir maintenant.

2. Valider tous les fichiers Compose avant la mise à niveau.

docker compose config --quiet 2>&1 | grep -i warning

Exécutez ceci dans chaque répertoire contenant un docker-compose.yml. La commande utilise la version de Compose actuellement installée : elle fait donc ressortir les avertissements existants sans prouver la compatibilité avec la version cible. Pour cela, testez la version cible dans un environnement de préproduction. Les versions actuelles de Compose signalent la clé obsolète version: en haut des fichiers Compose (elle est ignorée), et l’ancienne directive links: n’est plus nécessaire, car les services sur un réseau partagé peuvent se joindre par leur nom. Les avertissements peuvent devenir des erreurs dans les versions suivantes ; il vaut mieux les résoudre maintenant.

3. Confirmer l’espace disque disponible.

df -h /var/lib/docker
docker system df

La mise à niveau téléchargera un nouveau shim containerd et remplacera le binaire Docker Engine. L’ancien binaire et ses dépendances ne sont pas toujours nettoyés automatiquement. Sur un serveur qui tourne depuis un an, docker system df révèle souvent plusieurs gigaoctets de couches d’images récupérables. Nettoyez-les avant la mise à niveau, pas pendant.

docker system prune -f --volumes

Utilisez --volumes uniquement si vous êtes certain qu’aucun volume anonyme ne contient de données dont vous avez besoin. Sur notre serveur, toutes les données persistantes sont montées depuis des volumes nommés et explicitement déclarés — c’était donc sûr.

4. Exporter la version actuelle de Docker Engine et de Compose dans un fichier de référence.

docker version > /root/docker-pre-upgrade.txt
docker compose version >> /root/docker-pre-upgrade.txt
docker ps -a >> /root/docker-pre-upgrade.txt
dpkg-query -W docker-ce docker-ce-cli containerd.io docker-compose-plugin >> /root/docker-pre-upgrade.txt

Cela prend dix secondes et fournit une référence sans ambiguïté pour revenir à la version précédente si quelque chose tourne mal et que vous devez identifier quels conteneurs tournaient au moment de la mise à niveau.

5. Notifier les systèmes en aval.

Notre serveur exécute une pile Supabase auto-hébergée et plusieurs instances n8n d’automatisation des flux de travail. Tout webhook qui se déclenche pendant un cycle de redémarrage de conteneur échouera et, si l’émetteur ne réessaie pas, l’événement peut être perdu sans que personne ne le remarque. Nous avons défini une fenêtre de maintenance dans notre outil de supervision de la disponibilité et désactivé les webhooks entrants dans n8n pendant cette période.

Le plan de retour à la version précédente

Un plan de retour à la version précédente n’est utile que si vous avez décidé à l’avance quelle condition le déclenche. Le nôtre était simple : si un conteneur qui tournait avant la mise à niveau ne tourne pas dix minutes après la fin de celle-ci, et ne peut pas être récupéré avec un docker compose up -d, nous revenons au binaire Docker Engine précédent.

Revenir à la version précédente de Docker Engine sur un système basé sur Debian signifie épingler la version précédente du paquet. Nous avons noté la chaîne de version exacte avant de commencer :

apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin

Sortie sur notre serveur avant la mise à niveau :

docker-ce:
  Installed: 5:28.1.1-1~debian.12~bookworm
  Candidate: 5:28.2.2-1~debian.12~bookworm

docker-compose-plugin:
  Installed: 2.35.1-1~debian.12~bookworm
  Candidate: 2.36.0-1~debian.12~bookworm

La commande de retour à la version précédente, si nécessaire :

CONTAINERD_PREV=$(awk '$1 == "containerd.io" {print $2}' /root/docker-pre-upgrade.txt)
apt-get install -y --allow-downgrades   docker-ce=5:28.1.1-1~debian.12~bookworm   docker-ce-cli=5:28.1.1-1~debian.12~bookworm   containerd.io="${CONTAINERD_PREV}"   docker-compose-plugin=2.35.1-1~debian.12~bookworm

La commande fixe aussi containerd.io à la version enregistrée dans le fichier de référence à l’étape 4 et passe --allow-downgrades, car apt-get -y s’interrompt au lieu de revenir à des versions antérieures des paquets sans interaction.

Nous avons conservé cette commande dans un fichier texte sur le serveur à /root/docker-rollback.sh avec les permissions d’exécution, prête à être exécutée sans avoir à la saisir sous pression.

Le processus de mise à niveau

La mise à niveau proprement dite est simple une fois la préparation terminée. La séquence est importante : mettre à jour l’index des paquets en premier, vérifier ce qui sera installé, puis installer.

Étape 1 : Mettre à jour l’index apt uniquement pour le dépôt Docker.

apt-get update -o Dir::Etc::sourcelist="sources.list.d/docker.list"   -o Dir::Etc::sourceparts="-"   -o APT::Get::List-Cleanup="0"

Cela restreint la mise à jour au dépôt Docker plutôt que de rafraîchir toutes les sources. Sur un serveur de production, une mise à jour involontaire d’un paquet provenant d’une autre source pendant une fenêtre de maintenance Docker est une variable dont vous n’avez pas besoin.

Étape 2 : Confirmer les versions candidates.

apt-cache policy docker-ce docker-ce-cli containerd.io docker-compose-plugin

Vérifiez que la version candidate correspond à celle attendue avant de poursuivre. Si la version candidate est plus récente que celle testée en préproduction, interrompez la procédure pour l’évaluer.

Étape 3 : Installer.

apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

Cela arrêtera le démon Docker, remplacera les binaires et redémarrera le démon. Les conteneurs avec restart: always seront relancés par le démon au redémarrage. La fenêtre pendant laquelle les conteneurs ne tournent pas est généralement inférieure à trente secondes sur un serveur de taille raisonnable — bien que cela varie selon le temps que prend containerd à s’initialiser.

Surveillez le redémarrage dans un second terminal :

watch -n 2 'docker ps --format "table {{.Names}}	{{.Status}}" | sort'

Étape 4 : Vérifier les versions installées.

docker version
docker compose version

Sur notre serveur, la sortie a confirmé :

Docker Engine: 28.2.2
Docker Compose: v2.36.0

Docker Compose possède son propre versionnement, indépendant de Docker Engine : la version du paquet docker-compose-plugin (ici 2.36.0) est celle de Compose, et c’est celle que suit l’index apt.

Vérification après la mise à niveau

Dix minutes après la mise à niveau, nous avons effectué une vérification structurée. Cette vérification est indispensable — le redémarrage du démon relancera les conteneurs, mais il ne garantit pas que les conteneurs qui dépendent de contrôles de santé les ont réellement réussis.

Vérifier que tous les conteneurs sont dans l’état attendu.

docker ps -a --format "table {{.Names}}	{{.Status}}	{{.RunningFor}}" | sort

Tout conteneur affichant Restarting ou Exited nécessite une investigation immédiate. Sur notre serveur, un conteneur — un service d’analyse de données Supabase — affichait Restarting (1). Il était déjà sujet à des plantages répétés mais peu fréquents avant la mise à niveau ; la mise à niveau n’avait pas causé ce problème, mais la vérification qui a suivi l’a rendu plus visible. C’est exactement le type de problème préexistant que le recensement préalable des conteneurs vise à détecter.

Vérifier que les contrôles de santé réussissent.

docker inspect $(docker ps -q)   --format '{{.Name}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'   | grep -v "health=none" | sort

Les conteneurs sans contrôles de santé explicites n’ont pas d’état de santé ; selon la version de Docker, un {{.State.Health.Status}} non protégé affiche <no value> ou interrompt toute la commande avec une erreur de modèle. Le modèle vérifie donc d’abord .State.Health, et grep écarte les conteneurs qui n’en ont pas. Ceux qui comptent sont ceux dont dépendent vos autres conteneurs via condition: service_healthy.

Valider la configuration Compose de toutes les piles logicielles.

for dir in /opt/*/; do
  if [ -f "${dir}docker-compose.yml" ]; then
    echo "--- $dir ---"
    docker compose -f "${dir}docker-compose.yml" config --quiet 2>&1
  fi
done

Cela détecte toutes les directives de fichier Compose que la nouvelle version de Compose traite comme des avertissements ou des erreurs. Sur notre serveur, trois piles logicielles ont émis l’avertissement de dépréciation de la clé version. Ce sont des avertissements cosmétiques mais qui méritent d’être résolus lors du prochain cycle de maintenance.

Rechercher les erreurs dans les journaux du démon Docker.

journalctl -u docker --since "1 hour ago" | grep -i -E "error|warn|fatal"

Une mise à niveau propre ne produit aucune erreur dans le journal du démon. Les avertissements concernant la configuration dépréciée méritent d’être notés mais pas traités immédiatement.

Confirmer la connectivité réseau entre les conteneurs.

docker network ls
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}'

La deuxième commande affiche uniquement le réseau bridge par défaut. Répétez-la pour chaque réseau personnalisé répertorié par docker network ls afin de confirmer que les réseaux bridge personnalisés contiennent toujours les conteneurs attendus après le redémarrage du démon, et testez au moins une connexion réelle entre services dépendants.

Ce que nous avons observé

La mise à niveau s’est terminée en moins de deux minutes. Tous les conteneurs qui tournaient avant la mise à niveau tournaient après. Le changement de Compose que nous avons remarqué en premier était un format de sortie --dry-run plus clair — la nouvelle version produit une sortie structurée et colorée qui facilite l’examen de ce qu’un docker compose up fera réellement avant de l’exécuter.

L’ordre d’exécution des contrôles de santé depends_on s’est comporté de manière identique à Compose 2.35 sur nos piles logicielles. C’était attendu — nous avions validé les fichiers Compose au préalable — mais c’était rassurant de le confirmer.

Un constat substantiel : Compose n’autorise pas le champ container_name en combinaison avec scale, car les noms des conteneurs doivent être uniques. Nous avions une pile logicielle qui utilisait les deux — un vestige d’avant notre passage aux réplicas correctement configurés. Compose a émis une erreur claire sur docker compose up pour cette pile logicielle. La correction était une modification d’une ligne pour supprimer le champ container_name redondant.

Analyses connexes

Pour aller plus loin