Déployer sur Cloud Run, puis nettoyer
Pour brancher un vrai numéro WhatsApp Business, Meta exige une URL HTTPS publique. Pour ouvrir le widget aux clients, il faut un service qui tourne sans le portable du développeur. Cloud Run donne les deux en une commande. Restent trois questions qui se posent seulement en production : où s'exécute le modèle (et donc où partent les données des clients), ce qui se passe quand le quota est épuisé, et comment être sûr qu'on n'a rien oublié derrière soi en partant.
Le mécanisme
flowchart LR
subgraph Projet["Projet Google Cloud (label formation-lab=agent-support-omnicanal)"]
CB["Cloud Build : Dockerfile"] --> AR[("Artifact Registry : image")]
GCS[("run-sources : archive du code")] --> CB
AR --> CR["Cloud Run lab-agent-support-omnicanal (1 instance max)"]
SA["Compte de service dédié : roles/aiplatform.user"] -.-> CR
end
U["Widget, simulateur, Meta"] -->|"HTTPS"| CR
CR -->|"MODEL, GOOGLE_CLOUD_LOCATION"| V["Vertex AI : Gemini + gemini-embedding-2"]
make cloud-up crée cinq choses ; make cloud-down supprime les cinq, puis vérifie.
| Ressource | Créée par | Supprimée par |
|---|---|---|
Service Cloud Run lab-agent-support-omnicanal | gcloud run deploy --source . | gcloud run services delete |
Archive du code dans gs://run-sources-<projet>-<région>/services/lab-… | même commande | gcloud storage rm -r |
Image dans cloud-run-source-deploy/lab-… | Cloud Build | gcloud artifacts docker images delete --delete-tags |
Compte de service lab-agent-support-omnicanal@… | gcloud iam service-accounts create | gcloud iam service-accounts delete |
Droit roles/aiplatform.user sur le projet | add-iam-policy-binding | remove-iam-policy-binding |
L'archive et l'image sont les deux oublis classiques : gcloud run services delete ne les touche pas, et elles se facturent au stockage, mois après mois.
Mise en œuvre
Les choix du déploiement
gcloud run deploy $(SERVICE) --source . --project $(PROJECT) --region $(REGION) \
--service-account $(SA) --allow-unauthenticated --max-instances 1 --memory 1Gi --timeout 120 \
--labels formation-lab=$(SLUG) \
--set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$(PROJECT),GOOGLE_CLOUD_LOCATION=$(VERTEX_LOCATION),MODEL=$(VERTEX_MODEL),EMBED_LOCATION=global,ADMIN_TOKEN=$(ADMIN_TOKEN),...| Option | Raison |
|---|---|
--service-account dédié | le compte par défaut de Compute Engine a souvent le rôle Éditeur sur tout le projet ; celui-ci ne peut qu'appeler Vertex AI |
--allow-unauthenticated | Meta et les clients doivent joindre le webhook et le widget ; la console et le tableau de bord exigent ADMIN_TOKEN, que cloud-up refuse s'il vaut encore changez-moi |
--max-instances 1 | SQLite vit sur le disque de l'instance, les sessions ADK et les verrous par conversation en mémoire : deux instances auraient deux vérités |
--timeout 120 | un tour avec trois relances sur 429 dure jusqu'à 15 s ; 120 s laisse de la marge sans garder indéfiniment les flux SSE |
/health, pas /healthz | /healthz est réservé par l'infrastructure de Cloud Run et n'atteint pas le conteneur |
| Pas de clé d'API | sur Vertex AI, le compte de service s'authentifie seul (ADC) ; aucun secret Gemini dans le service |
Le disque d'une instance Cloud Run est en mémoire : un redéploiement ou une mise à l'échelle à zéro efface la base. Pour un test, c'est acceptable. Pour une vraie boutique, remplacez Store par Cloud SQL ou Firestore, et les sessions par un service persistant ; la limite --max-instances 1 pourra alors sauter.
Le point d'accès du modèle
VERTEX_LOCATION décide où le modèle s'exécute. Le défaut du Makefile est europe-west1 avec gemini-2.5-flash : le traitement reste dans l'Union européenne, ce qui simplifie l'analyse RGPD pour une boutique française. L'autre option est global avec gemini-3.5-flash-lite : plus de capacité, mais Google choisit la région de traitement. Le test ci-dessous a été fait avec les deux.
gcloud auth login && gcloud auth application-default login
# .env : GOOGLE_CLOUD_PROJECT=<votre-projet>, ADMIN_TOKEN=$(openssl rand -hex 16)
make cloud-up # europe-west1, gemini-2.5-flash
make cloud-up VERTEX_LOCATION=global VERTEX_MODEL=gemini-3.5-flash-lite
make e2e URL=https://lab-agent-support-omnicanal-<id>-ew.a.run.app TOKEN=<ADMIN_TOKEN>
make cloud-downRésultat obtenu
Déploiement réel du 2026-10-16 (identifiants de projet masqués). make cloud-up a pris 1 min 36 s, dont la construction de l'image par Cloud Build. Sans jeton, GET /api/metrics renvoie 401.
Avec VERTEX_LOCATION=global et gemini-3.5-flash-lite, le scénario complet à travers le réseau :
cible : https://lab-agent-support-omnicanal-<id>-ew.a.run.app
OK GET /health
[web e2e-visiteur-2] Où en est ma commande LL-10587 ? Mon e-mail : julien.robert@exemple.fr
-> outils=['suivre_colis'] 1794 ms
-> Votre commande LL-10587 a été remise au transporteur Colis Rapide et est actuellement en transit sur la
plateforme de Tours. Sa livraison est prévue demain en point relais. [...] CR784559871FR.
[...]
"latence_p50_ms": 2065, "latence_p95_ms": 2797, "cout_usd": 0.00543,
OK indicateurs cohérents (+4 conversations, +1 escalade)
E2E OK (10 contrôles sur 10, 12 s au total)Même service, basculé sur europe-west1 et gemini-2.5-flash (gcloud run services update --update-env-vars), quelques minutes plus tard :
[web e2e-visiteur-1] Bonjour, la livraison est-elle gratuite pour une commande de 95 euros ?
-> outils=['rechercher_aide'] 10120 ms
[web e2e-visiteur-2] Où en est ma commande LL-10587 ? Mon e-mail : julien.robert@exemple.fr
-> outils=['erreur'] 14698 ms
-> Le service est momentanément indisponible. Votre message est bien reçu, un conseiller vous répondra.
ÉCHEC outil de commande appelé
E2E EN ÉCHEC : outil de commande appelé, état réel du colis, indicateurs cohérents (+4 conversations, +1 escalade)12:14:46 WARNING support.service API 429, nouvel essai dans 2s
12:14:48 WARNING support.service API 429, nouvel essai dans 4s
12:14:52 WARNING support.service API 429, nouvel essai dans 8s
12:15:00 ERROR support.service échec du tour agent we-2abe8c1dd0
429 RESOURCE_EXHAUSTED. Resource exhausted. Please try again later.Lecture : le projet de test partage son quota Vertex AI régional avec d'autres charges. Le premier tour a passé après deux relances (10,1 s). Le second a épuisé ses trois relances (2 + 4 + 8 s) puis a été escaladé : le client a reçu une réponse, la conversation est partie en file avec la priorité haute, et le contrôle « +1 escalade » a échoué parce qu'il y en a eu deux. Le comportement de repli est celui prévu ; la latence, non. Sur un projet dédié ou avec un débit provisionné, l'écart entre les deux points d'accès se réduit ; mesurez-le sur le vôtre avant de choisir.
Le nettoyage, puis sa vérification :
Deleted service [lab-agent-support-omnicanal].
Removing gs://run-sources-<projet>-europe-west1/services/lab-agent-support-omnicanal/1792152730.235878-….zip
Delete request issued. (image Artifact Registry)
Updated IAM policy for project [<projet>].
deleted service account [lab-agent-support-omnicanal@<projet>.iam.gserviceaccount.com]
Plus aucune ressource du lab.make cloud-status relancé ensuite n'affiche plus que ses quatre en-têtes, sans ressource.
Ce qui casse en production
| Symptôme | Cause | Parade |
|---|---|---|
| Latence de 10 à 15 s par vagues, escalades « erreur technique » | 429 sur le quota partagé du point d'accès régional | point d'accès global si la résidence des données le permet, débit provisionné sinon ; alerte sur les escalades pour panne |
| Les vrais webhooks Meta restent sans réponse plusieurs secondes, ou jamais | la tâche de fond tourne après la réponse HTTP, et Cloud Run réduit le CPU hors requête en facturation à la requête | --no-cpu-throttling (facturation à l'instance), ou un envoi par Cloud Tasks ; le simulateur ne le montre pas, car il attend la fin du traitement |
| Conversations perdues après un déploiement | base SQLite sur le disque en mémoire de l'instance | Cloud SQL ou Firestore |
make cloud-up échoue sur add-iam-policy-binding | politique IAM modifiée en même temps par un autre déploiement (etag) | le Makefile réessaie quatre fois |
La facture continue après services delete | archive de sources et image oubliées | make cloud-down les supprime et échoue s'il reste quoi que ce soit |
ADMIN_TOKEN lisible dans la console Cloud Run | variable d'environnement en clair | Secret Manager (--set-secrets) en production |
La règle de décision
Cloud Run convient tant qu'une instance suffit et que la base peut être sortie du conteneur ; c'est le cas d'une boutique de cette taille, pour un ordre de grandeur de quelques euros par mois hors modèle. Choisissez le point d'accès du modèle sur deux critères mesurés, la résidence des données exigée par votre analyse RGPD et le taux de 429 sur votre projet, pas sur le nom du modèle. Et ne considérez jamais un test cloud comme terminé avant que make cloud-down ait affiché « Plus aucune ressource du lab ».
Pratique
Le tableau de bord montre 12 escalades « Erreur technique de l'assistant » entre 14 h et 14 h 20, aucune avant ni après. Les clients ont tous reçu une réponse. Quelle est la première action ?
Plus d'instances ne crée pas de quota modèle, et casserait la cohérence de SQLite et des sessions en mémoire. Allonger les attentes rendrait le client muet 60 s avant l'escalade. Une vague courte d'erreurs techniques est la signature d'un quota partagé épuisé : on le vérifie dans les journaux, puis on agit sur la capacité.
Vous lancez make cloud-down. Il affiche « Il reste : lab-agent-support-omnicanal » sous « Images : » et sort avec le code 1. Que faites-vous ?
Une image se facture au stockage, service ou pas. La commande est idempotente : la relancer termine ce qui manquait. Supprimer tout le dépôt cloud-run-source-deploy effacerait les images des autres services du projet, que ce lab n'a pas créées.
Exercice. Préparez le service pour de vrais webhooks Meta : (1) les tâches de fond doivent disposer du CPU après la réponse, (2) ADMIN_TOKEN et WHATSAPP_APP_SECRET ne doivent plus apparaître en clair dans la configuration du service. Écrivez les modifications de cloud-up et de cloud-down.
Voir la solution
(1) Ajoutez --no-cpu-throttling à gcloud run deploy. Le CPU reste alloué pendant toute la vie de l'instance : la facturation passe à l'instance, d'où l'intérêt de garder --max-instances 1 et de ne pas fixer --min-instances.
(2) Créez les secrets, donnez l'accès au seul compte de service, et référencez-les :
printf %s "$ADMIN_TOKEN" | gcloud secrets create lab-agent-support-omnicanal-admin --data-file=- \
--labels formation-lab=agent-support-omnicanal --project $PROJECT
gcloud secrets add-iam-policy-binding lab-agent-support-omnicanal-admin \
--member serviceAccount:$SA --role roles/secretmanager.secretAccessor --project $PROJECT
gcloud run deploy ... --set-secrets ADMIN_TOKEN=lab-agent-support-omnicanal-admin:latest,WHATSAPP_APP_SECRET=...Dans cloud-down, ajoutez gcloud secrets delete lab-agent-support-omnicanal-admin --quiet (et l'autre secret), et dans cloud-status une ligne gcloud secrets list --filter "labels.formation-lab=agent-support-omnicanal", sinon la vérification finale ne verra pas un secret oublié. Activez secretmanager.googleapis.com dans cloud-up.
- Je sais ce que
gcloud run deploy --sourcecrée en plus du service, et comment tout supprimer - Je sais justifier
--max-instances 1pour cette architecture, et ce qu'il faut changer pour le lever - Je sais choisir entre point d'accès régional et global, mesures à l'appui
- J'ai déployé, lancé
make e2e URL=…, puis vu « Plus aucune ressource du lab »