Certaines incidents de production commencent par un incendie. La plupart commencent par un haussement d’épaules : « C’est bizarre… ça marchait sur mon laptop. » Vous expédiez un conteneur, il passe la CI, il fonctionne en staging, puis la production se comporte comme si elle s’était réveillée dans un univers parallèle.
En général, c’est bien le cas. Pas parce que Docker est peu fiable. Parce que les humains le sont. Plus précisément : des humains traitant les tags d’image comme s’ils étaient des numéros de version alors que ce sont en réalité des surnoms. Les surnoms mentent.
La règle unique : ne déployez jamais de tags flottants
Voici la règle qui met fin à la plupart des drames du type « ça marche sur ma machine » :
Déployez par digest, pas par tag mutable.
Pas « latest ». Pas « main ». Pas même « 1.2 ». En production (et idéalement en staging aussi), vous déployez des identifiants immutables : image@sha256:….
Qu’est-ce qu’un « tag flottant » ?
Tout tag qui peut bouger sans que vous le remarquiez. Ce qui est… la majorité des tags.
latestest flottant par conception.main,stable,prod,candidatesont flottants parce que des humains les déplacent.1.2flotte lorsque vous reconstruisez le même tag après avoir patché une image de base ou « juste corrigé le Dockerfile ».- Même
1.2.3flotte si votre registre autorise la réécriture de tag et que votre processus ne l’empêche pas.
Quel est l’effet pratique de déployer par digest ?
Cela rend la réponse à « qu’est-ce qui tourne » possible en une ligne. Cela rend les rollbacks déterministes. Cela élimine la classe d’incidents où le même YAML Kubernetes déploie des choses différentes selon les jours.
Il y a aussi un aspect culturel : cela vous force à traiter « build » et « deploy » comme des événements séparés. La construction produit un artefact. Le déploiement sélectionne cet artefact. Si votre étape de « deploy » déclenche une reconstruction, vous ne déployez pas des artefacts ; vous jouez à la roulette avec un compilateur.
Blague sèche #1 : Le tag « latest » ressemble au lait dans le frigo du bureau : techniquement étiqueté, émotionnellement dangereux, et jamais ce que vous pensez.
Quand est-il acceptable d’utiliser des tags ?
Les tags sont utiles pour les humains et les workflows :
- En CI : « cette build a produit le tag
pr-1847. » - Dans les pipelines de promotion : « déplacer
candidatepour pointer sur le digest X. » - En développement : « exécuter
:latestlocalement si vous aimez les surprises. »
Mais la production doit tourner sur des digests. Si vous devez absolument conserver des tags dans les manifests pour l’ergonomie, alors imposez l’immutabilité des tags au registre et enregistrez quand même le digest que vous avez déployé. Sinon, vous faites de nouveau confiance à un surnom.
Pourquoi la dérive arrive (même avec de « bonnes » pratiques)
Le comportement de pull n’est pas un jugement moral
Docker et Kubernetes n’essaient pas de vous piéger. Ils cherchent à être efficaces. Si une image du même nom existe localement, le runtime peut ne pas retélécharger. Si vous avez plusieurs nœuds, chaque nœud a son propre cache local. Si votre politique est « if-not-present », la dérive est essentiellement une fonctionnalité.
Les reconstructions en CI signifient que vous expédiez une cible mouvante
Un anti-pattern classique ressemble à ceci :
- Les développeurs fusionnent vers main.
- La CI construit
myapp:latestet le pousse. - La CD déploie
myapp:latest. - Un hotfix déclenche une autre build qui pousse aussi
myapp:latest. - Certaines machines tirent, d’autres non. Ou elles tirent à des moments différents.
Vous avez maintenant un déploiement canari non planifié distribué par le comportement du cache. Ce ne sera pas le canari que vous vouliez.
Les images de base mutent et souvent vous le voulez
Le patching de sécurité est réel. Beaucoup d’équipes reconstruisent les images pour intégrer des couches de base corrigées. C’est bien. Mais si vous reconstruisez et écrasez le même tag, vous avez transformé une bonne hygiène de sécurité en un problème d’ambiguïté de déploiement.
Les registres, proxys et miroirs « utiles » s’en mêlent
Si vous avez un cache de registre, un proxy pull-through ou un miroir, vous ajoutez une couche qui peut renvoyer des résultats obsolètes si elle est mal configurée. Votre laptop peut parler à Docker Hub. La production peut parler à un miroir d’entreprise avec sa propre logique de rafraîchissement. Même tag. Des octets différents. Bon courage pour votre après-midi de debug.
Faits et historique qui expliquent pourquoi cela revient
- Docker Hub a popularisé « latest » comme choix UX par défaut. Les premiers workflows Docker privilégiaient la commodité plutôt que la traçabilité stricte.
- Le stockage adressable par contenu précède les conteneurs. Le modèle de Git (hash identifie le contenu) est plus ancien et conceptuellement similaire aux digests d’images ; les tags sont comme des noms de branches.
- L’OCI a standardisé les formats d’images. Ce que vous tirez aujourd’hui respecte largement une spécification ouverte, ce qui explique l’interopérabilité des runtimes et registres.
- Les images multi-arch ont changé le sens de « l’image ». Un tag peut pointer vers un index qui choisit des manifests différents selon l’architecture du nœud.
- Kubernetes a choisi par défaut ImagePullPolicy selon les tags. Si vous utilisez
:latest, la valeur par défaut est Always ; sinon elle est souvent IfNotPresent—subtil et fréquemment mal compris. - Le cache de couches est une optimisation avec des conséquences. Il réduit le réseau et accélère les déploiements, mais il cache aussi les mises à jour de tag sauf si vous forcez le pull.
- La sécurité de la chaîne d’approvisionnement a popularisé l’épinglage des digests. Provenance, signatures, SBOM—ces pratiques traitent le digest comme ancre.
- « Infrastructure immuable » devait rendre les déploiements ennuyeux. Les conteneurs ont facilité le packaging ; les digests facilitent la connaissance exacte de ce que vous avez packagé.
Une idée paraphrasée et orientée fiabilité qui mérite d’être tatouée sur votre pipeline de déploiement :
Werner Vogels (idée paraphrasée) : considérez tout comme jetable et concevez pour la défaillance ; vous passerez moins de temps à prier et plus de temps à restaurer le service.
Trois mini-histoires d’entreprise du terrain
Mini-histoire n°1 : L’incident causé par une mauvaise hypothèse
Une entreprise SaaS de taille moyenne avait une configuration propre : Kubernetes en production, GitOps pour les manifests, un registre de conteneurs et un contrôleur CD. Ils étaient fiers de leur discipline. Puis ils ont eu une panne de connexion qui n’a touché qu’environ un tiers des utilisateurs. Pas tout le monde. Pas toutes les régions. Juste assez pour gâcher le dîner de l’on-call.
Le premier indice était que la signature d’erreur variait selon le pod. Certains pods lançaient une exception à propos d’un chemin de bundle CA manquant. D’autres allaient bien. Le manifest de déploiement indiquait myorg/auth-service:1.8. Tout le monde supposait que 1.8 voulait dire une seule chose. C’était la mauvaise hypothèse.
Ils avaient reconstruit :1.8 pour intégrer des CVE de l’image de base une semaine plus tôt. Le registre autorisait la réécriture de tag. Certains nœuds avaient encore les anciennes couches en cache ; d’autres ont tiré l’image reconstruite lors d’un churn de routine. Kubernetes était réglé sur IfNotPresent. L’état final était un déploiement en « split-brain » : deux images différentes sous un même tag, en fonctionnement simultané.
La correction a été rapide une fois le diagnostic posé : épingler par digest dans le déploiement, forcer un rollout, puis rendre les tags immuables pour tout ce qui ressemblait à une release. Le postmortem a été franc : « Nous avons traité un tag comme une identité. » La semaine suivante, ils ont audité chaque manifest de production pour les tags flottants et les ont remplacés par des digests, tout en conservant un tag convivial dans la sortie CI.
Mini-histoire n°2 : L’optimisation qui s’est retournée contre eux
Une autre organisation gérait une API à haut débit et en avait assez des montées en charge lentes. Tirer des images pendant l’autoscaling provoquait des démarrages à froid. Ils ont donc introduit un miroir de registre à l’intérieur du VPC et un cache agressif sur les nœuds. Ça a marché : les scale-outs sont devenus beaucoup plus rapides et la sortie réseau a chuté.
Puis ils ont déployé un correctif critique et il n’a pas « pris » partout. Certains nœuds ont continué à servir l’ancien comportement pendant des heures. Le miroir respectait les en-têtes de cache et avait un intervalle de rafraîchissement ; les nœuds préféraient les images mises en cache localement pour éviter les pulls. L’équipe avait construit la vitesse en augmentant la staleness.
Le problème n’était pas l’existence du cache. Le problème était d’avoir laissé le cache décider de la correction. Leur déploiement référençait api-gateway:stable, mis à jour par la CI. Le tag a bougé, mais le cache s’en fichait. Le contrôleur de déploiement pensait avoir déployé. Ce fut le cas, juste pas les bits qu’ils voulaient.
Ils ont conservé le miroir, parce que les scale-outs rapides valent la peine. Mais ils ont changé le contrat : la promotion déplaçait un tag vers un digest, les déploiements utilisaient le digest, et les caches sont devenus sûrs parce que l’identifiant a cessé de bouger. Ils ont aussi ajouté une règle simple : « si le manifest prod contient un tag avec deux-points sans @sha256, échoue la PR. » Ennuyeux. Efficace.
Mini-histoire n°3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise liée à la finance (du genre qui se fait auditer pour le plaisir) avait un processus de release peu glamour. Chaque build produisait une image, la signait, enregistrait le digest et stockait ce digest avec le ticket de changement. Les environnements faisaient la promotion par digest uniquement. Aucune exception. Les développeurs se plaignaient que c’était lent et « trop entreprise ».
Un vendredi, une vulnérabilité runtime est apparue avec une vague de titres alarmants. La sécurité a demandé un patch immédiat. L’équipe a reconstruit les images de base, reconstruit les services et poussé de nouvelles images. Puis, au milieu de la ruée, un autre changement s’est glissé accidentellement dans le contexte de build d’un service — un tweak de config non relu. Dans beaucoup d’équipes, c’est comme ça que l’on obtient une panne de week-end.
Voici ce qui les a sauvés : la promotion exigeait une sélection explicite du digest. Le build non relu a produit un digest qui ne correspondait pas au ticket de changement approuvé. Le pipeline a refusé de le promouvoir. L’équipe a patché la vulnérabilité en utilisant les bons digests et a empêché la config accidentelle d’atteindre la prod.
Personne n’a eu d’applaudissements. Il n’y a pas eu de debug héroïque. Le système s’est simplement refusé à être malin. C’est l’objectif.
Blague sèche #2 : Si votre processus de déploiement a besoin d’un héros, votre processus est essentiellement une téléréalité avec un éclairage plus mauvais.
Tâches pratiques : commandes, sorties et décisions
Cette section est la partie « j’ai un terminal et un problème ». Chaque tâche inclut une commande, un extrait de sortie réaliste, ce que cela signifie, et la décision que vous prenez à partir de là.
Task 1: See what image a running container is actually using (Docker)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.ID}}'
NAMES IMAGE ID
auth-service registry.local/auth:1.8 7c1d2e9a0b53
billing-worker registry.local/billing:prod 3a9b6d1c4f21
Ce que cela signifie : Vous voyez des tags, pas des digests. C’est un indice, pas une preuve. Un tag peut avoir bougé depuis le démarrage du conteneur.
Décision : Inspecter le conteneur pour trouver l’identifiant d’image immuable / le mapping de digest.
Task 2: Inspect container image ID and repo digests (Docker)
cr0x@server:~$ docker inspect auth-service --format '{{.Image}} {{json .RepoDigests}}'
sha256:0e3d2b4f1e1b6d0f1b6b8c9a3f2a1d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4 ["registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5"]
Ce que cela signifie : Le conteneur en cours d’exécution référence un digest spécifique. C’est ce digest que vous devez enregistrer et comparer entre environnements.
Décision : Si le manifest déploie par tag, changez-le pour déployer par ce digest (ou celui approuvé).
Task 3: Check whether a tag currently points to a different digest (remote truth)
cr0x@server:~$ docker pull registry.local/auth:1.8
1.8: Pulling from auth
Digest: sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Status: Image is up to date for registry.local/auth:1.8
Ce que cela signifie : Le digest actuel du registre pour :1.8 est affiché. S’il ne correspond pas au digest de la Tâche 2, le tag a bougé.
Décision : Considérez cela comme un échec d’hygiène de release. Arrêtez de déployer par ce tag ; épinglez le digest et investigatez pourquoi le tag a changé.
Task 4: List local images and see repo digests (catch “same tag, different digests”)
cr0x@server:~$ docker images --digests --format 'table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}}'
REPOSITORY TAG DIGEST IMAGE ID CREATED SINCE
registry.local/auth 1.8 sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e 0e3d2b4f1e1b 9 days ago
registry.local/auth 1.8 <none> 9a1b2c3d4e5f 15 days ago
Ce que cela signifie : Vous pouvez avoir plusieurs images locales qui étaient liées au même tag. L’ancienne peut être maintenant sans tag.
Décision : Ne comptez pas sur la présence d’un tag dans le cache ; utilisez des digests dans les déploiements, et nettoyez les anciennes images si l’espace disque est un facteur.
Task 5: Force a clean pull to eliminate cache ambiguity (Docker)
cr0x@server:~$ docker rmi registry.local/auth:1.8
Untagged: registry.local/auth:1.8
Deleted: sha256:0e3d2b4f1e1b6d0f1b6b8c9a3f2a1d0c9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4
cr0x@server:~$ docker pull registry.local/auth:1.8
1.8: Pulling from auth
Digest: sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Status: Downloaded newer image for registry.local/auth:1.8
Ce que cela signifie : Maintenant le tag local résout vers le contenu actuel du registre.
Décision : Si cela change le comportement, vous avez prouvé une dérive due au cache. Corrigez le processus, pas le nœud.
Task 6: In Kubernetes, see what image the Deployment claims vs what pods actually run
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
registry.local/auth:1.8
cr0x@server:~$ kubectl -n prod get pods -l app=auth-service -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[0].imageID}{"\n"}{end}'
auth-service-6d9df7c6d9-7h5qv docker-pullable://registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5
auth-service-6d9df7c6d9-km2nw docker-pullable://registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Ce que cela signifie : Même Deployment tag, digests d’images différents par pod. C’est de la dérive.
Décision : Épinglez le Deployment sur un unique digest et procédez au rollout. Ensuite corrigez le pipeline pour empêcher la réécriture de tag.
Task 7: Check ImagePullPolicy (Kubernetes) to understand cache behavior
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].imagePullPolicy}{"\n"}'
IfNotPresent
Ce que cela signifie : Les nœuds peuvent ne pas tirer même si le tag a changé, selon l’état du cache.
Décision : Ne « fixez » pas cela en mettant Always partout comme contrôle principal. Corrigez l’identifiant (digest). Utilisez Always de façon sélective quand vous voulez vraiment suivre un tag en dev.
Task 8: Confirm the rollout revision and correlate it to an image change
cr0x@server:~$ kubectl -n prod rollout history deploy/auth-service
deployment.apps/auth-service
REVISION CHANGE-CAUSE
12 image updated to registry.local/auth:1.8
13 configmap reload
Ce que cela signifie : L’historique vous dit « image updated » mais pas le digest à moins que vous l’ayez enregistré explicitement.
Décision : Commencez à annoter les déploiements avec le digest (ou commit SHA + digest) au moment du déploiement.
Task 9: Inspect a registry tag’s digest via manifest inspection (no guessing)
cr0x@server:~$ docker manifest inspect registry.local/auth:1.8 | head -n 12
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e",
"platform": {
"architecture": "amd64",
Ce que cela signifie : Le tag pointe vers un index (multi-arch). Vous pouvez voir les digests par plateforme.
Décision : Épinglez le digest de l’index si vous voulez une référence unique toutes architectures confondues, ou épinglez les digests spécifiques par plateforme si vous avez besoin d’un contrôle déterministe par arch.
Task 10: Find which node is running which digest (targeted remediation)
cr0x@server:~$ kubectl -n prod get pods -l app=auth-service -o wide
NAME READY STATUS RESTARTS AGE IP NODE
auth-service-6d9df7c6d9-7h5qv 1/1 Running 0 2h 10.20.1.14 ip-10-0-4-21
auth-service-6d9df7c6d9-km2nw 1/1 Running 0 2h 10.20.2.33 ip-10-0-6-18
cr0x@server:~$ kubectl -n prod get pod auth-service-6d9df7c6d9-7h5qv -o jsonpath='{.status.containerStatuses[0].imageID}{"\n"}'
docker-pullable://registry.local/auth@sha256:8b4c6a3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5
Ce que cela signifie : Vous pouvez cibler la dérive jusqu’aux nœuds, pas seulement aux pods.
Décision : Si vous avez besoin d’une action immédiate de confinement, cordonnez/vidangez le nœud fautif après avoir corrigé le manifest ; sinon cela se reproduira.
Task 11: Verify what containerd thinks is present (runtime-level truth)
cr0x@server:~$ sudo ctr -n k8s.io images ls | grep registry.local/auth
registry.local/auth:1.8 application/vnd.oci.image.index.v1+json sha256:1111aaaabbbb2222cccc3333dddd4444eeee5555ffff6666777788889999aaaa 245.3 MiB linux/amd64,linux/arm64
registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e application/vnd.oci.image.manifest.v1+json sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e 84.1 MiB linux/amd64
Ce que cela signifie : Le runtime peut stocker à la fois des références de tag et des références de digest. La liste montre ce qui est mis en cache et pour quelles plateformes.
Décision : Si les pods ne tirent pas comme prévu, confirmez si le digest est déjà présent et si votre politique de pull et le comportement du runtime correspondent à vos attentes.
Task 12: Confirm a rollout actually replaced pods (not just updated a spec)
cr0x@server:~$ kubectl -n prod rollout status deploy/auth-service
deployment "auth-service" successfully rolled out
cr0x@server:~$ kubectl -n prod get rs -l app=auth-service --sort-by=.metadata.creationTimestamp
NAME DESIRED CURRENT READY AGE
auth-service-6d9df7c6d9 6 6 6 2h
auth-service-5c7bbf99b8 0 0 0 7d
Ce que cela signifie : Vous avez un seul ReplicaSet actif. Bien. Si plusieurs sont actifs de façon inattendue, vous pourriez avoir des rollouts partiels ou des terminaisons bloquées.
Décision : Si la dérive persiste, le problème est probablement la référence d’image elle-même (tag mouvant) ou le cache des nœuds combiné à des identifiants mutables.
Task 13: Detect if your manifest contains a floating tag (cheap guardrail)
cr0x@server:~$ grep -RIn 'image: .*:[^ @]\+$' k8s/prod | head
k8s/prod/auth/deploy.yaml:27:image: registry.local/auth:1.8
k8s/prod/api/deploy.yaml:19:image: registry.local/api:latest
Ce que cela signifie : Les lignes montrent des images référencées par tag uniquement. Vous êtes à un overwrite de registre d’une ambiguïté.
Décision : Remplacez par la forme digest ou imposez l’immutabilité des tags ; de préférence les deux.
Task 14: Pin an image by digest in Kubernetes (the actual fix)
cr0x@server:~$ kubectl -n prod set image deploy/auth-service auth-service=registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
deployment.apps/auth-service image updated
cr0x@server:~$ kubectl -n prod get deploy auth-service -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
registry.local/auth@sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Ce que cela signifie : Votre desired state est maintenant immuable.
Décision : Commitez ce changement dans Git si vous utilisez GitOps ; ne laissez pas kubectl devenir la seule source de vérité.
Task 15: Record the digest as a deployment annotation (make audits and rollbacks sane)
cr0x@server:~$ kubectl -n prod annotate deploy/auth-service deployed-image-digest=sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e --overwrite
deployment.apps/auth-service annotated
cr0x@server:~$ kubectl -n prod get deploy/auth-service -o jsonpath='{.metadata.annotations.deployed-image-digest}{"\n"}'
sha256:2f9e1d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f9e
Ce que cela signifie : Vous pouvez maintenant répondre à « qu’avons-nous exactement déployé ? » sans fouiller les logs.
Décision : Automatisez cela dans le pipeline lors de la promotion.
Playbook de diagnostic rapide
Si la production se comporte bizarrement et que vous suspectez une dérive d’images/versions, ne commencez pas par les Dockerfiles ou les graphes de dépendances. Commencez par l’identité.
Première étape : confirmer si les pods tournent le même digest
- Vérifiez le
imageIDdes pods à travers les réplicas. - Si plusieurs digests existent, vous avez de la dérive. Arrêtez-vous et corrigez la référence de déploiement d’abord.
Deuxième étape : vérifier ce que référence le déploiement (tag ou digest)
- S’il s’agit d’un tag : supposez qu’il peut bouger. Considérez-le comme non sûr jusqu’à preuve du contraire.
- S’il s’agit d’un digest : la dérive ne devrait pas arriver sauf en cas de confusion multi-arch, de conteneurs multiples ou de déploiements multiples.
Troisième étape : comparer l’état du registre vs le comportement du cache des nœuds
- Confirmez quel digest le tag pointe actuellement en inspectant le manifest ou en effectuant un pull qui affiche le digest.
- Vérifiez ImagePullPolicy et le cache runtime des nœuds.
- Si vous utilisez un miroir/proxy, confirmez qu’il ne sert pas de mappings de tag périmés.
Quatrième étape : décider la mesure de confinement
- Idéal : épinglez le digest et effectuez le rollout.
- Si vous avez besoin d’uniformité d’urgence : drenez les nœuds et forcez de nouveaux pulls après l’épinglage.
- Si c’est multi-arch : confirmez quel digest de plateforme chaque nœud tire ; épinglez au bon niveau (index vs manifest).
Cinquième étape : écrire la correction permanente
- Immutabilité des tags au registre pour les tags de release.
- Pipeline de promotion qui déplace les environnements par digest.
- Checks politiques en CI bloquant les tags flottants dans les manifests prod.
Erreurs courantes : symptôme → cause racine → correctif
Mistake 1: “Some pods behave differently, but they’re on the same Deployment”
Symptôme : Messages d’erreur différents, comportement différent, même YAML.
Cause racine : Tag mutable + IfNotPresent + caches de nœuds mixtes (ou staleness du miroir).
Correctif : Déployez par digest. Puis effectuez un rollout pour remplacer tous les pods. Imposer l’immutabilité des tags et arrêter d’écraser les tags de release.
Mistake 2: “We updated the tag, but nothing changed”
Symptôme : La CI dit qu’elle a poussé :stable, la CD dit qu’elle a déployé, mais le runtime n’a pas changé.
Cause racine : Le runtime n’a pas tiré parce que l’image est déjà présente ; le contrôleur n’a pas vu de changement de spec qui déclenche un rollout.
Correctif : Épinglez le digest (force un changement de spec). Si vous devez utiliser des tags, mettez en place une politique qui force de nouveaux pulls et déclenche des rollouts—puis acceptez le coût opérationnel.
Mistake 3: “We pinned digests and still see differences between nodes”
Symptôme : Même digest dans le manifest, mais comportement différent sur des nœuds arm64 vs amd64.
Cause racine : Vous avez épinglé un digest d’index vs un digest de manifest spécifique à la plateforme (ou inversement) et l’image sous-jacente par plateforme diffère en subtilités (glibc vs musl, dépendances natives, etc.).
Correctif : Décidez si vous voulez un digest d’index multi-arch unique ou un épinglage explicite par arch. Testez les deux architectures. N’assumez pas la parité.
Mistake 4: “Security rebuilt base images and now prod is inconsistent”
Symptôme : Après une reconstruction, certains pods commencent à échouer sur TLS, DNS ou logique de fuseau horaire.
Cause racine : Reconstruction du même tag ; les mises à jour de l’image de base ont modifié les bundles de certificats, le comportement de libc ou les versions de paquets.
Correctif : Reconstruisez sous un nouveau tag (ou un tag de build unique), épinglez le digest pour le déploiement, et exécutez une étape de promotion avec des tests. Continuez à patcher les images de base mais arrêtez d’écraser les tags utilisés par les clusters en fonctionnement.
Mistake 5: “Rollback didn’t roll back”
Symptôme : Vous revenez le manifest de :prod à :previous, et vous observez toujours le nouveau comportement.
Cause racine : Les deux tags pointent vers le même digest (le tag a bougé), ou le tag « précédent » a été écrasé pendant des reconstructions.
Correctif : Effectuez le rollback par digest. Maintenez un registre immuable des derniers digests sains par environnement.
Mistake 6: “We set ImagePullPolicy=Always, so we’re safe”
Symptôme : Lenteur fréquente des déploiements, limites de débit depuis les registres, et échecs occasionnels pendant les problèmes du registre.
Cause racine : Vous avez utilisé le pull comme substitut à une identité immuable. Maintenant la correction dépend de la disponibilité du registre.
Correctif : Utilisez des digests. Gardez Always pour les workflows de développement où le suivi de tag est intentionnel, et assurez la résilience du registre/miroir si vous en dépendez.
Listes de contrôle / plan étape par étape
Étape par étape : implémenter la règle du tag d’image dans une organisation réelle
- Définissez la politique : « Les manifests prod et staging doivent référencer les images par digest. » Écrivez-la. Rendez-la révisible.
- Décidez de votre schéma de nommage : Gardez des tags pour les humains (SHA de commit, numéro de build, numéro de PR). Les digests pour les machines.
- Rendez la promotion explicite : Une promotion déplace un digest d’un environnement à l’autre. Pas « reconstruire et redéployer ».
- Imposez l’immutabilité des tags quand c’est possible : Au moins pour les tags de release (ex.
v1.2.3). Si votre registre peut rejeter les overwrites, activez-le. - Ajoutez une vérification CI : Bloquez les manifests prod contenant
image: repo:tagsans@sha256. - Enregistrez les digests déployés : Ajoutez des annotations ou des métadonnées de release dans Git afin que la réponse incident ne dépende pas de la mémoire tribale.
- Décidez de la stratégie multi-arch : Épinglez-vous sur des digests d’index ou par arch ? Documentez et testez.
- Entraînez l’équipe on-call : Apprenez-leur à « vérifier imageID d’abord » comme geste par défaut en cas d’anomalie.
- Conservez une liste de derniers digests sains : Une par service et par environnement. Cela rend le rollback une modification en une ligne.
- Auditez périodiquement : Cherchez les manifests avec des tags flottants ; traitez cela comme des certificats TLS expirés—travail ennuyeux et prévisible.
Checklist opérationnelle : avant de déclarer un incident « résolu »
- Tous les réplicas tournent le même digest (ou l’ensemble de digests attendu par arch).
- L’état désiré référence ce digest, pas seulement un tag.
- Le tag humain pour le label convivial pointe vers le même digest que celui que vous avez déployé (optionnel, mais conserve la sanity).
- Le rollout est terminé et les anciens ReplicaSets sont réduits à zéro (sauf si vous les conservez intentionnellement).
- Le plan de rollback est « changer le digest en arrière », pas « espérer que le tag signifie encore ce qu’il signifiait hier ».
Checklist de release : comment éviter de créer de la dérive
- Chaque build crée un tag unique (SHA de commit, ID de build) et le pousse une fois.
- La promotion sélectionne un digest issu de la sortie de build.
- Aucune reconstruction n’a lieu pendant le déploiement ; le déploiement consomme des artefacts.
- Les mises à jour d’image de base déclenchent de nouveaux tags/digests, sans jamais écraser les tags de release utilisés par les clusters en fonctionnement.
- La réponse incident inclut la capture du digest depuis les pods en cours dans le ticket.
FAQ
1) Pourquoi déployer avec :latest est-il si mauvais ?
Parce que ce n’est pas une version. C’est un pointeur mouvant. Vous ne pouvez pas répondre de manière fiable à « qu’est-ce qui tourne ? » et vous ne pouvez pas rollbacker de manière fiable.
2) Un tag de version sémantique comme 1.2.3 n’est-il pas sûr ?
Seulement si votre registre et votre processus rendent ce tag immuable. Sinon, ce n’est qu’un tag avec de bonnes manières. L’épinglage par digest est la garantie technique.
3) L’épinglage des digests ne rend-il pas les workflows plus difficiles pour les humains ?
Un peu, jusqu’à ce que vous sépariez les « labels humains » de « l’identité machine ». Gardez les tags pour la navigation et les dashboards, mais déployez le digest. Votre futur vous remerciera en silence.
4) Quid de ImagePullPolicy de Kubernetes — dois-je mettre Always ?
Pas comme mécanisme de sécurité principal. Always augmente le trafic de pulls et couple la correction à la disponibilité du registre. Utilisez des digests pour obtenir l’immutabilité, puis choisissez la politique de pull pour les besoins de performance et de fraîcheur.
5) Si j’épingle par digest, puis-je arrêter de m’inquiéter des builds reproductibles ?
Non. L’épinglage par digest garantit que vous déployez le même artefact de manière répétée. Les builds reproductibles garantissent que l’artefact correspond au code source et aux entrées que vous souhaitiez. Ils résolvent des modes de défaillance différents.
6) Comment rollbacker proprement ?
Conservez un dernier digest sain par service. Rollbackez en changeant l’image du déploiement vers ce digest. Évitez les rollbacks qui référencent des tags comme :previous à moins d’imposer l’immutabilité.
7) Pourquoi deux nœuds tirent-ils des images différentes pour le même tag ?
Parce que les caches sont locaux et la politique de pull varie. De plus, les tags multi-arch peuvent résoudre différemment selon l’architecture du nœud. Les tags ne sont pas des identités stables ; ce sont des clés de recherche.
8) Quelle est la différence entre épingler un digest d’index et un digest de plateforme ?
Un digest d’index identifie une liste de manifests (multi-arch). Un digest de plateforme identifie le manifest d’image spécifique pour amd64, arm64, etc. Épinglez le niveau qui correspond à votre stratégie de flotte.
9) Nous utilisons un miroir de registre. Cela change-t-il le conseil ?
Non, cela renforce le conseil. Les miroirs améliorent la performance des pulls mais peuvent ajouter de la staleness. Les digests rendent les miroirs sûrs parce que le contenu mis en cache est indexé par des identifiants immuables.
10) Est-ce que c’est seulement un problème Kubernetes ?
Non. Tout système qui déploie des conteneurs peut souffrir de dérive de tags : Docker Compose, Nomad, ECS, Docker sur VM. Le problème sous-jacent est le même : des références mutables.
Conclusion : étapes concrètes à faire cette semaine
Si vous ne faites rien d’autre, faites ceci : choisissez un service de production et changez son déploiement de repo:tag à repo@sha256:digest. Enregistrez le digest dans les métadonnées de déploiement. Validez que chaque réplique exécute le même digest. Vous venez d’éliminer toute une catégorie d’ambiguïtés.
Puis rendez-le systémique :
- Ajoutez une porte CI qui rejette les tags flottants dans les manifests de production.
- Arrêtez d’écraser les tags de release. Si votre registre prend en charge l’immutabilité, activez-la pour les namespaces de release.
- Séparez build et deploy : la build produit des digests ; le déploiement sélectionne des digests. La promotion est un changement de pointeur dans Git, pas une reconstruction.
- Apprenez à l’on-call à vérifier d’abord le
imageID. C’est la vérité la plus rapide que vous avez.
« Ça marche sur ma machine » ne veut pas dire que vos coéquipiers sont négligents. Cela veut dire que votre système a permis l’ambiguïté. Supprimez l’ambiguïté. Rendez les octets ennuyeux.