Docker : empêcher les conteneurs de redémarrer lors des mises à jour — verrouillez les images de façon responsable

Cet article vous a aidé ?

Vous vous réveillez, consultez les tableaux de bord, et votre service « ennuyeux » ne l’est plus. Un conteneur a redémarré après un correctif habituel de l’hôte,
a récupéré une image différente de celle d’hier, et maintenant votre flux de connexion joue les tragédies.

C’est la terreur silencieuse des tags mutables des conteneurs : tout semble figé — jusqu’à ce que ce ne le soit plus. Réglons cela correctement : empêchez les conteneurs
de changer lors des mises à jour, sans transformer votre parc en musée d’images impossibles à corriger.

Ce que vous essayez réellement de contrôler

« Empêcher les conteneurs de démarrer lors des mises à jour » signifie généralement l’une des trois choses suivantes, et vous devriez préciser laquelle vous visez car les corrections diffèrent :

1) Ne pas changer les octets quand quelque chose redémarre

Vous acceptez que les conteneurs puissent redémarrer (mises à jour du noyau, redémarrage du démon, reboot du nœud, reschedule par l’orchestrateur), mais vous voulez que le conteneur redémarré
utilise exactement les mêmes octets d’image qu’avant. C’est le problème de verrouillage d’image.

2) Ne pas redémarrer les conteneurs pendant les maintenances d’hôte

Il s’agit de maintenance et d’orchestration : drainer les nœuds, échelonner les reboots, configurer le comportement de mise à jour de systemd/docker, et éviter l’automatisation « utile »
qui redémarre tout lors des mises à jour de paquets. Le verrouillage aide, mais n’empêche pas les redémarrages.

3) Ne pas déployer de nouvelles versions d’application sans changement explicite

C’est de l’ingénierie des releases : découpler la construction du déploiement, exiger un changement de configuration (commit Git / demande de changement) pour déployer un nouveau digest,
et rendre les rollbacks ennuyeusement fiables.

Cet article se concentre sur (1) et (3), avec assez de réalité opérationnelle pour que (2) ne vous prenne pas au dépourvu.

Faits et bref historique : pourquoi cela se reproduit

Un peu de contexte aide, car ce problème n’est pas une erreur utilisateur ; c’est une propriété de la façon dont les images, les tags et les pulls fonctionnent.

  • Fait 1 : Les tags d’image Docker sont des pointeurs mutables. Le registre peut déplacer :latest (ou :1.2) vers un nouveau digest à tout moment.
  • Fait 2 : Le seul identifiant immuable auquel vous pouvez vraiment vous fier pour « mêmes octets » est le digest de contenu (par ex. @sha256:…).
  • Fait 3 : L’UX originelle de Docker a fait croire que les tags étaient des versions, ce qui a entraîné toute une industrie à traiter des chaînes mutables comme des faits immuables.
  • Fait 4 : L’API v2 du Docker Registry (milieu des années 2010) a formalisé manifests et digests, ce qui permet aujourd’hui de pinner par digest sans bricolage.
  • Fait 5 : « Latest » n’est pas une version ; c’est un terme marketing. Les registres n’ont jamais promis de sémantique pour ce tag, et ils ont tenu parole.
  • Fait 6 : Les images multi-architecture ont compliqué le « même tag » : un même tag peut pointer sur des manifests différents selon l’architecture.
  • Fait 7 : Le comportement de pull a évolué selon les versions de Docker Engine, Compose et les orchestrateurs—donc « avant ça ne le faisait pas » peut être vrai.
  • Fait 8 : Les fonctionnalités modernes de la chaîne d’approvisionnement (SBOM, preuves de provenance, vérification de signatures) supposent que vous pouvez nommer un artefact immuable. Les digests sont ce nom.
  • Fait 9 : Les builds reproductibles restent rares dans le monde des conteneurs. Même avec un Dockerfile inchangé, une reconstruction produit souvent un digest différent.

Si vous cherchez un seul coupable, ce n’est pas Docker. C’est la combinaison de pointeurs mutables plus l’automatisation qui suppose que ces pointeurs sont stables.

Modes de défaillance : comment les conteneurs « se mettent à jour »

Pull-on-recreate : la mésaventure la plus commune

Un conteneur ne se transforme pas silencieusement en nouvelle image pendant qu’il tourne. Ce qui se passe est plus banal : quelque chose le recrée (Compose up, mise à jour Swarm,
rollout Kubernetes, reboot du nœud), et le runtime récupère ce que le tag pointe à ce moment précis.

