gcloud builds submit --tag false-success trap
Problem
Une recette typique de déploiement Cloud Run ressemble à ceci :
gcloud builds submit --tag us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:latest
gcloud run services update SERVICE \
--image us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:latest \
--region us-central1 --project PROJECT
Quand la première commande échoue (le plus souvent parce que le répertoire courant ne
contient pas de Dockerfile), son message d'erreur sur une seule ligne s'efface au-dessus
du spinner de progression verbeux de la deuxième commande. La deuxième commande récupère
alors l'image existante :latest d'Artifact Registry — qui est le build précédent —
et la déploie. Vous obtenez :
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
Service [...] revision [...] has been deployed and is serving 100 percent of traffic.
Un déploiement vert « SUCCESS » qui n'a pas changé le code en cours d'exécution. L'utilisateur passe alors des heures à déboguer pourquoi la correction n'est pas en ligne, ou pire, conclut que la correction ne fonctionne pas et annule du bon code.
Context / Trigger Conditions
- L'utilisateur rapporte « J'ai déployé la correction mais le comportement en production est inchangé »
- La sortie récente du shell contient à la fois :
ERROR: (gcloud.builds.submit) Invalid value for [source]: Dockerfile required when specifying --tagrevision [...] has been deployed and is serving 100 percent of traffic
- Le
gcloud builds submit --tag …a été exécuté sans argument source positionnel ET le répertoire courant ne contient pas le Dockerfile (courant dans les monorepos où le Dockerfile se trouve dans un sous-répertoire commecloud-run-upload/,services/api/, etc.) - Les cibles de déploiement utilisent l'étiquette mutable
:latest(ou une autre étiquette qui existe déjà dans le registre)
Root cause
gcloud builds submit --tag IMAGE_TAG a besoin d'un répertoire source. Quand vous l'omettez, il
est défini par défaut sur le répertoire courant, et s'il n'y a pas de Dockerfile (ou un
cloudbuild.yaml), il se termine immédiatement avec le code de sortie 1 — sans rien uploader,
sans créer de job de build, sans toucher Artifact Registry.
Parce que gcloud run services update --image :latest résout :latest au moment du déploiement
depuis Artifact Registry, il redéploie joyeusement ce qui était là du build précédent
réussi. La réponse de Cloud Run ne compare pas le digest résolu à ce qui s'exécute déjà ;
elle crée joyeusement une nouvelle révision qui épingle le même digest. La sortie dit « Done »
parce que, du point de vue de Cloud Run, tout a fonctionné.
Deux erreurs se composent ici :
- L'échec du build est une seule ligne rouge parmi une sortie autrement verte et c'est facile à manquer.
- L'étiquette
:latestcache le fait que « l'image n'a pas réellement changé » — une étiquette immuable (SHA git, timestamp) aurait rendu la staleness évidente.
Solution
Immediate fix
Réexécutez le build depuis le répertoire contenant le Dockerfile, ou passez la source comme argument positionnel :
# Option A: cd dans le répertoire
cd cloud-run-upload
gcloud builds submit --tag us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:latest
# Option B: passer la source comme argument positionnel
gcloud builds submit cloud-run-upload \
--tag us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:latest
Attendez STATUS: SUCCESS dans la sortie (pas juste le retour de l'invite).
Ensuite, réexécutez la commande gcloud run services update pour récupérer le nouveau digest.
Permanent prevention (recommended)
-
Utilisez des étiquettes immuables. Ne déployez jamais
:latestsur Cloud Run pour quoi que ce soit d'important. Étiquez avec le SHA git ou le timestamp pour que la staleness soit visible :TAG=$(git rev-parse --short HEAD) gcloud builds submit cloud-run-upload \ --tag us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:${TAG} gcloud run services update SERVICE \ --image us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:${TAG} ...Si l'étape 1 échoue silencieusement, l'étape 2 échoue bruyamment avec « image not found » parce que cette étiquette n'existe pas encore. Faux positif converti en vrai erreur.
-
Chaînez avec
&&ou mettez les commandes dans un script avecset -e. Copier-coller deux commandes séparées laisse la deuxième s'exécuter même si la première a eu une erreur :#!/bin/bash set -euo pipefail cd "$(dirname "$0")/cloud-run-upload" TAG=$(git rev-parse --short HEAD) IMAGE=us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:${TAG} gcloud builds submit --tag "${IMAGE}" gcloud run services update SERVICE --image "${IMAGE}" --region us-central1 -
Vérifiez que la sortie du build contient
STATUS: SUCCESSavant d'exécuter le déploiement. La dernière ligne d'ungcloud builds submitréussi estSTATUS: SUCCESS. Si la dernière ligne est quelque chose d'autre (en particulierERROR:), le build n'a pas eu lieu.
Verification
Après réexécution, confirmez que le déploiement a réellement abouti en vérifiant que le digest de l'image de la révision déployée correspond à celle nouvellement construite :
# Digest de l'étiquette que vous venez de construire
gcloud artifacts docker images describe \
us-central1-docker.pkg.dev/PROJECT/REPO/IMAGE:latest \
--format='value(image_summary.digest)'
# Digest de l'image que Cloud Run sert actuellement
gcloud run services describe SERVICE --region us-central1 \
--format='value(spec.template.spec.containers[0].image)'
La deuxième commande affiche le digest résolu (si :latest a été épinglé) ou l'étiquette.
Pour une vérification comportementale de bout en bout, accédez à un endpoint de version
si le service en expose un, ou consultez les logs et confirmez les nouvelles lignes de log
que seul le code corrigé émettrait :
gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=SERVICE AND textPayload=~"NEW_LOG_LINE_FROM_FIX"' \
--limit=5 --freshness=5m --format='value(timestamp,textPayload)'
Example
Un utilisateur exécute ce qui suit comme deux commandes collées séparément :
$ gcloud builds submit --tag us-central1-docker.pkg.dev/PROJECT/REPO/blossom-upload:latest
ERROR: (gcloud.builds.submit) Invalid value for [source]: Dockerfile required when specifying --tag
$ gcloud run services update divine-blossom-upload \
--image us-central1-docker.pkg.dev/PROJECT/REPO/blossom-upload:latest \
--region us-central1 --project PROJECT
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
Done.
Service [divine-blossom-upload] revision [divine-blossom-upload-00008-kvg] has been deployed and is serving 100 percent of traffic.
La révision 00008-kvg est en direct mais exécute l'image avant la correction. L'utilisateur
teste la correction en production, elle se comporte toujours mal, et il escalade : « J'ai
déployé et ça ne fonctionne toujours pas. »
La correction est de réexécuter gcloud builds submit depuis le répertoire contenant le
Dockerfile (ou de le passer comme argument positionnel), d'attendre STATUS: SUCCESS,
puis de réexécuter gcloud run services update. La révision suivante portera le nouveau digest.
Notes
- Ce n'est pas spécifique à Cloud Run. La même forme de bug s'applique à tout pipeline de
déploiement qui sépare « construire l'image » de « pointer le service vers :latest » :
Kubernetes avec
imagePullPolicy: Always, ECS avec une définition de tâchelatest, Fly.iofly deployaprès unflyctl image pushéchoué, etc. Chaque fois que l'étape de déploiement résout une étiquette mutable, elle hérite de l'image précédente en cas d'échec de l'étape de build. gcloud builds submitaccepte également--gcs-source-staging-diret les workflowscloudbuild.yaml— le message d'erreur change mais le principe est identique : vérifiez le succès du build, ne chaînez pas les commandes sans condition.- Si vous voyez
PERMISSION_DENIEDdegcloud builds submit, c'est un échec différent et laisse également le registre intact — le même motif de faux positif s'applique. - Dans les shells avec middleware
rtk/tee, un bloc de plusieurs commandes peut être réexécuté par index ; réexécuter juste la commandeupdatesans lebuildest la recette exacte pour recréer ce bug.
References
- gcloud builds submit reference — notez que
--tagnécessite un argument source positionnel qui contient un Dockerfile - Cloud Run deploy from source — le one-shot
gcloud run deploy --source .évite entièrement ce piège de commandes séparées et vaut la peine d'être envisagé comme workflow alternatif - Related skill:
docker-buildx-stale-rust-binary— mode d'échec différent (staleness du cache de couche dans un build réussi) mais partage le symptôme « le déploiement semble correct, le code est vieux »