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 :
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.
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.
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"]
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.invokerLe 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 ?
IAM raisonne par identité. Une identité par agent permet des droits distincts et des journaux d'audit lisibles (qui a appelé qui). Un compte partagé rend impossible de restreindre l'invocation à un seul appelant.
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 ?
L'audience empêche de réutiliser un jeton obtenu pour un service auprès d'un autre. Cloud Run rejette un jeton dont l'audience ne correspond pas au service appelé.
Résultat obtenu
Extraits réels de deployer.sh du 2026-10-15 (identifiants masqués) :
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.appL'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.invokerRien 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.invokersur un seul service à un seul compte de service - Expliquer le rôle de l'audience d'un jeton d'identité