Si vous avez utilisé image: vendor/app:1.4 et que le fournisseur retague 1.4 pour y inclure un correctif de sécurité, votre prochaine recréation téléchargera de nouveaux octets.
Cela peut être souhaitable. Ou catastrophique. Ce n’est certainement pas contrôlé par votre gestion des changements.

Mises à jour d’hôte qui redémarrent le démon Docker

Les mises à jour de paquets peuvent redémarrer dockerd. Selon vos politiques de redémarrage, les conteneurs reviennent. Si votre orchestrateur les recrée, les tags sont résolus de nouveau.
Si vous avez piné par digest et que le nœud a toujours l’image en local, vous obtenez les mêmes octets. Si vous comptez sur des tags et autorisez les pulls, vous jouez à la roulette.

Tâches de « nettoyage » qui suppriment des images

Le pruning d’images est sain jusqu’à ce que ce ne le soit plus. Si un nœud supprime les images inutilisées et doit plus tard recréer un conteneur, il doit de nouveau tirer l’image. C’est là que les tags mutent sous vos pieds.

Retagging côté registre et culture du force-push

Certaines équipes traitent les tags comme des branches et les réécrivent. Si votre déploiement référence des tags, vous avez accepté « ce qu’il y a sur cette branche maintenant », que vous l’ayez voulu ou non.

Blague #1 : Utiliser :latest en production, c’est comme nommer votre unique fichier de sauvegarde final_final_really_final.zip. C’est une ambiance, pas un plan.

Valeurs par défaut d’orchestrateur qui encouragent le pull

Kubernetes avec imagePullPolicy: Always parlera toujours au registre même si le digest est présent. Compose a ses propres comportements de pull selon la commande.
Les réglages existent, mais les valeurs par défaut sont optimisées pour la commodité, pas pour les postmortems d’incident.

Options de verrouillage : tags, digests et références signées

Option A : verrouiller par digest (recommandé pour « ne pas changer les octets »)

Utilisez repo/name@sha256:…. Ce digest identifie un manifest (souvent un index multi-arch), qui à son tour pointe vers les couches d’image. C’est immuable par définition.
Si vous pull par digest, vous obtenez les mêmes octets demain, la semaine prochaine et lors d’un rollback à 3 h du matin.

L’inconvénient est l’ergonomie humaine. Les digests sont longs, laids et peu parlants dans une revue de code. C’est pourquoi le verrouillage responsable utilise les deux :
un tag lisible pour l’intention, et un digest pour l’immuabilité.

Option B : verrouiller par « tags immuables » (acceptable si vous faites respecter l’immuabilité)

Si votre registre et la politique de votre organisation garantissent que des tags comme 1.2.3 ne sont jamais réécrits, alors pinner par tag sémantique peut fonctionner.
Mais comprenez ce en quoi vous croyez : un contrat social, pas de la cryptographie.

Option C : verrouiller par digest et vérifier les signatures (idéal quand c’est possible)

Les digests résolvent « mêmes octets ». Ils ne résolvent pas « octets en qui on a confiance ». C’est là que la signature et la provenance entrent en jeu : vous pouvez exiger qu’un digest
soit signé par l’identité CI et lié à un SBOM / déclaration de provenance.

Pas besoin de tout automatiser d’un coup. Commencez par le verrouillage par digest. Ajoutez l’application de signatures quand les bases ne vous réveillent plus la nuit.

Ce que signifie « de manière responsable »

Le verrouillage responsable n’est pas « ne jamais mettre à jour ». C’est « les mises à jour n’arrivent que quand on décide, et on peut expliquer quelles versions tournent ».
Vous voulez :

  • Des références immuables en production (digests).
  • Un workflow de promotion qui avance des artefacts testés.
  • Des rollbacks qui réutilisent des digests connus bons.
  • Des politiques de rétention de registre qui n’effacent pas ce que la prod utilise.

Docker Compose et Swarm : éviter les pulls surprises

Compose : rendre la recréation déterministe

Compose est souvent utilisé comme orchestrateur mais se comporte comme un outil de commodité. C’est acceptable — jusqu’à ce que vous l’exécutiez sous automatisation sur un planning.
Là, vous obtenez des surprises « pourquoi a-t-il pullé ? ».

Pour Compose, la stratégie pratique est :

  • Référencer les images par digest dans compose.yaml pour la production.
  • Utiliser explicitement docker compose pull pendant les fenêtres de maintenance ou les déploiements pilotés par CI.
  • Éviter le comportement « always pull » à moins que ce soit vraiment voulu.

