Leçon 4 — Déployer et tester

Deux services privés et une seule autorisation

5 min de lecture

Deux services privés et une seule autorisation

Une image, deux services

Les deux agents partagent les mêmes dépendances. On construit donc une seule image ; la variable MODULE choisit l'application que lance uvicorn :

files/Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY stock_agent.py commandes_agent.py ./
ENV MODULE=stock_agent PYTHONUNBUFFERED=1
# Cloud Run fournit $PORT (8080 par défaut)
CMD exec uvicorn ${MODULE}:app --host 0.0.0.0 --port ${PORT:-8080}

Ici on n'utilise pas adk deploy cloud_run : ce dernier lance adk api_server, alors que nous voulons servir nos applications A2A to_a2a telles quelles, avec une URL de carte maîtrisée. Un Dockerfile de huit lignes suffit.

files/deployer.sh
gcloud artifacts repositories create lab-agents-a2a --repository-format docker \
  --location "$REGION" --project "$PROJECT_ID" --labels "$LABEL"
gcloud builds submit --tag "$IMAGE" --project "$PROJECT_ID" --region "$REGION" .

Connaître l'URL avant de déployer

La carte A2A doit annoncer l'URL publique du service, et agent_commandes doit connaître celle de agent_stock. Cloud Run attribue une URL déterministe de la forme https://<service>-<numéro de projet>.<région>.run.app : on peut la calculer avant même le premier déploiement.

files/deployer.sh
NUM=$(gcloud projects describe "$PROJECT_ID" --format 'value(projectNumber)')
STOCK_URL="https://${STOCK}-${NUM}.${REGION}.run.app"
CMD_URL="https://${CMD}-${NUM}.${REGION}.run.app"

Une identité par agent, une seule autorisation

flowchart LR
  SC["SA lab-a2a-commandes"] -->|"roles/aiplatform.user (projet)"| V["Vertex AI"]
  SS["SA lab-a2a-stock"] -->|"roles/aiplatform.user (projet)"| V
  SC -->|"roles/run.invoker sur le SEUL service lab-a2a-stock"| ST["lab-a2a-stock"]
  U["Vous (Propriétaire)"] -->|"run.invoker hérité"| CO["lab-a2a-commandes"]
files/deployer.sh
for SA in $STOCK $CMD; do
  gcloud iam service-accounts create "$SA" --project "$PROJECT_ID" --display-name "Lab A2A : $SA"
  gcloud projects add-iam-policy-binding "$PROJECT_ID" --condition None \
    --member "serviceAccount:${SA}@${PROJECT_ID}.iam.gserviceaccount.com" --role roles/aiplatform.user
done

gcloud run deploy "$STOCK" --image "$IMAGE" --region "$REGION" --project "$PROJECT_ID" \
  --no-allow-unauthenticated --service-account "${STOCK}@${PROJECT_ID}.iam.gserviceaccount.com" \
  --set-env-vars "MODULE=stock_agent,PUBLIC_URL=${STOCK_URL},${VERTEX}" \
  --max-instances 1 --memory 1Gi --labels "$LABEL"

# Seul le compte de service de agent_commandes peut invoquer agent_stock
gcloud run services add-iam-policy-binding "$STOCK" --region "$REGION" --project "$PROJECT_ID" \
  --member "serviceAccount:${CMD}@${PROJECT_ID}.iam.gserviceaccount.com" --role roles/run.invoker

Le rôle run.invoker est accordé sur le service lab-a2a-stock, pas sur le projet : le compte de agent_commandes ne peut invoquer aucun autre service.

Pourquoi deux comptes de service plutôt qu'un seul partagé par les deux agents ?

Le jeton d'identité de service à service

Dans le conteneur de agent_commandes, id_token.fetch_id_token(Request(), STOCK_URL) interroge le serveur de métadonnées de Cloud Run, qui renvoie un jeton signé par Google pour le compte lab-a2a-commandes, avec STOCK_URL comme audience (le destinataire prévu du jeton). Cloud Run, devant agent_stock, vérifie la signature, l'audience et le rôle run.invoker avant de transmettre la requête.

sequenceDiagram
  participant C as "Conteneur agent_commandes"
  participant M as "Serveur de métadonnées"
  participant F as "Cloud Run (frontal de lab-a2a-stock)"
  participant S as "Conteneur agent_stock"
  C->>M: jeton d'identité, audience = STOCK_URL ?
  M-->>C: JWT signé (compte lab-a2a-commandes)
  C->>F: POST / + Authorization: Bearer JWT
  F->>F: signature, audience, run.invoker ?
  F->>S: requête transmise
  S-->>C: tâche A2A

Le jeton est demandé avec l'audience https://lab-a2a-commandes-... (l'URL du service appelant) au lieu de STOCK_URL. Que répond le service stock ?

Résultat obtenu

Extraits réels de deployer.sh du 2026-10-15 (identifiants masqués) :

deployer.sh (2026-10-15)
Created repository [lab-agents-a2a].
Successfully installed a2a-sdk-1.2.2 ... google-adk-2.10.0 ... uvicorn-0.54.0 ...
Successfully tagged europe-west1-docker.pkg.dev/mon-projet-gcp/lab-agents-a2a/agents:v1
ID                                    CREATE_TIME                DURATION  ...  STATUS
a568bda4-...                          2026-10-15T14:42:12+00:00  52S       ...  SUCCESS
Created service account [lab-a2a-stock].
Deploying container to Cloud Run service [lab-a2a-stock] in project [mon-projet-gcp] region [europe-west1]
Service URL: https://lab-a2a-stock-123456789012.europe-west1.run.app
Updated IAM policy for service [lab-a2a-stock].
bindings:
- members:
  - serviceAccount:lab-a2a-commandes@mon-projet-gcp.iam.gserviceaccount.com
  role: roles/run.invoker
Deploying container to Cloud Run service [lab-a2a-commandes] in project [mon-projet-gcp] region [europe-west1]
Service URL: https://lab-a2a-commandes-123456789012.europe-west1.run.app

L'URL affichée par Cloud Run est bien celle calculée à l'avance : la carte de agent_stock est correcte dès le premier déploiement. La construction de l'image a pris moins d'une minute.

Exercice

Une équipe « facturation » déploie un troisième agent qui doit, lui aussi, consulter agent_stock, mais jamais agent_commandes. Quelles commandes IAM ajoutez-vous ?

Voir la solution

Un compte de service propre à l'agent facturation, puis une seule liaison, sur le service stock :

gcloud iam service-accounts create lab-a2a-facturation --project "$PROJECT_ID"
gcloud run services add-iam-policy-binding lab-a2a-stock --region "$REGION" --project "$PROJECT_ID" \
  --member "serviceAccount:lab-a2a-facturation@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role roles/run.invoker

Rien sur lab-a2a-commandes : sans liaison, son frontal Cloud Run renverra 403 à l'agent facturation. Et le service facturation est déployé avec --service-account lab-a2a-facturation@.... Pensez à ajouter ces suppressions à nettoyer.sh.

Je sais faire

  • Servir deux agents depuis une seule image avec une variable MODULE
  • Calculer l'URL déterministe d'un service Cloud Run avant de le déployer
  • Donner run.invoker sur un seul service à un seul compte de service
  • Expliquer le rôle de l'audience d'un jeton d'identité
Suivant