Introduction
La mise à niveau et la migration des builds multi-étapes Docker ne se résument pas à une simple opération de copier-coller. Il s'agit d'une séquence d'observations, de modifications ciblées et d'étapes de vérification qui protègent votre pipeline de build et les images qu'il produit. Ce guide vous accompagne du début à la fin – depuis la vérification de vos versions actuelles de Docker et BuildKit jusqu'à la restauration en toute sécurité en cas de problème.
Vous apprendrez à :
- Inventorier votre environnement Docker actuel et la configuration du build multi-étapes
- Identifier les exigences de compatibilité et choisir la bonne version de mise à niveau
- Tester les changements de manière contrôlée avant de toucher à la production
- Valider que le nouveau build produit une image correcte et plus légère
- Récupérer après des échecs courants sans perdre de données ni d'historique de build
Les exemples utilisent une application Node.js réaliste, mais les modèles s'appliquent à n'importe quel langage ou framework. Les commandes sont présentées avec les sorties attendues et les signaux d'échec afin que vous puissiez comparer ce que vous voyez avec ce qui devrait se produire.
Inventaire des versions et de l'environnement
Avant de changer quoi que ce soit, documentez l'état exact de votre installation Docker et des builds que vous migrez. Cela vous donne une base de comparaison et un point de restauration.
Vérifier le moteur Docker et la CLI
Exécutez ces commandes et enregistrez la sortie :
docker version
La sortie attendue comprend des sections distinctes pour le client et le serveur avec les numéros de version. 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:01 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:01 2023
OS/Arch: linux/amd64
Experimental: false
Si la section Serveur est absente ou affiche une erreur, le démon Docker n'est pas en cours d'exécution. Démarrez-le avec sudo systemctl start docker (Linux) ou Docker Desktop (Windows/macOS).
Vérifier le support de BuildKit
Les builds multi-étapes utilisent BuildKit pour des fonctionnalités avancées comme le cache mount et les secrets. Vérifiez si BuildKit est activé :
docker buildx version
Sortie attendue :
github.com/docker/buildx v0.12.0 4b6b4b8
Si vous voyez docker: 'buildx' is not a docker command, installez le plugin buildx ou activez le builder hérité. Pour la plupart des versions modernes de Docker (20.10+), BuildKit est le builder par défaut. Vérifiez avec :
docker buildx ls
Exemple de sortie :
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running 20.10.24 linux/amd64, linux/arm64
desktop-linux docker
desktop-linux desktop-linux running 23.0.6 linux/amd64, linux/arm64
Identifier votre Dockerfile multi-étapes actuel
Examinez le Dockerfile que vous prévoyez de migrer. Un build multi-étapes typique pour une application Node.js peut ressembler à ceci :
# Étape 1 : build
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Étape 2 : production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/server.js"]
Notez les noms des étapes (builder, production), les images de base et les copies de fichiers. Ce seront les points focaux des changements de mise à niveau.
Vérification des données et volumes avant la mise à niveau
Si votre processus de build utilise des volumes pour la mise en cache (par exemple, les caches npm ou Maven), confirmez où ils sont stockés. Un volume nommé comme npm-cache:/root/.npm persiste entre les builds et peut être réutilisé. Un montage bind comme ./.npm-cache:/root/.npm dépend de l'existence du répertoire hôte.
Pour tester la persistance, exécutez un build avec la mise en cache activée, puis redémarrez le démon Docker et reconstruisez. Comparez les temps de build et vérifiez si le cache a été réutilisé. Exemple :
docker buildx build --push -t myapp:test --build-arg BUILDKIT_INLINE_CACHE=1 .
# Notez le temps de build, puis reconstruisez après le redémarrage
time docker buildx build --push -t myapp:test --build-arg BUILDKIT_INLINE_CACHE=1 .
Si le deuxième build est presque aussi rapide que le premier et que les journaux montrent CACHED, votre configuration de volume est correcte. Si le deuxième build ré-exécute toutes les étapes, le cache n'a pas été persisté et vous devez ajuster les montages de volume.
Chemin de configuration sûr
Cette section couvre les étapes réelles de migration : mise à jour du Dockerfile, de l'environnement et de la configuration du builder de manière à pouvoir être restaurée.
Choisir la version cible
Consultez les notes de version de Docker et BuildKit pour les changements cassants. Par exemple, Docker Engine 23.0 a déprécié le builder hérité et a fait de BuildKit le défaut. Si vous passez d'une version plus ancienne, prévoyez de tester BuildKit explicitement.
Exemple de version cible :
- Actuel : Docker 20.10.24, BuildKit 0.8.2
- Cible : Docker 24.0.7, BuildKit 0.12.0
Vérifiez que votre runner CI ou hôte de production peut installer la version cible. Pour les systèmes basés sur apt :
sudo apt-get update
sudo apt-get install docker-ce=5:24.0.7-1~ubuntu.22.04~jammy docker-ce-cli=5:24.0.7-1~ubuntu.22.04~jammy
Épinglez toujours la version exacte pour éviter des mises à niveau accidentelles.
Mettre à jour les définitions d'étapes du Dockerfile
Migrez les changements de syntaxe. Par exemple, si vous utilisiez le format hérité FROM ... AS, il est inchangé. Mais si vous utilisiez une ancienne syntaxe de cache mount, mettez-la à jour.
Ancienne syntaxe (avant BuildKit 0.9) :
RUN --mount=type=cache,target=/root/.npm npm ci
Nouvelle syntaxe :
RUN --mount=type=cache,target=/root/.npm npm ci
(Pas de changement ici, mais assurez-vous de ne pas utiliser de drapeaux dépréciés comme --stream dans docker build.)
Plus important : si vous passez d'un build mono-étape à multi-étapes, divisez le build. Pour une application Python :
Avant :
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Après :
FROM python:3.9 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt
FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Cela réduit la taille de l'image en excluant les outils de build et les caches.
Ajuster les arguments de build et l'environnement
Si votre build utilise ARG pour des dépendances versionnées, mettez-les à jour pour correspondre à la nouvelle image de base. Exemple :
ARG NODE_VERSION=18
FROM node:${NODE_VERSION} AS builder
Lors de la migration vers Node 20, changez la valeur par défaut :
ARG NODE_VERSION=20
Puis construisez avec :
docker build --build-arg NODE_VERSION=20 -t myapp:node20-test .
Ne codez pas en dur les versions à l'intérieur du Dockerfile sauf si nécessaire ; utilisez des valeurs par défaut ARG et surchargez au moment du build pour plus de flexibilité.
Tester le nouveau build isolément
Avant de remplacer les images existantes, construisez avec un tag différent et exécutez des tests de fumée.
docker build -t myapp:upgrade-test .
docker run -d --name upgrade-test -p 3001:3000 myapp:upgrade-test
curl http://localhost:3001/health
Attendu : {"status":"ok"}.
Vérifiez les journaux :
docker logs upgrade-test --tail 20
Si l'application ne démarre pas, inspectez le conteneur :
docker inspect upgrade-test | jq '.[0].State'
Recherchez "Running": false et "ExitCode": 1 avec un message d'erreur.
Exécuter un build parallèle et comparer les images
Comparez les anciennes et nouvelles images en taille et en couches :
docker images myapp
Exemple de sortie :
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp old-prod 1a2b3c4d5e6f 2 weeks ago 450MB
myapp upgrade-test a1b2c3d4e5f6 10 minutes ago 320MB
Une migration multi-étapes réussie devrait réduire la taille de l'image, parfois de 50 % ou plus. Utilisez docker history myapp:upgrade-test pour vérifier que seuls les fichiers de l'étape finale sont présents.
Vérification et diagnostics
Après la mise à niveau, confirmez que le nouveau build est correct et que le conteneur résultant se comporte comme prévu.
Vérification automatisée du build
Exécutez le build avec des vérifications qui échouent rapidement en cas d'erreur :
docker build --progress=plain --no-cache -t myapp:verify . 2>&1 | tee build.log
Inspectez build.log pour tout avertissement ou erreur. Problèmes courants :
WARNING: No output specified for stage X- une étape est inutilisée ou mal définieERROR: failed to solve: failed to compute cache key- les chemins de fichiers ne correspondent pasERROR: executor failed running [/bin/sh -c npm run build]: exit code: 1- le script de build a échoué
Chacun de ces problèmes nécessite une correction différente : vérifiez les noms d'étapes, confirmez les chemins COPY ou déboguez la commande de build.
Commandes de vérification à l'exécution
Pour le conteneur en cours d'exécution, exécutez des vérifications de santé :
docker exec upgrade-test sh -c "curl -s http://localhost:3000/health"
Attendu : {"status":"ok"}.
Vérifiez que le conteneur utilise les couches d'image de base attendues :
docker inspect upgrade-test | jq '.[0].Config.Image'
Attendu : myapp:upgrade-test (ou le digest de l'image de base).
Comparaison des performances et des ressources
Mesurez le temps de démarrage et l'utilisation de la mémoire entre les anciens et nouveaux conteneurs.
time docker run --rm myapp:old-prod true
time docker run --rm myapp:upgrade-test true
Pour la mémoire :
docker stats --no-stream upgrade-test
Enregistrez les valeurs. Un build multi-étapes ne devrait pas augmenter l'utilisation des ressources ; souvent, il la diminue car l'image finale est plus légère.
Valider le contenu de l'image
Assurez-vous que l'image finale ne contient pas d'outils de build ou de code source. Utilisez un conteneur temporaire pour explorer :
docker run --rm -it myapp:upgrade-test sh
À l'intérieur, exécutez :
ls -la /app
Vous devriez voir uniquement la sortie compilée et les dépendances d'exécution. Si vous voyez des artefacts de build comme node_modules avec des dépendances de développement, la copie multi-étapes est trop large.
Corrigez en copiant uniquement les répertoires nécessaires :
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
Ne copiez pas /app en entier.
Modes d'échec et récupération
Même avec une planification minutieuse, les mises à niveau peuvent échouer. Voici les modes d'échec courants et comment récupérer.
Échec de build dû à des fonctionnalités BuildKit manquantes
Si le build échoue avec une erreur comme :
ERROR: BuildKit is enabled but the buildx component is missing or broken
Ou :
ERROR: failed to solve: rpc error: code = Unknown desc = failed to load cache key
Cela se produit souvent lorsque la version de Docker est plus ancienne que les fonctionnalités utilisées dans le Dockerfile. Vérifiez la version de BuildKit et envisagez d'utiliser le builder hérité comme solution de repli temporaire :
DOCKER_BUILDKIT=0 docker build -t myapp:legacy .
Mais ne comptez pas sur le mode hérité à long terme ; mettez à niveau Docker à la place.
Échec de tirage de l'image de base
La nouvelle image de base peut ne pas exister ou être incompatible avec votre architecture.
Erreur :
ERROR: failed to solve: node:20: not found
Vérifiez les tags disponibles :
docker manifest inspect node:20
Si aucun manifeste, utilisez un tag différent comme node:20-alpine. Restauration : revenez à la ligne FROM du Dockerfile précédent et reconstruisez.
Échec d'installation des dépendances dans l'étape de build
Une étape d'installation de paquets échoue en raison de conflits de versions ou de bibliothèques natives manquantes.
Exemple avec Node :
> node-gyp rebuild
make: g++: No such file or directory
Correction : ajoutez des outils de build uniquement à l'étape de build :
FROM node:18 AS builder
RUN apt-get update && apt-get install -y python3 make g++
Ces outils ne sont pas inclus dans l'étape finale, donc la taille de l'image reste petite.
Crash à l'exécution après un build réussi
Le conteneur se construit mais se termine immédiatement avec un code non nul.
Vérifiez les journaux :
docker logs upgrade-test
Erreur courante : bibliothèque partagée manquante ou variable d'environnement. Par exemple :
Error: Cannot find module 'express'
Cela signifie que les dépendances de production n'ont pas été installées. Dans le Dockerfile, assurez-vous que npm ci --only=production s'exécute dans l'étape finale ou copiez node_modules depuis le builder.
Restauration : arrêtez et supprimez le conteneur cassé, puis exécutez l'ancienne image :
docker stop upgrade-test && docker rm upgrade-test
docker run -d --name prod-old -p 3000:3000 myapp:old-prod
Perte de données due à une mauvaise configuration de volume
Si le nouveau conteneur ne trouve pas les données existantes, vérifiez les montages de volume.
docker inspect upgrade-test | jq '.[0].Mounts'
Si le volume attendu est manquant, recréez le conteneur avec le bon drapeau -v :
docker run -d --name upgrade-test -v app_data:/var/lib/app -p 3000:3000 myapp:upgrade-test
Ne supprimez jamais les volumes nommés tant que vous n'avez pas vérifié que le nouveau conteneur fonctionne.
Liste de contrôle des opérations
Utilisez cette liste de contrôle avant et pendant une mise à niveau de build multi-étapes. Chaque élément a un responsable et une fréquence de révision.
| Étape | Responsable | Action | Vérifier | Revisiter |
|---|---|---|---|---|
| 1. Documenter les versions actuelles | Ingénieur DevOps (ex. Priya Shah) | Exécuter docker version et docker buildx version, sauvegarder la sortie | Versions enregistrées dans le runbook | Annuellement ou avant toute mise à niveau |
| 2. Examiner le Dockerfile pour les modèles multi-étapes | Développeur d'application (ex. Marcus Lee) | Identifier toutes les lignes FROM, AS, COPY --from | Aucun nom d'étape manquant | À chaque changement de Dockerfile |
| 3. Choisir la version cible Docker/BuildKit | Ingénieur DevOps | Vérifier les notes de version et la matrice de compatibilité | Version cible notée dans le ticket | Par cycle de mise à niveau |
| 4. Construire l'image de test avec la nouvelle version | Pipeline CI | Exécuter docker build -t myapp:upgrade-test . en staging | Le build se termine sans erreur | À chaque build |
| 5. Exécuter des tests de fumée sur le conteneur de test | Ingénieur QA (ex. Sofia Garcia) | Exécuter la vérification de santé et les tests fonctionnels | Tous les tests passent | À chaque candidat de version |
| 6. Comparer les tailles d'images et les couches | Ingénieur DevOps | docker images et docker history | Taille de la nouvelle image <= taille de l'ancienne ou justifiée | À chaque build |
| 7. Plan de restauration documenté | Ingénieur DevOps | Écrire les étapes de restauration dans le runbook | Runbook mis à jour et révisé | Avant le déploiement en production |
| 8. Surveiller la production après le déploiement | Ingénieur de fiabilité des sites (ex. Tom Okafor) | Regarder les journaux, les métriques et les taux d'erreur pendant 24 heures | Aucun pic d'erreur nouveau | Immédiatement après le déploiement, puis hebdomadairement pendant le premier mois |
Responsabilité : chaque élément a un seul responsable nommé. La fréquence de révision est spécifiée pour garantir une exactitude continue.
Pièges courants et comment les éviter
Ne pas épingler les versions des images de base
Utiliser FROM node:latest ou FROM ubuntu conduit à des changements inattendus lorsque l'image de base est mise à jour. Épinglez toujours la version majeure et mineure :
FROM node:18.17.1 AS builder
Ou utilisez le digest :
FROM node:18@sha256:... AS builder
Cela rend les builds reproductibles.
Copier des fichiers inutiles dans l'étape finale
Une erreur courante est COPY --from=builder /app /app, qui inclut le code source, les outils de build et les secrets. À la place, copiez uniquement les artefacts d'exécution :
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
RUN npm ci --only=production
Cela réduit la taille de l'image et la surface d'attaque.
Ignorer l'invalidation du cache de build
Les builds multi-étapes peuvent mettre en cache les couches de manière incorrecte si l'ordre de COPY et RUN n'est pas optimal. Pour Node.js, copiez d'abord package.json et package-lock.json, puis exécutez npm ci, puis copiez le reste du code source. Ainsi, l'installation des dépendances est mise en cache à moins que ces fichiers ne changent.
Avant :
COPY . .
RUN npm ci
Après :
COPY package*.json ./
RUN npm ci
COPY . .
Maintenant, les changements dans les fichiers sources n'invalident pas le cache des dépendances.
Sauter les tests de restauration
De nombreuses équipes testent la mise à niveau mais pas la restauration. Testez toujours le retour à la version d'image précédente.
docker run -d --name rollback-test -p 3000:3000 myapp:old-prod
curl http://localhost:3000/health
Si l'image de restauration fonctionne, vous avez un chemin sûr.
Ne pas utiliser les secrets BuildKit
Si votre étape de build a besoin d'accéder à des paquets privés ou à des identifiants, évitez de passer les secrets comme arguments de build car ils persistent dans l'historique de l'image. Utilisez les secrets BuildKit :
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
Construisez avec :
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp:secure .
Cela garde les secrets hors de l'image finale.
Conclusion
La mise à niveau et la migration des builds multi-étapes Docker est un processus contrôlé, pas une seule commande. En documentant votre état actuel, en apportant des changements ciblés, en vérifiant chaque étape et en planifiant la restauration, vous protégez votre pipeline de déploiement et l'exécution de l'application.
Points clés à retenir :
- Vérifiez toujours les versions de Docker et BuildKit avant de changer quoi que ce soit
- Mettez à jour le Dockerfile de manière incrémentale, étape par étape
- Testez la nouvelle image isolément avec un tag différent
- Comparez les tailles et contenus des images pour assurer une image finale plus légère
- Ayez un plan de restauration et testez-le
- Attribuez les responsabilités et révisez les listes de contrôle régulièrement
Utilisez ce guide comme modèle pour votre propre mise à niveau. Adaptez les commandes et les exemples à votre application et environnement spécifiques, et vous réduirez les risques et les temps d'arrêt.
Prochaine étape : choisissez un petit build multi-étapes de votre projet, exécutez les commandes d'inventaire et créez une base de référence. Ensuite, tentez une mise à niveau de version dans un environnement de test et vérifiez les résultats. Documentez ce qui a fonctionné et ce qui n'a pas fonctionné, et vous serez mieux préparé pour des migrations plus importantes.