Swarm : comprendre les mises à jour de service et les digests

Les services Swarm peuvent suivre des tags, mais ils enregistrent aussi les digests résolus. Vous devez rester explicite sur les mises à jour pour contrôler quand un nouveau digest est adopté.

Angle Kubernetes : même problème, autres réglages

Les utilisateurs Kubernetes aiment faire croire que le drame des tags Docker concerne les petites structures. Puis une Deployment référence myapp:stable et un nœud est drainé,
et soudain « stable » signifie « surprise ».

Les leviers dans Kubernetes sont :

  • Verrouiller par digest dans le Pod spec : image: repo/myapp@sha256:…
  • Définir imagePullPolicy intentionnellement. Pour les digests, IfNotPresent est généralement correct, mais attention à la dérive du cache des nœuds.
  • Utiliser des politiques d’admission pour rejeter les tags mutables dans certains namespaces.

Si vous pinnez par digest, un reschedule ne change pas les octets. Si vous pinnez par tag, les reschedules deviennent des déploiements.

Registres, rétention et le piège du digest

Le verrouillage par digest introduit un nouveau mode de défaillance : votre registre peut garbage collecter des manifests sans tag. Beaucoup de systèmes de rétention considèrent « sans tag » comme « sûr à supprimer ».
Mais quand vous déployez par digest, vous pouvez cesser de référencer le tag, et le registre pense que l’artefact est mort.

Le schéma responsable est : gardez un tag qui suit l’artefact promu (même si la production utilise le digest), ou configurez la rétention du registre
pour préserver les manifests référencés par des digests en production. C’est moins glamour que la signature, mais plus susceptible d’éviter votre prochain incident.

Aussi : si vous utilisez un digest d’index multi-arch, supprimer le manifest d’une architecture peut casser les pulls sur cette architecture, même si « ça marche sur amd64 ».
Le registre ne se soucie pas de vos sentiments.

Tâches pratiques : commandes, sorties et décisions (12+)

Ce sont des choses concrètes que vous pouvez faire sur un hôte aujourd’hui. Chaque tâche inclut : une commande, ce que la sortie signifie, et la décision à prendre.

Task 1: See what image a running container is actually using

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.ID}}\t{{.Status}}'
NAMES          IMAGE                 ID           STATUS
api            myco/api:1.9          7c2d1a9f01b2   Up 3 days
worker         myco/worker:1.9       1a0b9c3f9e44   Up 3 days

Signification : Vous voyez la référence configurée (souvent un tag), pas nécessairement le digest vers lequel elle a résolu.
Décision : Si ceci affiche des tags, ne présumez pas l’immuabilité. Passez à l’inspection du digest ensuite.

Task 2: Inspect the image ID and repo digests for a container

cr0x@server:~$ docker inspect api --format 'ImageID={{.Image}} RepoDigests={{json .RepoDigests}}'
ImageID=sha256:5c0c0b9d2f2d8e0e5a11c8b3c9fb4f0d0a4c3cc0f55b9c8e0d1f0f2a3b4c5d6e RepoDigests=["myco/api@sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a"]

Signification : .Image est l’ID de contenu local de l’image ; RepoDigests montre la correspondance digest/registre.
Décision : Si RepoDigests est présent, enregistrez-le et pinez-le dans la config. Si absent, vous pouvez exécuter une image sans digest de registre (construite localement ou tirée sans métadonnées de digest).

Task 3: Resolve a tag to a digest without pulling layers (manifest inspect)

cr0x@server:~$ docker buildx imagetools inspect myco/api:1.9
Name:      myco/api:1.9
MediaType: application/vnd.oci.image.index.v1+json
Digest:    sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a

Manifests:
  Name:      myco/api:1.9@sha256:4f3c2b1a...
  Platform:  linux/amd64
  Name:      myco/api:1.9@sha256:7a6b5c4d...
  Platform:  linux/arm64

Signification : Le tag pointe actuellement vers un digest d’index ; les manifests par arch diffèrent.
Décision : Pinez sur le digest d’index si vous exécutez des architectures mixtes ; pinez sur le digest spécifique à l’architecture si vous exigez une détermination absolue par plateforme.

Task 4: Confirm whether Compose will recreate containers (and thus possibly pull)

cr0x@server:~$ docker compose up -d --no-build
[+] Running 2/2
 ✔ Container stack-api-1     Started
 ✔ Container stack-worker-1  Started

