Leçon 8 — Exploiter

Déployer sur Cloud Run, puis nettoyer

10 min de lecture

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.

RessourceCréée parSupprimée par
Service Cloud Run lab-agent-support-omnicanalgcloud run deploy --source .gcloud run services delete
Archive du code dans gs://run-sources-<projet>-<région>/services/lab-…même commandegcloud storage rm -r
Image dans cloud-run-source-deploy/lab-…Cloud Buildgcloud artifacts docker images delete --delete-tags
Compte de service lab-agent-support-omnicanal@…gcloud iam service-accounts creategcloud iam service-accounts delete
Droit roles/aiplatform.user sur le projetadd-iam-policy-bindingremove-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

files/Makefile (extrait)
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),...
OptionRaison
--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-unauthenticatedMeta 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 1SQLite 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 120un 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'APIsur 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.

terminal
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-down

Ré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 :

make e2e URL=… (Cloud Run, global, 2026-10-16, extrait)
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 :

make e2e URL=… (Cloud Run, europe-west1, 2026-10-16, extrait)
[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)
gcloud logging read (Cloud Run, 2026-10-16)
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 :

make cloud-down (2026-10-16, extrait)
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ômeCauseParade
Latence de 10 à 15 s par vagues, escalades « erreur technique »429 sur le quota partagé du point d'accès régionalpoint 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 jamaisla 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éploiementbase SQLite sur le disque en mémoire de l'instanceCloud SQL ou Firestore
make cloud-up échoue sur add-iam-policy-bindingpolitique 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 deletearchive de sources et image oubliéesmake cloud-down les supprime et échoue s'il reste quoi que ce soit
ADMIN_TOKEN lisible dans la console Cloud Runvariable d'environnement en clairSecret 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 ?

Vous lancez make cloud-down. Il affiche « Il reste : lab-agent-support-omnicanal » sous « Images : » et sort avec le code 1. Que faites-vous ?

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 --source crée en plus du service, et comment tout supprimer
  • Je sais justifier --max-instances 1 pour 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 »