Signification : « Started » suggère que des conteneurs existants ont été démarrés ; « Recreated » signifierait que de nouveaux conteneurs ont été créés.
Décision : Si vous voyez « Recreated » de façon inattendue, trouvez pourquoi (dérive de config, changement d’VAR d’environnement, modification de bind mount). La recréation est l’endroit où la dérive de tag fait mal.

Task 5: Force Compose to use locally cached images only (detect tag drift safely)

cr0x@server:~$ docker compose up -d --pull never
[+] Running 2/2
 ✔ Container stack-api-1     Started
 ✔ Container stack-worker-1  Started

Signification : Compose a refusé de pull. S’il avait besoin d’une image manquante, il aurait échoué.
Décision : Utilisez ceci dans l’automatisation de production pour empêcher « oh, il a pullé quelque chose de nouveau parce que le cache a été pruné ».

Task 6: Detect whether a tag changed since last time (compare digests)

cr0x@server:~$ docker buildx imagetools inspect myco/api:1.9 --format '{{.Digest}}'
sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a

Signification : Ceci est le digest actuel pour le tag.
Décision : Enregistrez-le dans Git (ou votre système de déploiement). S’il change sans release planifiée, traitez-le comme un événement de changement en amont.

Task 7: Pin an image by digest in Compose

cr0x@server:~$ cat compose.yaml
services:
  api:
    image: myco/api@sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a
    restart: unless-stopped

Signification : Le déploiement nomme maintenant un artefact immuable.
Décision : Vous avez échangé « mises à jour pratiques » contre « mises à jour contrôlées ». C’est le but. Construisez un pipeline de promotion pour ne pas rester bloqué indéfiniment.

Task 8: Verify what digest is running after deployment

cr0x@server:~$ docker compose ps --format json | jq -r '.[].Name + " " + .Image'
stack-api-1 myco/api@sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a

Signification : Votre référence runtime inclut désormais le digest.
Décision : Si vous voyez encore des tags ici, votre chemin de déploiement réécrit la référence. Corrigez cela avant de vous féliciter.

Task 9: Find recent image pulls and correlate with incidents

cr0x@server:~$ journalctl -u docker --since "24 hours ago" | grep -E "Pulling|Downloaded|Digest"
Jan 03 01:12:44 server dockerd[1123]: Pulling image "myco/api:1.9"
Jan 03 01:12:49 server dockerd[1123]: Downloaded newer image for myco/api:1.9
Jan 03 01:12:49 server dockerd[1123]: Digest: sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a

Signification : Les logs du démon montrent l’activité de pull de tags et le digest résolu.
Décision : Si un incident a commencé juste après un pull, supposez une dérive d’image jusqu’à preuve du contraire. Capturez le digest pour le postmortem et le rollback.

Task 10: See what images are present and whether pruning might hurt you

cr0x@server:~$ docker images --digests --format 'table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.Size}}' | head
REPOSITORY   TAG   DIGEST                                                                    ID            SIZE
myco/api     1.9   sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a   5c0c0b9d2f2d  312MB
myco/worker  1.9   sha256:1a2b3c4d5e6f...                                                    9aa0bb11cc22  188MB

Signification : Les digests affichés ici sont ce que votre runtime peut utiliser s’il n’a pas besoin de pull.
Décision : Si vos nœuds prunent régulièrement, considérez cela comme « nous allons puller à nouveau », et pinez par digest pour éviter la dérive de tag.

Task 11: Dry-run a pull to see if the tag would change (without deploying)

cr0x@server:~$ docker pull myco/api:1.9
1.9: Pulling from myco/api
Digest: sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a
Status: Image is up to date for myco/api:1.9
docker.io/myco/api:1.9

Signification : « Up to date » signifie que le tag résout actuellement vers ce que vous avez déjà localement.
Décision : Si le digest change ici, arrêtez et traitez cela comme une nouvelle release. Ne laissez pas un reboot de maintenance devenir un déploiement non révisé.

Task 12: Prove to yourself that tags are mutable (the uncomfortable demo)

cr0x@server:~$ docker image inspect myco/api:1.9 --format '{{index .RepoDigests 0}}'
myco/api@sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a

Signification : Ceci est ce à quoi 1.9 correspond à l’instant. Ce n’est pas une garantie pour la semaine prochaine.
Décision : Si vous tenez à des redémarrages déterministes, arrêtez de déployer des tags en production. Point final.

Task 13: Identify containers likely to change on next restart (using tags)

cr0x@server:~$ docker ps --format '{{.Names}} {{.Image}}' | grep -v '@sha256:' | head
api myco/api:1.9
nginx nginx:latest

Signification : Tout ce qui n’affiche pas @sha256: est basé sur un tag et donc mutable.
Décision : Mettez-les sur une liste de remédiation. Commencez par les services exposés à Internet et les chemins d’authentification. Vous savez, ceux qui ruinent les week-ends.

Task 14: Check restart policies that will amplify daemon restarts

cr0x@server:~$ docker inspect api --format 'Name={{.Name}} RestartPolicy={{.HostConfig.RestartPolicy.Name}}'
Name=/api RestartPolicy=unless-stopped

Signification : Les politiques de redémarrage déterminent ce qui revient quand le démon redémarre.
Décision : Conservez les politiques de redémarrage, mais associez-les au pinning par digest. « Always restart » + « tag mutable » est la recette des redeploys surprises.

Task 15: Validate registry retention risk (list local digests in use)

cr0x@server:~$ docker ps -q | xargs -n1 docker inspect --format '{{.Name}} {{json .RepoDigests}}' | head -n 3
/api ["myco/api@sha256:9d3a0c2e4d8b2b8a9f3f2c1d0e9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a"]
/worker ["myco/worker@sha256:1a2b3c4d5e6f..."]
/metrics ["prom/prometheus@sha256:7e6d5c4b3a2f..."]

Signification : Ceci est l’ensemble réel des artefacts dont dépend la production.
Décision : Assurez-vous que votre registre conserve ces digests. Si votre politique de rétention supprime les « non tagués », veillez à ce que ces digests restent tagués ou exemptés.

Task 16: Catch “helpful” automation like Watchtower (or equivalents)

cr0x@server:~$ docker ps --format '{{.Names}} {{.Image}}' | grep -i watchtower
watchtower containrrr/watchtower:1.7.1

Signification : Un auto-updater tourne sur l’hôte.
Décision : Soit supprimez-le de la production, soit restreignez-le fortement, soit rendez-le digest-aware avec une porte d’approbation. Les mises à jour non contrôlées sont l’inverse du SRE.

Playbook de diagnostic rapide

Quand vous suspectez que « les conteneurs ont changé après des mises à jour », ne commencez pas par arguer des tags dans un fil de discussion. Vérifiez rapidement les faits et isolez le goulot :
s’agit-il d’une dérive d’image, d’une dérive de configuration ou d’une pression sur les ressources ?

Premier point : le digest en cours a-t-il changé ?

  • Vérifiez les RepoDigests du conteneur via docker inspect.
  • Comparez avec votre dernier digest connu bon (depuis Git, journaux de changement ou notes d’incident).
  • Si les digests diffèrent, traitez cela comme un déploiement. Lancez un rollback ou une correction en avant.

Second point : qu’est-ce qui a déclenché le redémarrage/recréation ?

  • Consultez journalctl -u docker pour les redémarrages du démon et les pulls.
  • Vérifiez les événements Compose/Swarm/Kubernetes pour les reschedules et recréations.
  • Recherchez les logs de mise à jour de paquets qui ont redémarré docker/containerd.

Troisième point : si le digest n’a pas changé, qu’est-ce qui a changé d’autre ?

  • Dérive de config : vars d’environnement, fichiers montés, secrets, DNS, certificats.
  • Changements du noyau/hôte : comportement des cgroups, iptables/nft, MTU, changements du driver de stockage.
  • Contraintes de ressources : disque plein, épuisement d’inodes, throttling CPU.

Quatrième point : décider de geler ou d’avancer

  • Si vous exécutez des tags : geler en passant au digest, puis enquêter en toute sécurité sur le mouvement du tag en amont.
  • Si vous êtes déjà pinés : votre incident n’est probablement pas une « mise à jour mystère ». Concentrez-vous sur ce qui compte.

Erreurs courantes : symptôme → cause → correction

1) « Rien n’a changé » mais le comportement a changé après un reboot

Symptôme : Après un reboot de nœud, les conteneurs se comportent différemment ; les bannières de version ne correspondent pas aux attentes.

Cause : Les conteneurs ont été recréés et ont pullé un digest différent parce que le tag a bougé, ou l’image locale a été prunée.

Correction : Pinez par digest dans les configs de production. Utilisez docker compose up --pull never dans l’automatisation. Arrêtez de pruner sans comprendre les conséquences sur les redeploys.

2) Le rollback n’a pas rollbacké

Symptôme : Vous avez « rollbacké » vers :stable mais le bug persiste, ou évolue.

Cause : Vous avez rollbacké vers un tag qui avait déjà avancé ; les tags ne sont pas des snapshots.

Correction : Rollbackez vers un digest enregistré. Stockez les digests par release dans Git ou votre outil de déploiement.

3) Seuls les nœuds ARM sont cassés

Symptôme : amd64 est OK ; arm64 fait des crashloops après reschedule.

Cause : L’index multi-arch a changé ou le manifest d’une arch a été supprimé par la rétention / GC.

Correction : Pinez sur le digest d’index multi-arch et protégez-le du GC ; vérifiez que chaque manifest d’architecture existe avant la promotion.

4) « On a piné » mais on a quand même eu de la dérive

Symptôme : Le fichier Compose montre un digest, pourtant les nœuds pullent autre chose.

Cause : Un script wrapper ou un système de templating a réécrit la référence d’image en tag à l’exécution, ou un autre fichier Compose a été utilisé.

Correction : Affichez la config effective dans CI/CD, et assertz la présence de @sha256:. Traitez la « config rendue » comme l’artefact de déploiement.

5) La production ne peut plus puller le digest piny

Symptôme : Un nouveau nœud échoue avec « manifest unknown » pour un digest que vous avez déployé le mois dernier.

Cause : La rétention du registre a supprimé le manifest parce qu’il était sans tag, ou un repo a été nettoyé agressivement.

Correction : Conservez des « tags de release » pointant sur les digests promus, ou configurez des exemptions de rétention pour les digests utilisés en production. Auditez la rétention régulièrement.

6) L’équipe sécurité déteste le pinning

Symptôme : « Vous avez pingé les images, donc vous ne patcherez jamais. »

Cause : Le pinning a été adopté comme gel, et non comme workflow de promotion contrôlé.

Correction : Pinez en production, mais mettez à jour via promotion pilotée par CI. Ajoutez des rebuilds planifiés, des gates de scan et des rollouts explicites.

Trois mini-récits d’entreprise issus du terrain

Mini-récit 1 : un incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne exécutait Docker Compose sur quelques VM puissantes. Elle utilisait myco/api:2.3 partout et supposait « 2.3 veut dire 2.3 ».
L’équipe fournisseur de l’image avait une hypothèse différente : elle traitait 2.3 comme une ligne mineure et la reconstruisait régulièrement avec des images de base patchées.

Un mardi, l’équipe infra a déployé des correctifs de sécurité hôte. Docker a redémarré. Les conteneurs ont redémarré. L’API est revenue — en grande partie.
Le symptôme était étrange : un sous-ensemble de clients obtenait des 401, puis les retries fonctionnaient, puis échouaient à nouveau. On aurait dit un throttling ou un cache de tokens.

L’astreinte a fait le diagnostic habituel : vérifier les logs de l’app, Redis, le load balancer. Les courbes indiquaient une latence élevée, pas des pannes nettes.
Pendant ce temps, les clients escaladaient parce que « problèmes d’auth intermittents » sont des bugs qui font fondre la confiance.

La percée fut embarrassante de simplicité. Quelqu’un a comparé le digest en cours sur un hôte avec celui d’un autre et a constaté qu’ils étaient différents.
La moitié du parc avait pullé une nouvelle reconstruction de 2.3 ; l’autre moitié avait encore le digest précédent en cache et n’avait pas eu besoin de pull.
Le cluster était devenu un canari hétérogène non voulu.

Le résoudre a pris moins de temps qu’à l’expliquer : ils ont piné la production sur un digest, ont roulé tout le parc vers un artefact connu, et ont stabilisé la situation.
La correction à long terme fut culturelle : « tag = version » est devenu une hypothèse interdite sauf si le registre faisait respecter l’immuabilité.

Mini-récit 2 : une optimisation qui s’est retournée contre eux

Une autre entreprise avait un objectif sensé : réduire l’usage disque sur les nœuds. Leurs conteneurs généraient beaucoup de churn d’images, et l’équipe stockage se plaignait
des volumes root gonflés et des sauvegardes lentes. Ils ont donc ajouté un nettoyage nocturne agressif : docker system prune -af sur chaque nœud.

Au début, ça a marché. Les alertes disque ont disparu. Tout le monde s’est tapé dans la main et est reparti ignorer la planification de capacité. Puis une dépendance discrète a été mise à jour :
un nœud a rebooté après un patch du noyau, et quelques services ont été recréés. Ces services étaient basés sur des tags, et maintenant toutes les images devaient être téléchargées à nouveau.

Le registre était légèrement limité en taux, et le chemin réseau vers lui n’était pas aussi large qu’on le croyait. Les redémarrages graduels sont devenus des redémarrages lents.
Plus important, un tag en amont avait évolué vers un digest incluant un changement de librairie avec des defaults TLS différents. Le service n’a pas planté ; il n’a juste plus pu parler à un upstream qui utilisait des chiffrements anciens.

L’incident n’était pas « le nettoyage disque a cassé la prod ». L’incident était « le nettoyage disque a transformé chaque restart en redeploy ».
La correction fut de séparer les préoccupations : garder le nettoyage, mais piner les images par digest et conserver un petit cache des digests de production connus sur chaque nœud.
Ils ont aussi cessé de prétendre que les registres sont disponibles à vitesse infinie.

Blague #2 : docker system prune -af dans un cron est l’équivalent opérationnel de se raser avec une tronçonneuse. C’est rapide — jusqu’à ce que ça ne le soit plus.

Mini-récit 3 : une pratique ennuyeuse mais correcte qui a sauvé la situation

Une entreprise régulée exécutait des centaines de conteneurs, et leur contrôle de changement était… appelons-le « enthousiaste ». Les ingénieurs râlaient pour la paperasse,
mais le processus de release avait une fonctionnalité sous-estimée : chaque artefact de déploiement incluait les digests d’images résolus, stockés avec la config.

Un matin, un service critique a commencé à lancer des segfaults après qu’un échec de nœud ait déclenché un rescheduling. Tout le monde soupçonnait
« mauvais hardware » ou « regression du noyau ». La signature du crash se trouvait dans une dépendance native, donc les accusations ont commencé.

L’SRE d’astreinte a fait quelque chose de profondément peu sexy : comparer les digests dans l’enregistrement de déploiement aux digests tournant sur le nouveau nœud.
Ils ne correspondaient pas. Le nœud avait pullé un tag qui avait avancé pendant la nuit parce qu’un pipeline de build avait republié « stable » après des tests.

Parce que l’organisation avait enregistré le digest, le rollback a été immédiat et précis : redéployer le digest précédent connu bon. Pas de devinettes. Pas de « essayez le tag de la semaine dernière ».
Le service a rapidement récupéré, et l’incident a été reclassé de « panique infrastructure » à « bug de contrôle de release ».

Le postmortem n’était pas glamour non plus : imposer le pinning par digest, cesser d’utiliser « stable » en production, et exiger des approbations pour retagging.
Les pratiques ennuyeuses n’ont pas de conférences, mais elles vous rendent le sommeil.

Listes de contrôle / plan étape par étape

Étape par étape : passer un déploiement Compose des tags aux digests (sans chaos)

  1. Inventoriez ce qui tourne. Rassemblez les noms de conteneurs, images courantes et digests de repo en cours.
  2. Résolvez les tags en digests. Pour chaque tag utilisé en production, enregistrez le digest auquel il correspond actuellement.
  3. Mettre à jour les fichiers Compose. Remplacez repo:tag par repo@sha256:….
  4. Déployez avec pulls désactivés. Utilisez docker compose up -d --pull never pour éviter d’adopter accidentellement un tag déplacé.
  5. Vérifiez la référence en cours. Confirmez que @sha256: apparaît dans docker compose ps et docker inspect.
  6. Enregistrez les digests dans Git/journaux de changement. Facilitez la réponse à « qu’est-ce qui tourne ? » sans SSHer en prod.
  7. Corrigez votre politique de rétention. Assurez-vous que les digests promus ne sont pas garbage collectés.
  8. Construisez un workflow de promotion. Le CI build une fois, tague immuablement, et « promeut » en déplaçant un tag de release ou en mettant à jour la config avec un digest.

Checklist production : avant de patcher les hôtes

  • Confirmer que la production utilise des digests, pas des tags.
  • S’assurer que les nœuds ont assez de disque pour ne pas être forcés à un prune d’urgence.
  • Vérifier la connectivité et les credentials vers le registre depuis les nœuds.
  • Désactiver les auto-updaters sur les nœuds de production (ou les restreindre aux stacks non critiques).
  • Planifier le redémarrage du démon : savoir quels conteneurs redémarreront et dans quel ordre.

Checklist release : avant de promouvoir une nouvelle image en production

  • Promouvoir un digest spécifique qui a passé les tests ; ne pas promouvoir un tag susceptible de bouger.
  • Vérifier que le digest existe pour les architectures requises.
  • Scanner l’image et enregistrer SBOM/provenance si votre organisation l’exige.
  • Garder un digest de rollback explicite prêt (et testé).

Principe opérationnel à imprimer

paraphrased idea de Gene Kim : rendre les changements plus petits et plus contrôlés réduit le risque et accélère la récupération.
Verrouiller par digest rend les redémarrages ennuyeux ; la promotion contrôlée rend les mises à jour délibérées.

FAQ

1) Si les conteneurs ne changent pas à l’exécution, pourquoi mon appli a-t-elle changé « sans déploiement » ?

Parce que quelque chose a recréé le conteneur (redémarrage du démon, reboot du nœud, reschedule orchestrateur), et votre config référencait un tag mutable.
Cette recréation a implicitement effectué un déploiement.

2) Le pinning par digest suffit-il pour la sécurité ?

C’est suffisant pour la déterminisme. La sécurité est distincte : vous avez toujours besoin de rebuilds ponctuels, de scans et d’un chemin de promotion contrôlé.
Les digests aident la sécurité en clarifiant exactement quel artefact vous avez scanné et approuvé.

3) Puis-je continuer à utiliser des tags en dev ?

Oui. En dev, la commodité compte. Utilisez des tags comme :latest si ça accélère l’itération. Mais rendez la frontière de promotion stricte :
la production doit accepter des digests (ou des tags immuables imposés) uniquement.

4) Quel est le problème avec les tags :1.2.3 ?

Rien — si votre registre et la politique d’organisation font respecter l’immuabilité, et si vous l’auditez. Sinon, vous faites confiance à ce que personne ne retagge,
ne force-pushe ou ne « reconstruise 1.2.3 avec un correctif rapide ».

5) Le pinning par digest va-t-il casser le patching automatique des images de base ?

Cela change les choses de « silencieux » à « explicite ». Vous devriez rebuild et promouvoir de nouveaux digests selon un calendrier. C’est plus sain que de se réveiller avec une surprise.

6) Comment rollbacker en toute sécurité ?

Rollbackez en déployant le digest connu bon précédent, pas en « revenant » à un nom de tag. Enregistrez les digests par release pour que le rollback soit un changement, pas une chasse aux indices.

7) Compose pull toujours lors d’un up ?

Pas toujours. Le comportement dépend de la commande et des flags. Si vous voulez prévenir les pulls en production, utilisez --pull never.
Si vous voulez rafraîchir délibérément, exécutez docker compose pull comme étape séparée.

8) Et Kubernetes — dois-je toujours utiliser des digests là aussi ?

Pour la production, oui, sauf si vous avez une immuabilité stricte des tags et du contrôle d’admission. Le pinning par digest évite que « reschedule = redeploy ».
Si vous utilisez des tags, considérez chaque événement de nœud comme un rollout potentiel.

9) Pourquoi mon digest piny a-t-il échoué à puller sur un nouveau nœud des mois plus tard ?

La rétention du registre ou le garbage collection a probablement supprimé le manifest (souvent parce qu’il était sans tag). Corrigez les politiques de rétention et gardez des tags de release
pointant sur les digests promus.

10) Doit-on piner l’index digest ou le digest spécifique à l’architecture ?

Si vous exécutez des architectures mixtes, pinez l’index digest pour que chaque nœud obtienne le manifest de plateforme correct.
Si vous exécutez une seule architecture et voulez la plus grande détermination, pinez le digest spécifique à la plateforme.

Conclusion : prochaines étapes réalisables cette semaine

Si vous ne retenez qu’une chose : les tags sont des noms pratiques, pas des versions immuables. Si votre configuration de production référence des tags, votre prochaine mise à jour d’hôte peut devenir un déploiement.
Ce n’est pas du « DevOps ». C’est du jeu.

Prochaines étapes pratiques :

  • Inventoriez les conteneurs de production qui tournent encore avec des images basées sur des tags (sans @sha256:).
  • Choisissez un service critique et pinez-le par digest dans sa config de déploiement.
  • Mettez à jour votre automatisation pour déployer avec pulls désactivés par défaut (--pull never) et promouvoir explicitement les nouveaux digests.
  • Auditez la rétention du registre pour que les digests pinés restent pullables pendant des mois, pas des jours.
  • Enregistrez les digests par release pour que le rollback soit un changement, pas une chasse au trésor.

Vous n’avez pas besoin d’un outillage parfait de chaîne d’approvisionnement pour arrêter les mises à jour surprises. Vous avez besoin de déterminisme, de discipline et d’un refus de traiter des pointeurs mutables comme des contrats.

← Précédent
Erreur d’email « 550 rejected » : ce que ça signifie réellement et comment débloquer
Suivant →
Docker « Impossible de se connecter au daemon Docker » : corrections qui fonctionnent réellement

Laisser un commentaire