Scentralizowane zarządzanie bramą agentów z Agent Registry w wielu projektach na potrzeby Agent Runtime

1. Wprowadzenie

W miarę jak organizacje wdrażają generatywną AI, architektury szybko ewoluują od samodzielnych, monolitycznych czatbotów do rozproszonych systemów wieloagentowych (Agent-to-Agent / A2A). W tych nowoczesnych topologiach agenty orkiestratora wysokiego poziomu koordynują złożone przepływy pracy w firmie, delegując zadania do wyspecjalizowanych agentów roboczych domeny, serwerów narzędzi Model Context Protocol (MCP) i baz danych przedsiębiorstwa w ramach niezależnych projektów w chmurze Google.

Jednak działanie systemów wieloagentowych na dużą skalę wiąże się z poważnymi wyzwaniami dotyczącymi bezpieczeństwa, zarządzania i operacji:

  • Shadow Agent i rozrost narzędzi: gdy zespoły programistów wdrażają agentów w odizolowanych projektach bez scentralizowanego katalogu, organizacje tracą wgląd w to, jakie narzędzia i subagenci istnieją.
  • Niemonitorowany ruch wychodzący między projektami: zezwalanie agentom na bezpośrednie, niesprawdzone trasy sieciowe stwarza ryzyko wydobycia danych i pomija granice zabezpieczeń.
  • Niestabilne integracje zakodowane na stałe: kodowanie na stałe adresów URL agentów niższego poziomu i identyfikatorów silnika rozumowania tworzy niestabilne zależności, które przestają działać podczas uaktualniania lub ponownego wdrażania.
  • Brak tożsamości o najmniejszych uprawnieniach: udostępnione konta usługi nie zapewniają kryptograficznej niezaprzeczalności na poziomie poszczególnych instancji agenta.

Aby rozwiązać te problemy, platforma Gemini Enterprise Agent Platform udostępnia ujednoliconą platformę sterującą do zarządzania i zapewniania łączności, która składa się z 4 głównych filarów:

  1. Brama agentów (networkservices.googleapis.com): zarządzany, regionalny serwer proxy sieci i egzekwowania zasad. Działa w trybie ruchu wychodzącego AGENT_TO_ANYWHERE, przechwytuje ruch wychodzący agenta, przekazuje oceny autoryzacji do rozszerzeń zabezpieczeń i kieruje żądania przez granice projektu.
  2. Rejestr agentów (agentregistry.googleapis.com): pojedynczy katalog usług dla przedsiębiorstw. Zapewnia scentralizowany, zweryfikowany katalog wszystkich dostępnych narzędzi, serwerów MCP i agentów równorzędnych w organizacji, umożliwiając dynamiczne automatyczne wykrywanie w czasie działania bez zakodowanych na stałe punktów końcowych.
  3. Agent Identity & IAP v2 Governance (iap.googleapis.com i iam.googleapis.com): kryptograficzna platforma tożsamości i dostępu. Agenty wykonawcze otrzymują unikalne, atestowane identyfikatory URN maszyn SPIFFE (principal://...). Ruch wychodzący jest oceniany pod kątem scentralizowanych ujednoliconych zasad dostępu IAM (UAP / IAP w wersji 2), które weryfikują uprawnienia uniwersalne iap.googleapis.com/resources.egressViaIAP za pomocą rozbudowanych warunków katalogu Common Expression Language (CEL) (destination.agent_registry.*).
  4. Agent Runtime (silniki rozumowania): w pełni zarządzana, bezserwerowa platforma do wykonywania aplikacji opartych na agentach w języku Python, z natywnymi powiązaniami konfiguracji (agent_gateway_config) z bramami centralnymi.

Scenariusz biznesowy ćwiczenia: zakupy żywności i napojów w wielu projektach

W tym ćwiczeniu utworzysz rzeczywisty ekosystem zakupowy obejmujący wiele projektów i będziesz nim zarządzać. Ekosystem ten będzie obejmować 3 różne projekty w chmurze Google Cloud:

  • Centralny projekt zarządzania (PROJECT_GOVERNANCE): należy do centralnego zespołu IT i SecOps, hostuje centralną bramę agentów, centralny rejestr agentów i ujednolicone zasady dostępu uprawnień.
  • Projekt Consumer Orchestrator (PROJECT_CONCIERGE): należy do zespołu ds. zakupów i zawiera agenta obsługi zakupów, który dynamicznie wykrywa dostawców i kieruje zamówienia klientów.
  • Projekt dostawcy domeny (PROJECT_SELLERS): należy do dostawców zewnętrznych lub dostawców działu i hostuje agenta sprzedawcy burgerów oraz agenta sprzedawcy pizzy.

figure1

Rys. 1. Architektura scentralizowanego zarządzania wieloma projektami

Dlaczego warto korzystać ze scentralizowanego zarządzania między projektami?

W dużych przedsiębiorstwach zespoły zajmujące się usługami i grupy badaczy danych tworzą agenty AI w dziesiątkach niezależnych projektów w chmurze Google Cloud. Zapewnienie każdemu zespołowi bezpośredniej kontroli nad rejestracją narzędzi, trasami sieci wychodzącej i zabezpieczeniami powoduje niekontrolowany wzrost liczby niesprawdzonych narzędzi, niespójne zasady DLP, niemonitorowany ruch wychodzący VPC i pofragmentowane dzienniki kontrolne.

Scentralizowane zarządzanie między projektami oddziela tworzenie zasad od wykonywania agentów:

  • Centralny zespół IT i SecOps tworzy zasady bezpieczeństwa, sprawdza narzędzia i monitoruje ruch wychodzący w ramach jednego projektu scentralizowanego zarządzania.
  • Zespoły ds. usług i aplikacji skupiają się wyłącznie na logice biznesowej w swoich niezależnych Agent Runtime Projects, łącząc się bezpośrednio z bramą centralną bez narzutu operacyjnego związanego z zarządzaniem lokalnymi sieciami VPC, połączeniami międzysieciowymi czy rozproszonymi silnikami zasad.

figure2

Rysunek 2. Trzypoziomowa architektura i granice zarządzania między projektami

Dwupoziomowy model określania zakresu tożsamości w ujednoliconych zasadach dostępu

Gdy agenty komunikują się przez centralną bramę agentów, Identity-Aware Proxy (IAP v2) ocenia dostęp na podstawie tożsamości agenta wywołującego (tożsamości opartej na SPIFFE, poświadczonej kryptograficznie, wydawanej automatycznie kontenerowi środowiska wykonawczego) w odniesieniu do globalnej zasady dostępu uprawnień:

  • Poziom 1. Podstawowe interfejsy Google Cloud API (ogólne, za pomocą principalSet:// w regule 1): autoryzacja ruchu wychodzącego na poziomie projektu, która umożliwia wszystkim środowiskom wykonawczym agentów w projektach promieniowych dostęp do standardowych interfejsów API Google (aiplatform, iamcredentials, telemetry, agentregistry) na potrzeby wykrywania, generowania tokenów i wnioskowania.
  • Poziom 2. Narzędzia biznesowe i usługi A2A (szczegółowe uprawnienia za pomocą principal:// w regułach 2 i 3): rygorystyczny dostęp z jak najmniejszymi uprawnieniami powiązany z poszczególnymi instancjami silnika rozumowania, egzekwowany za pomocą warunków języka CEL (Common Expression Language) kierowanych na konkretne zarejestrowane usługi rejestru agentów (destination.agent_registry.agent.name).

Co utworzysz

  • Scentralizowana brama agentów (centralized-agw) w regionie PROJECT_GOVERNANCE
  • Rozszerzenie usługi autoryzacji IAP w wersji 2 i zasady autoryzacji w trybie ścisłego WYMUSZANIA (failOpen: false)
  • Podstawowa ujednolicona zasada dostępu uprawnień (uap-rules.json) i powiązanie zasad projektu
  • Uprawnienia agenta usługi do wielu projektów (ar_agw_cross_project_sa)
  • Udostępniony centralny zasobnik przejściowy Google Cloud Storage (GCS)
  • Odizolowani sprzedawcy burgerów i pizzy w regionie PROJECT_SELLERS
  • Agent Concierge ds. zakupów z dynamicznym automatycznym wykrywaniem REST w PROJECT_CONCIERGE
  • Rejestracje usług w centralnym rejestrze agentów z adresami URL mTLS obejmującymi wiele projektów
  • Dynamiczne aktualizacje zasad ruchu wychodzącego IAP v2 z weryfikacją na żywo i audytami Cloud Logging

figure3

Rysunek 3. Sekwencja implementacji krok po kroku

Czego się nauczysz

  • Jak skonfigurować uprawnienia agenta usługi w wielu projektach dla scentralizowanych bram
  • Jak kierować ruch wychodzący z Agent Runtime przez centralną Bramę agentów w środowiskach obejmujących wiele projektów
  • Jak przekazać uprawnienia bramy agentów do Identity-Aware Proxy (IAP w wersji 2) za pomocą rozszerzeń usługi (iapPolicyVersion: "V2")
  • Jak tworzyć i wiązać ujednolicone zasady dostępu (UAP) uprawnień z regułami języka CEL (Common Expression Language) zarządzającymi zarejestrowanymi miejscami docelowymi w Agent Registry (destination.agent_registry.*)
  • Jak wyeliminować zakodowane na stałe identyfikatory i adresy URL agentów, korzystając z automatycznego wykrywania w czasie działania w rejestrze agentów.
  • Jak przetestować rzeczywiste blokowanie w ramach modelu zero-trust (HTTP 403 Forbidden) i sprawdzić aktualizacje zasad na żywo w Cloud Logging.

Wymagania

  • 3 projekty Google Cloud z włączonymi rozliczeniami:
    • PROJECT_GOVERNANCE: centralne zarządzanie, brama, rejestr i zasady dostępu uprawnień
    • PROJECT_CONCIERGE: agent orkiestratora usługi Concierge ds. zakupów
    • PROJECT_SELLERS: agenty sprzedawcy specjalizującego się w burgerach i pizzy.
  • Użytkownik uprawnień lub konto usługi z uprawnieniami roles/owner lub uprawnieniami administracyjnymi we wszystkich 3 projektach.
  • organizację Google Cloud (na potrzeby mapowania domeny zaufania SPIFFE).
  • Google Cloud Shell lub komputer lokalny z zainstalowanym interfejsem wiersza poleceń gcloud, python (w wersji 3.11 lub nowszej) i uv.

To już koniec wprowadzenia. Przejdźmy do sekcji Konfiguracja i środowisko.

2. Konfiguracja

Chociaż ta architektura obejmuje 3 różne projekty w chmurze Google Cloud, możesz wykonać wszystkie polecenia wdrażania w terminalu, pobierania repozytoriów i operacji przejściowych z poziomu jednego terminala Cloud Shell ustawionego na PROJECT_GOVERNANCE. Każdy skrypt wdrażania i polecenie gcloud wyraźnie kieruje do odpowiedniego projektu docelowego za pomocą flag interfejsu wiersza poleceń (--project).

Zacznij od uzyskania dostępu do wiersza poleceń projektu w chmurze Google Cloud:

Ustawianie kontekstu projektu

# set terminal project context to Central Governance Project
gcloud config set project SET_YOUR_GOVERNANCE_PROJECT_ID_HERE
# login to gcloud cli
gcloud auth login
# login for application default credentials
gcloud auth application-default login
# update gcloud components
gcloud components update --quiet

Ustawianie zmiennych środowiskowych powłoki

Wpisz identyfikatory projektu.

# 1. Project Identifiers
export PROJECT_GOVERNANCE="SET_YOUR_GOVERNANCE_PROJECT_ID_HERE"
export PROJECT_CONCIERGE="SET_YOUR_CONCIERGE_PROJECT_ID_HERE"
export PROJECT_SELLERS="SET_YOUR_SELLERS_PROJECT_ID_HERE"

Te zmienne powłoki zostaną automatycznie wygenerowane.

# 2. Regional & Gateway Settings
export REGION="us-central1"
export AGW_NAME="centralized-agw"
export UAP_POLICY_NAME="uap-policy-${AGW_NAME}"
export UAP_BINDING_NAME="uap-binding-${AGW_NAME}"

# 3. Retrieve Project Numbers
export PROJECT_NUMBER_GOVERNANCE=$(gcloud projects describe ${PROJECT_GOVERNANCE} --format="value(projectNumber)")
export PROJECT_NUMBER_CONCIERGE=$(gcloud projects describe ${PROJECT_CONCIERGE} --format="value(projectNumber)")
export PROJECT_NUMBER_SELLERS=$(gcloud projects describe ${PROJECT_SELLERS} --format="value(projectNumber)")

# 4. Obtain Organization ID
export ORG_ID=$(gcloud projects get-ancestors ${PROJECT_GOVERNANCE} --format="value(id, type)" | grep organization | awk '{print $1}')

# 5. Set Application Default Credentials (ADC) Quota Project
gcloud auth application-default set-quota-project ${PROJECT_GOVERNANCE}

echo "Governance Project: ${PROJECT_GOVERNANCE} (${PROJECT_NUMBER_GOVERNANCE})"
echo "Concierge Project:  ${PROJECT_CONCIERGE} (${PROJECT_NUMBER_CONCIERGE})"
echo "Sellers Project:    ${PROJECT_SELLERS} (${PROJECT_NUMBER_SELLERS})"
echo "Organization ID:    ${ORG_ID}"
echo "UAP Policy Name:    ${UAP_POLICY_NAME}"
echo "UAP Binding Name:   ${UAP_BINDING_NAME}"

Tworzenie katalogu lokalnego na pliki konfiguracji

# create config folder
mkdir -p cfg

Przypisywanie roli administratora zasad dostępu na potrzeby ujednoliconych zasad dostępu

# grant Access Policy Admin and Project IAM Admin to current user in Governance Project
for ROLE in "roles/iam.accessPolicyAdmin" "roles/resourcemanager.projectIamAdmin"; do
  gcloud projects add-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="user:$(gcloud config get-value account)" \
    --role="${ROLE}" \
    --condition=None
done

Włączanie logów kontrolnych dostępu do danych Cloud Audit Logs dla IAP w wersji 2

Domyślnie Google Cloud wyłącza logi kontrolne dostępu do danych, aby zapobiec niezamierzonym kosztom przechowywania. IAP w wersji 2 generuje decyzje dotyczące autoryzacji (granted=true i granted=false) jako logi kontrolne sprawdzania dostępu do danych, dlatego włącz logowanie ADMIN_READ, DATA_READ i DATA_WRITE dla iap.googleapis.com w PROJECT_GOVERNANCE:

# 1. export current IAM policy for PROJECT_GOVERNANCE
gcloud projects get-iam-policy ${PROJECT_GOVERNANCE} \
  --format=json > cfg/gov_iam_policy.json
# 2. append auditConfigs for iap.googleapis.com
python3 -c "
import json
with open('cfg/gov_iam_policy.json') as f:
    policy = json.load(f)
audit_configs = [c for c in policy.get('auditConfigs', []) if c.get('service') != 'iap.googleapis.com']
audit_configs.append({
    'service': 'iap.googleapis.com',
    'auditLogConfigs': [
        {'logType': 'ADMIN_READ'},
        {'logType': 'DATA_READ'},
        {'logType': 'DATA_WRITE'}
    ]
})
policy['auditConfigs'] = audit_configs
with open('cfg/gov_iam_policy.json', 'w') as f:
    json.dump(policy, f, indent=2)
"
# 3. apply updated policy
gcloud projects set-iam-policy ${PROJECT_GOVERNANCE} cfg/gov_iam_policy.json
# 4. verify auditConfigs applied
gcloud projects get-iam-policy ${PROJECT_GOVERNANCE} --format="yaml(auditConfigs)"

Włączanie wymaganych interfejsów Google Cloud API

# enable google apis (agent platform & security bundle, part 1)
for PROJ in ${PROJECT_GOVERNANCE} ${PROJECT_CONCIERGE} ${PROJECT_SELLERS}; do
  gcloud services enable \
    agentregistry.googleapis.com \
    aiplatform.googleapis.com \
    apphub.googleapis.com \
    apptopology.googleapis.com \
    cloudapiregistry.googleapis.com \
    cloudtrace.googleapis.com \
    compute.googleapis.com \
    dataform.googleapis.com \
    iam.googleapis.com \
    agentidentity.googleapis.com \
    iap.googleapis.com \
    logging.googleapis.com \
    modelarmor.googleapis.com \
    monitoring.googleapis.com \
    networksecurity.googleapis.com \
    networkservices.googleapis.com \
    notebooks.googleapis.com \
    observability.googleapis.com \
    --project=${PROJ}
done
# enable google apis (agent platform bundle, part 2)
for PROJ in ${PROJECT_GOVERNANCE} ${PROJECT_CONCIERGE} ${PROJECT_SELLERS}; do
  gcloud services enable \
    securitycenter.googleapis.com \
    saasservicemgmt.googleapis.com \
    storage.googleapis.com \
    telemetry.googleapis.com \
    texttospeech.googleapis.com \
    --project=${PROJ}
done
# enable google apis (foundational & agent runtime build bundle, part 3)
for PROJ in ${PROJECT_GOVERNANCE} ${PROJECT_CONCIERGE} ${PROJECT_SELLERS}; do
  gcloud services enable \
    artifactregistry.googleapis.com \
    cloudbuild.googleapis.com \
    cloudresourcemanager.googleapis.com \
    iamcredentials.googleapis.com \
    serviceusage.googleapis.com \
    run.googleapis.com \
    orgpolicy.googleapis.com \
    --project=${PROJ}
done

Sprawdzanie, czy interfejsy API są włączone we wszystkich projektach

Zapewnienie, że wszystkie 3 projekty (PROJECT_GOVERNANCE, PROJECT_CONCIERGE i PROJECT_SELLERS) mają włączone dokładnie te same interfejsy API, zapewnia spójność operacyjną i zapobiega błędom generowania tokenów w czasie działania, błędom katalogowania schematów i przerwom w telemetrii.

Aby sprawdzić, czy interfejsy API są takie same we wszystkich 3 projektach, uruchom w Cloud Shell ten skrypt walidacji:

# validate that all required APIs are enabled across all 3 projects
python3 - << 'EOF'
import subprocess
import os
import sys

REQUIRED_APIS = [
    "agentregistry.googleapis.com",
    "aiplatform.googleapis.com",
    "apphub.googleapis.com",
    "apptopology.googleapis.com",
    "cloudapiregistry.googleapis.com",
    "cloudtrace.googleapis.com",
    "compute.googleapis.com",
    "dataform.googleapis.com",
    "iam.googleapis.com",
    "agentidentity.googleapis.com",
    "iap.googleapis.com",
    "logging.googleapis.com",
    "modelarmor.googleapis.com",
    "monitoring.googleapis.com",
    "networksecurity.googleapis.com",
    "networkservices.googleapis.com",
    "notebooks.googleapis.com",
    "observability.googleapis.com",
    "securitycenter.googleapis.com",
    "saasservicemgmt.googleapis.com",
    "storage.googleapis.com",
    "telemetry.googleapis.com",
    "texttospeech.googleapis.com",
    "artifactregistry.googleapis.com",
    "cloudbuild.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "iamcredentials.googleapis.com",
    "serviceusage.googleapis.com",
    "run.googleapis.com",
    "orgpolicy.googleapis.com"
]

projects = {
    "GOVERNANCE": os.environ.get("PROJECT_GOVERNANCE", ""),
    "CONCIERGE": os.environ.get("PROJECT_CONCIERGE", ""),
    "SELLERS": os.environ.get("PROJECT_SELLERS", "")
}

enabled = {}
for role, proj in projects.items():
    if not proj:
        print(f"Error: Environment variable for {role} is not set.")
        sys.exit(1)
    res = subprocess.run(
        ["gcloud", "services", "list", "--enabled", f"--project={proj}", "--format=value(config.name)"],
        capture_output=True, text=True, check=True
    )
    enabled[role] = set(res.stdout.strip().splitlines())

print(f"\n{'API Name':<36} | {'GOVERNANCE':<12} | {'CONCIERGE':<12} | {'SELLERS':<12}")
print("-" * 78)

all_synced = True
for api in REQUIRED_APIS:
    g_status = "ENABLED" if api in enabled["GOVERNANCE"] else "MISSING"
    c_status = "ENABLED" if api in enabled["CONCIERGE"] else "MISSING"
    s_status = "ENABLED" if api in enabled["SELLERS"] else "MISSING"
    if "MISSING" in (g_status, c_status, s_status):
        all_synced = False
    print(f"{api:<36} | {g_status:<12} | {c_status:<12} | {s_status:<12}")

print("-" * 78)
if all_synced:
    print("✅ All 29 required APIs are ENABLED and synchronized across all three projects.\n")
else:
    print("❌ Discrepancies detected. Please re-run the enablement commands for missing services.\n")
    sys.exit(1)
EOF

Przykładowe dane wyjściowe weryfikacji:

Wszystkie interfejsy API powinny być włączone.

✅ All 30 required APIs are ENABLED and synchronized across all three projects.

Konfigurowanie zasad organizacji

Domyślne zasady organizacji Google Cloud wymuszają ograniczenia, które ograniczają powiązania zasad dostępu uprawnień w wersji 3 z zasobami (constraints/iam.managed.disableAccessPolicyBinding).

Zastąp wszystkie odziedziczone ograniczenia zasad organizacji na poziomie projektu, jawnie ustawiając wartość enforce: false na „zezwól”.

# disable iam v3 constraint (allow v3 access policies)
gcloud org-policies set-policy /dev/stdin << EOF
name: projects/${PROJECT_NUMBER_GOVERNANCE}/policies/iam.managed.disableAccessPolicyBinding
spec:
  rules:
  - enforce: false
EOF
# verify org policy constraints on project
gcloud org-policies describe iam.managed.disableAccessPolicyBinding \
  --project=${PROJECT_GOVERNANCE} --effective

To już koniec konfiguracji. Przejdź do sekcji Rejestrowanie podstawowych interfejsów API Google.

3. Rejestr agentów

Rejestrowanie usługi punktu końcowego podstawowych interfejsów API Google

Brama agentów wymaga zarejestrowania adresów URL interfejsów API Google w centralnym rejestrze agentów, aby agenty skonfigurowane za pomocą agent_gateway_config mogły bezpiecznie kierować ruch wychodzący do podstawowych usług backendu Google Cloud (takich jak aiplatform, dane logowania uprawnień i telemetria).

Tworzenie agenta core-gapi-services w rejestrze agentów

# register core google api endpoints in agent registry with standard and :443 port variants
gcloud agent-registry services create core-gapi-services \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} \
  --display-name="gapi.core.services" \
  --description="Core Google Cloud APIs and Service Endpoints" \
  --endpoint-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://telemetry.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://telemetry.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://aiplatform.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://aiplatform.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://aiplatform.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://aiplatform.mtls.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://cloudresourcemanager.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://cloudresourcemanager.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://cloudresourcemanager.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://cloudresourcemanager.mtls.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://iamcredentials.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://iamcredentials.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://iamcredentials.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://iamcredentials.mtls.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://agentregistry.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://agentregistry.mtls.googleapis.com \
  --interfaces=protocolBinding=JSONRPC,url=https://agentregistry.googleapis.com:443 \
  --interfaces=protocolBinding=JSONRPC,url=https://agentregistry.mtls.googleapis.com:443

Przechwyć identyfikator zasobu punktu końcowego interfejsów API Core

# capture the underlying Agent Registry endpoint ID
export ENDPOINT_ID=$(gcloud agent-registry services describe core-gapi-services \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} \
  --format="value(registryResource)" | awk -F'/' '{print $NF}')
echo "Core APIs Endpoint ID: ${ENDPOINT_ID}"

Różnice między principalSet a principal w tożsamości agenta

W Google Cloud IAM i Gemini Enterprise Agent Platform tożsamości maszyn wydawane kontenerom agentów wykonawczych używają kryptograficznie poświadczonych identyfikatorów SPIFFE URN ocenianych przez Identity-Aware Proxy (IAP v2). Podczas konfigurowania ujednoliconych zasad dostępu uprawnień możesz kierować na konkretny pojedynczy principal lub na principalSet oparty na atrybutach:

Wymiar

principal:// (tożsamość pojedynczej maszyny)

principalSet:// (grupa oparta na atrybutach)

Składnia uprawnień

principal://...

principalSet://...

Szczegółowość

Szczegółowe (na poziomie instancji): identyfikuje pojedynczą, konkretną instancję kontenera silnika rozumowania.

Ogólne (na poziomie projektu): identyfikuje wszystkie silniki rozumowania, które mają wspólny atrybut projektu.

Wzorzec URN

principal://agents.global.org-${ORG_ID}.system.id.goog/resources/aiplatform/projects/${PROJECT_NUMBER}/locations/${REGION}/reasoningEngines/${ENGINE_ID}

principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER}

Przypadek użycia w Agent Platform

Poziom 2 (narzędzia biznesowe i A2A): autoryzowanie określonych agentów orkiestratora do wywoływania narzędzi domeny docelowej (np. Concierge zakupów $ ightarrow$ sprzedawca burgerów).

Poziom 1 (infrastruktura podstawowa): przyznanie wszystkim agentom w projekcie dostępu do interfejsów Google Cloud API (core-gapi-services).

Wpływ cyklu życia

Jeśli agent zostanie usunięty i utworzony ponownie, jego nowy identyfikator wyszukiwarki będzie wymagał zaktualizowanego powiązania zasady uprawnień.

Automatycznie stosuje się do nowo wdrożonych agentów w tym projekcie bez dodatkowych aktualizacji uprawnień.

Zarządzanie deklaratywne za pomocą ujednoliconych zasad dostępu (UAP / IAP v2)

W starszej wersji IAP v1 zasady ruchu wychodzącego były dołączane bezpośrednio do poszczególnych zasobów Agent Registry za pomocą gcloud beta iap web add-iam-policy-binding. W przypadku IAP v2 i ujednoliconych zasad dostępu powiązania poszczególnych zasobów są eliminowane na rzecz jednej, scentralizowanej zasady dostępu uprawnień (cfg/uap-rules.json).

Podstawowa autoryzacja ruchu wychodzącego dla core-gapi-services zostanie skonfigurowana jako Reguła 1 w ujednoliconych zasadach dostępu w sekcji 5, co zapewni, że wszystkie kontenery agentów będą miały ustanowione podstawowe trasy ruchu wychodzącego przed wdrożeniem.

Więcej informacji technicznych o identyfikatorach podmiotów zabezpieczeń i mechanizmach Workload Identity znajdziesz w tych artykułach:

To już koniec rejestracji punktu końcowego podstawowych interfejsów API. Przejdź do sekcji Wdrażanie scentralizowanej bramy agentów.

4. Brama agentów

Wdrażanie scentralizowanej bramy agentów

Wdróż scentralizowaną bramę agentów (centralized-agw) w trybie ruchu wychodzącego AGENT_TO_ANYWHERE w projekcie $PROJECT_GOVERNANCE.

Definiowanie pliku manifestu konfiguracji bramy

Utwórz cfg/${AGW_NAME}.yaml na potrzeby zarządzania ruchem wychodzącym:

# generate agent gateway config yaml
cat > cfg/${AGW_NAME}.yaml << EOF
name: ${AGW_NAME}
protocols:
  - MCP
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
registries:
  - "//agentregistry.googleapis.com/projects/${PROJECT_GOVERNANCE}/locations/${REGION}"
EOF

Importowanie konfiguracji bramy agentów

# import and create agent gateway
gcloud network-services agent-gateways import ${AGW_NAME} \
  --source="cfg/${AGW_NAME}.yaml" \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Weryfikowanie szczegółów bramy agentów

# show agent gateway status
gcloud network-services agent-gateways describe ${AGW_NAME} \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Przykładowe dane wyjściowe:

agentGatewayCard:
  mtlsEndpoint: projects/${AGW_TP_ID}/regions/us-central1/serviceAttachments/unitkind1-swp-mtls-psc-sa
  rootCertificates:
  - |
    -----BEGIN CERTIFICATE-----
    MIIDwzCCAqugAwIBAgITNQuWGopdOZaHdcK7r7AYFhonqDANBgkqhkiG9w0BAQsF
    ...
    -----END CERTIFICATE-----
  serviceExtensionsServiceAccount: service-${PROJ_NO}@gcp-sa-dep.iam.gserviceaccount.com
createTime: 'YYYY-MM-DDT12:34:56.789098765Z'
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
name: projects/${PROJECT_GOVERNANCE}/locations/us-central1/agentGateways/centralized-agw
protocols:
- MCP
registries:
- //agentregistry.googleapis.com/projects/${PROJECT_GOVERNANCE}/locations/us-central1
updateTime: 'YYYY-MM-DDT12:34:56.789098765Z'

To już koniec wdrażania bramy. Teraz przejdź do sekcji Konfigurowanie autoryzacji.

5. Autoryzacja

Skonfiguruj autoryzację bramy agentów i podstawową platformę UAP

Brama agentów zabezpiecza ruch wychodzący narzędzi i agentów oraz nim zarządza przy użyciu zasad autoryzacji (networksecurity.authzPolicies) zintegrowanych z ujednoliconymi zasadami dostępu (UAP) Identity-Aware Proxy (IAP w wersji 2).

Omówienie architektury autoryzacji

figure4

Rysunek 4. Omówienie architektury autoryzacji

Architektura autoryzacji składa się z 3 połączonych ze sobą warstw:

  1. Rozszerzenie usługi IAP (authzExtension): zasób regionalny skonfigurowany z użyciem service: iap.googleapis.com, metadata: iapPolicyVersion: "V2" i failOpen: false na potrzeby rygorystycznego egzekwowania modelu zero-trust na granicy.
  2. Zasady autoryzacji bramy (authzPolicy): zasób regionalny kierujący do bramy agentów za pomocą policyProfile: REQUEST_AUTHZ i action: CUSTOM, który przekierowuje kontrole autoryzacji do rozszerzenia autoryzacji IAP.
  3. Ujednolicone zasady dostępu i powiązanie uprawnień (accessPolicy i policyBinding): globalny zasób uprawnień w wersji 3 oceniany przez IAP. Sprawdza uprawnienia uniwersalne iap.googleapis.com/resources.egressViaIAP pod kątem tożsamości SPIFFE elementu wywołującego i warunków katalogu CEL.

Krok 1. Utwórz i zaimportuj rozszerzenie autoryzacji IAP w wersji 2

Utwórz plik manifestu rozszerzenia usługi z iapPolicyVersion: "V2" i failOpen: false w trybie ścisłym ENFORCE:

# create authz extension config file in ENFORCE mode
cat > cfg/${AGW_NAME}-svc-ext-authz-iap.yaml << EOF
name: ${AGW_NAME}-svc-ext-authz-iap
service: iap.googleapis.com
failOpen: false
timeout: 1s
metadata:
  iapPolicyVersion: "V2"
EOF

Zaimportuj rozszerzenie autoryzacji:

# import IAP v2 authz extension
gcloud service-extensions authz-extensions import ${AGW_NAME}-svc-ext-authz-iap \
  --source=cfg/${AGW_NAME}-svc-ext-authz-iap.yaml \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Sprawdź, czy rozszerzenie Authz jest aktywne:

# describe authz extension
gcloud service-extensions authz-extensions describe ${AGW_NAME}-svc-ext-authz-iap \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Przykładowe dane wyjściowe:

createTime: 'YYYY-MM-DDT12:34:56.789098765Z'
failOpen: false
metadata:
  iapPolicyVersion: V2
name: projects/${PROJECT_GOVERNANCE}/locations/us-central1/authzExtensions/centralized-agw-svc-ext-authz-iap
service: iap.googleapis.com
timeout: 1s

Krok 2. Utwórz i zaimportuj zasadę autoryzacji bramy

Utwórz konfigurację zasad autoryzacji, która jest dołączona do bramy agentów i deleguje weryfikację żądań do rozszerzenia autoryzacji IAP:

# create authz policy manifest
cat > cfg/${AGW_NAME}-authz-policy-profile-iap.yaml << EOF
name: ${AGW_NAME}-authz-policy-profile-iap
target:
  resources:
    - "projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agentGateways/${AGW_NAME}"
policyProfile: REQUEST_AUTHZ
action: CUSTOM
customProvider:
  authzExtension:
    resources:
      - "projects/${PROJECT_GOVERNANCE}/locations/${REGION}/authzExtensions/${AGW_NAME}-svc-ext-authz-iap"
EOF

Zaimportuj zasadę autoryzacji:

# import authz policy
gcloud beta network-security authz-policies import ${AGW_NAME}-authz-policy-profile-iap \
  --source=cfg/${AGW_NAME}-authz-policy-profile-iap.yaml \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Sprawdź aktywną zasadę autoryzacji:

# describe authz policy
gcloud beta network-security authz-policies describe ${AGW_NAME}-authz-policy-profile-iap \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE}

Krok 3. Utwórz początkową ujednoliconą zasadę dostępu (reguła 1: podstawowe interfejsy API Google)

Utwórz cfg/uap-rules.json z regułą 1, która autoryzuje 3 principalSet projektu do uzyskiwania dostępu do core-gapi-services:

# create initial unified access policy rules manifest
cat > cfg/uap-rules.json << EOF
[
  {
    "description": "Rule 1: Allow agent runtimes across all 3 projects to reach Core Google APIs",
    "effect": "ALLOW",
    "principals": [
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_GOVERNANCE}",
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}",
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_SELLERS}"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true && \
         destination.agent_registry.resource_type == 'ENDPOINT' && ( \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/endpoints/core-gapi-services' || \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/endpoints/${ENDPOINT_ID}' || \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/${REGION}/endpoints/${ENDPOINT_ID}')"
      }
    }
  }
]
EOF

Krok 4. Utwórz i powiąż zasadę dostępu uprawnień

Utwórz globalną zasadę dostępu uprawnień:

# create global IAM access policy
gcloud iam access-policies create ${UAP_POLICY_NAME} \
  --details-rules=cfg/uap-rules.json \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Powiąż zasadę dostępu z PROJECT_GOVERNANCE:

# bind access policy to governance project
gcloud iam policy-bindings create ${UAP_BINDING_NAME} \
  --policy="projects/${PROJECT_GOVERNANCE}/locations/global/accessPolicies/${UAP_POLICY_NAME}" \
  --target-resource="//cloudresourcemanager.googleapis.com/projects/${PROJECT_GOVERNANCE}" \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Sprawdź, czy powiązanie zasady jest aktywne:

# verify policy binding
gcloud iam policy-bindings describe ${UAP_BINDING_NAME} \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Przykładowe dane wyjściowe:

name: projects/${PROJECT_GOVERNANCE}/locations/global/policyBindings/uap-binding-centralized-agw
policy: projects/${PROJECT_GOVERNANCE}/locations/global/accessPolicies/uap-policy-centralized-agw
policyKind: ACCESS_POLICY
target:
  resource: //cloudresourcemanager.googleapis.com/projects/${PROJECT_GOVERNANCE}

Ruch wychodzący podstawowego interfejsu Google Cloud API jest teraz bezpiecznie autoryzowany we wszystkich 3 projektach w trybie ścisłym (ENFORCE).

To kończy konfigurację autoryzacji bramy. Przejdź do sekcji Konfigurowanie uprawnień obejmujących wiele projektów.

6. Uprawnienia między projektami

Konfigurowanie uprawnień między projektami

W tej topologii obejmującej wiele projektów środowiska wykonawcze Agent Runtime znajdują się w projektach podrzędnych (PROJECT_CONCIERGE i PROJECT_SELLERS), a centralna Brama agentów i Agent Registry znajdują się w projekcie PROJECT_GOVERNANCE.

Projekty Google Cloud są odizolowanymi obwodami zabezpieczeń, dlatego dostęp między projektami musi być wyraźnie przyznany na 2 warstwach operacyjnych:

  1. Platforma sterująca (czas wdrażania): podczas wdrażania kontenera agenta skonfigurowanego za pomocą --agent-gateway-config Agent Runtime Service Agent (service-@gcp-sa-aiplatform.iam.gserviceaccount.com) projektu podrzędnego musi podłączyć kontener do bramy centralnej. Tworzymy minimalną rolę niestandardową (ar_agw_cross_project_sa) przyznającą uprawnienia networkservices.agentGateways.use, get i operations.get w usłudze PROJECT_GOVERNANCE.
  2. Płaszczyzna danych (wykonywanie w środowisku wykonawczym):
    • Wykrywanie katalogu: tożsamości promienia wymagają uprawnienia roles/agentregistry.viewer w PROJECT_GOVERNANCE, aby dynamicznie rozpoznawać docelowe punkty końcowe agenta.
    • Wywołanie docelowe: agent Concierge potrzebuje uprawnień roles/aiplatform.user w PROJECT_SELLERS, aby wykonywać zapytania do silników rozumowania sprzedawcy.

Tworzenie niestandardowej roli uprawnień w: PROJECT_GOVERNANCE

# create custom role in central governance project
gcloud iam roles create ar_agw_cross_project_sa \
  --project=${PROJECT_GOVERNANCE} \
  --title="Runtime Agent Gateway Cross-Project SA" \
  --description="Custom role for cross-project service agents to access Central Agent Gateway" \
  --permissions="networkservices.agentGateways.get,networkservices.agentGateways.use,networkservices.operations.get" \
  --stage="GA"

Przypisywanie roli niestandardowej do agentów usługi Agent Runtime

# 1. ensure aiplatform service identities are provisioned across all projects
for PROJ in ${PROJECT_GOVERNANCE} ${PROJECT_CONCIERGE} ${PROJECT_SELLERS}; do
  gcloud beta services identity create --service=aiplatform.googleapis.com --project=${PROJ}
done
# 2. derive aiplatform service agent emails
export CONCIERGE_AI_SA="service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform.iam.gserviceaccount.com"
export CONCIERGE_RE_SA="service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
export CONCIERGE_COMPUTE_SA="${PROJECT_NUMBER_CONCIERGE}-compute@developer.gserviceaccount.com"

export SELLERS_AI_SA="service-${PROJECT_NUMBER_SELLERS}@gcp-sa-aiplatform.iam.gserviceaccount.com"
export SELLERS_RE_SA="service-${PROJECT_NUMBER_SELLERS}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
export SELLERS_COMPUTE_SA="${PROJECT_NUMBER_SELLERS}-compute@developer.gserviceaccount.com"
# 3. grant custom role & network viewer to Concierge and Sellers Service Agents
for SA in ${CONCIERGE_AI_SA} ${SELLERS_AI_SA}; do
  gcloud projects add-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="serviceAccount:${SA}" \
    --role="projects/${PROJECT_GOVERNANCE}/roles/ar_agw_cross_project_sa" \
    --condition=None

  gcloud projects add-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="serviceAccount:${SA}" \
    --role="roles/networkservices.viewer" \
    --condition=None
done
# 4. grant agent registry viewer on Governance Project for dynamic autodiscovery
for MEMBER in "serviceAccount:${CONCIERGE_AI_SA}" "serviceAccount:${CONCIERGE_RE_SA}" "serviceAccount:${CONCIERGE_COMPUTE_SA}" "serviceAccount:${SELLERS_AI_SA}" "serviceAccount:${SELLERS_RE_SA}" "serviceAccount:${SELLERS_COMPUTE_SA}" "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}" "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_SELLERS}"; do
  gcloud projects add-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="${MEMBER}" \
    --role="roles/agentregistry.viewer" \
    --condition=None
done
# 5. grant agent project viewer on Governance Project for dynamic autodiscovery
for SA in ${CONCIERGE_COMPUTE_SA} ${CONCIERGE_AI_SA}; do
  gcloud projects add-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="serviceAccount:${SA}" \
    --role="roles/viewer" \
    --condition=None
done
# 6. grant aitplatform user on Sellers project to Concierge for cross-project A2A invocation
for MEMBER in "serviceAccount:${CONCIERGE_AI_SA}" "serviceAccount:${CONCIERGE_RE_SA}" "serviceAccount:${CONCIERGE_COMPUTE_SA}" "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}"; do
  gcloud projects add-iam-policy-binding ${PROJECT_SELLERS} \
    --member="${MEMBER}" \
    --role="roles/aiplatform.user" \
    --condition=None
done

To już koniec konfiguracji uprawnień obejmujących wiele projektów. Przejdź do sekcji Wdrażanie agentów sprzedawcy i Concierge.

7. Agent Runtime

Wdrażanie agentów sprzedawcy i usługi Concierge

Baza kodu aplikacji z wieloma agentami i skrypty wdrażania używane w tym ćwiczeniu są przechowywane w zdalnym repozytorium Google Cloud na GitHubie. Te kroki spowodują sklonowanie repozytorium lokalnie, skopiowanie niezbędnych plików do bieżącej struktury katalogu roboczego, wyczyszczenie plików tymczasowych i zainstalowanie zależności za pomocą uv.

Pobieranie artefaktów zdalnych

# clone remote repository to temp local dir
git clone https://github.com/GoogleCloudPlatform/cloud-networking-solutions.git ./temp_agw_multiproject
# copy multi-agent application files to current working directory
cp -r temp_agw_multiproject/codelabs/agw-multiproject ./cross-project-multiagent
# remove temporary directory
rm -rf temp_agw_multiproject
# install dependencies
uv sync --directory ./cross-project-multiagent

Tworzenie współdzielonego centralnego zasobnika przejściowego

# create shared central staging bucket
gcloud storage buckets create gs://${PROJECT_GOVERNANCE}-shared-staging \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION}
# grant cross-project read/write access to runtime service agents
gcloud storage buckets add-iam-policy-binding gs://${PROJECT_GOVERNANCE}-shared-staging \
  --member="serviceAccount:service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

gcloud storage buckets add-iam-policy-binding gs://${PROJECT_GOVERNANCE}-shared-staging \
  --member="serviceAccount:service-${PROJECT_NUMBER_SELLERS}@gcp-sa-aiplatform.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

Jak działa wiązanie bramy agentów między projektami

W tym kroku wdrożysz agentów sprzedawcy w projekcie promienia (PROJECT_SELLERS) i skonfigurujesz ich tak, aby kierowali ruch wychodzący przez centralną bramę agentów w projekcie PROJECT_GOVERNANCE:

# !-- for example purposes -- NOT a command to execute --!
# snippet from deploy_burger.py
burger_config = {
    "staging_bucket": staging_bucket_uri,
    "gcs_dir_name": "burger_agent",
    "display_name": "burger-seller-agent-adk",
    "identity_type": "AGENT_IDENTITY",
    "agent_gateway_config": {
        "agent_to_anywhere_config": {
            "agent_gateway": f"projects/{args.governance_project}/locations/{args.region}/agentGateways/{args.gateway}"
        }
    },
}
deployed_burger = client.agent_engines.create(agent=burger_playground, config=burger_config)

Ponieważ reguła 1 została ustanowiona wcześniej w ujednoliconych zasadach dostępu, żądania inicjowania kontenera do interfejsów Google Cloud API są dozwolone przez bramę bez przerw.

Wdrażanie agentów sprzedających burgery i pizzę w PROJECT_SELLERS

# 1. deploy Burger Seller Agent to PROJECT_SELLERS
uv run --directory ./cross-project-multiagent python deploy_burger.py \
  --project=${PROJECT_SELLERS} \
  --region=${REGION} \
  --governance-project=${PROJECT_GOVERNANCE} \
  --gateway=projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agentGateways/${AGW_NAME}
# 2. deploy Pizza Seller Agent to PROJECT_SELLERS
uv run --directory ./cross-project-multiagent python deploy_pizza.py \
  --project=${PROJECT_SELLERS} \
  --region=${REGION} \
  --governance-project=${PROJECT_GOVERNANCE} \
  --gateway=projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agentGateways/${AGW_NAME}

Sprawdzanie routingu bramy sprzedawcy

# retrieve deployed seller reasoning engine IDs
export BURGER_ENGINE_ID=$(grep BURGER_SELLER_AGENT_ID cross-project-multiagent/burger_agent.env | awk -F'/' '{print $NF}')
export PIZZA_ENGINE_ID=$(grep PIZZA_SELLER_AGENT_ID cross-project-multiagent/pizza_agent.env | awk -F'/' '{print $NF}')

echo "Burger Engine ID: ${BURGER_ENGINE_ID}"
echo "Pizza Engine ID:  ${PIZZA_ENGINE_ID}"
# inspect runtime configuration for both Seller Agents
for ENGINE_ID in ${BURGER_ENGINE_ID} ${PIZZA_ENGINE_ID}; do
  curl -s -X GET "https://${REGION}-aiplatform.googleapis.com/v1beta1/projects/${PROJECT_SELLERS}/locations/${REGION}/reasoningEngines/${ENGINE_ID}" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "Content-Type: application/json" \
    | jq '{displayName: .displayName, identityType: .spec.identityType, effectiveIdentity: .spec.effectiveIdentity, agentGatewayConfig: .spec.deploymentSpec.agentGatewayConfig}'
done

Wdrażanie agenta asystenta zakupów w: PROJECT_CONCIERGE

# deploy Purchasing Concierge to PROJECT_CONCIERGE
uv run --directory ./cross-project-multiagent python deploy_concierge_adk.py \
  --project=${PROJECT_CONCIERGE} \
  --region=${REGION} \
  --staging-bucket=gs://${PROJECT_GOVERNANCE}-shared-staging \
  --gateway-name=${AGW_NAME} \
  --gateway-project=${PROJECT_GOVERNANCE}

Weryfikacja routingu bramy zakupowej

# retrieve Concierge engine ID
export CONCIERGE_ENGINE_ID=$(grep CONCIERGE_AGENT_ID cross-project-multiagent/concierge_agent.env | awk -F'/' '{print $NF}')
echo "Concierge Engine ID: ${CONCIERGE_ENGINE_ID}"
# inspect runtime configuration for Purchasing Concierge
curl -s -X GET "https://${REGION}-aiplatform.googleapis.com/v1beta1/projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}" \
  -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
  -H "Content-Type: application/json" \
  | jq '{displayName: .displayName, identityType: .spec.identityType, effectiveIdentity: .spec.effectiveIdentity, agentGatewayConfig: .spec.deploymentSpec.agentGatewayConfig}'

Dane wyjściowe powinny zawierać tożsamość i projekt Agent Runtime agenta Concierge oraz powiązanie z Bramą agentów projektu zarządzania.

{
  "displayName": "purchasing-concierge-adk",
  "identityType": "AGENT_IDENTITY",
  "effectiveIdentity": "agents.global.org-${ORG_ID}.system.id.goog/resources/aiplatform/projects/${PROJECT_CONCIERGE}/locations/us-central1/reasoningEngines/${CONCIERGE_ENGINE_ID}",
  "agentGatewayConfig": {
    "agentToAnywhereConfig": {
      "agentGateway": "projects/${PROJECT_GOVERNANCE}/locations/us-central1/agentGateways/centralized-agw"
    }
  }
}

To już koniec wdrażania agentów… Przejdźmy do sekcji Rejestrowanie agentów w centralnym rejestrze agentów.

8. Rejestr obejmujący wiele projektów

Rejestrowanie agentów w centralnym rejestrze agentów

Zarejestruj wszystkie 3 agenty w centralnym rejestrze agentów w regionie PROJECT_GOVERNANCE, używając regionalnych punktów końcowych mTLS między projektami i numerycznych numerów projektów.

Rejestrowanie usług jako agentów innych niż agent A2A w Agent Registry

# 1. register Burger Seller Agent
gcloud agent-registry services create burger-seller-agent \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} \
  --display-name="Burger Seller Agent" \
  --description="Specialist agent that sells burgers and fries" \
  --agent-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${BURGER_ENGINE_ID}:query
# 2. register Pizza Seller Agent
gcloud agent-registry services create pizza-seller-agent \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} \
  --display-name="Pizza Seller Agent" \
  --description="Specialist agent that sells pizzas and pasta" \
  --agent-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_SELLERS}/locations/${REGION}/reasoningEngines/${PIZZA_ENGINE_ID}:query
# 3. register Purchasing Concierge Agent
gcloud agent-registry services create purchasing-concierge-adk \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} \
  --display-name="Purchasing Concierge Agent" \
  --description="Orchestrator concierge agent that routes purchasing requests" \
  --agent-spec-type=no-spec \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query \
  --interfaces=protocolBinding=JSONRPC,url=https://${REGION}-aiplatform.mtls.googleapis.com:443/v1beta1/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}:query

Przechwytywanie identyfikatorów bazowych rejestru agentów

# capture underlying Agent Registry Agent UUIDs
export BURGER_AGENT_ID=$(gcloud agent-registry services describe burger-seller-agent --project=${PROJECT_GOVERNANCE} --location=${REGION} --format="value(registryResource)" | awk -F'/' '{print $NF}')
export PIZZA_AGENT_ID=$(gcloud agent-registry services describe pizza-seller-agent --project=${PROJECT_GOVERNANCE} --location=${REGION} --format="value(registryResource)" | awk -F'/' '{print $NF}')
export CONCIERGE_AGENT_ID=$(gcloud agent-registry services describe purchasing-concierge-adk --project=${PROJECT_GOVERNANCE} --location=${REGION} --format="value(registryResource)" | awk -F'/' '{print $NF}')

echo "Burger Agent ID:    ${BURGER_AGENT_ID}"
echo "Pizza Agent ID:     ${PIZZA_AGENT_ID}"
echo "Concierge Agent ID: ${CONCIERGE_AGENT_ID}"

To kończy konfigurację rejestru. Przejdź do sekcji Konfigurowanie zasad ruchu wychodzącego A2A.

9. Zasady UAP

Konfigurowanie zasad ruchu wychodzącego A2A w ujednoliconych zasadach dostępu

W architekturze domyślnej odmowy bramy agentów w trybie ścisłym ENFORCE:

  1. Reguła 1 (podstawowe interfejsy Google Cloud API): umożliwia kontenerom agentów we wszystkich 3 projektach dostęp do core-gapi-services.
  2. Reguła 2 (Burger Seller Agent: ALLOW): zezwala instancji Purchasing Concierge Agent na wywoływanie agenta Burger Seller Agent.
  3. Agent sprzedawcy pizzy (domyślnie ODMOWA): celowo pominięty w regułach zasad. W trybie ENFORCE (failOpen: false) każda próba wywołania sprzedawcy pizzy przez Concierge zostanie natychmiast zakończona na granicy bramy z błędem HTTP 403 Forbidden.

Formułowanie tożsamości agenta Concierge

# formulate the exact SPIFFE machine identity for the Concierge Agent
export CONCIERGE_SPIFFE_PRINCIPAL="principal://agents.global.org-${ORG_ID}.system.id.goog/resources/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}"
echo "Concierge SPIFFE Principal: ${CONCIERGE_SPIFFE_PRINCIPAL}"

Zaktualizuj plik manifestu aktualizacji za pomocą reguł 1 i 2

Aby bezpiecznie zaktualizować zasadę dostępu bez nadpisywania istniejących reguł, zdefiniuj pełny skonsolidowany plik manifestu zawierający Regułę 1 (podstawowe interfejsy API Google) i Regułę 2 (Burger Seller Agent):

# create consolidated policy manifest with Rule 1 and Rule 2
cat > cfg/uap-rules-consolidated.json << EOF
[
  {
    "description": "Rule 1: Allow agent runtimes across all 3 projects to reach Core Google APIs",
    "effect": "ALLOW",
    "principals": [
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_GOVERNANCE}",
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}",
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_SELLERS}"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true && \
         destination.agent_registry.resource_type == 'ENDPOINT' && ( \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/endpoints/core-gapi-services' || \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/endpoints/${ENDPOINT_ID}' || \
         destination.agent_registry.endpoint.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/${REGION}/endpoints/${ENDPOINT_ID}')"
      }
    }
  },
  {
    "description": "Rule 2: Allow Purchasing Concierge to invoke Burger Seller Agent via Central Gateway",
    "effect": "ALLOW",
    "principals": [
      "${CONCIERGE_SPIFFE_PRINCIPAL}",
      "principal://agents.global.org-${ORG_ID}.system.id.goog/attribute.agentIdentity/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}",
      "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.agentIdentity/projects/${PROJECT_NUMBER_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true && \
         destination.agent_registry.resource_type == 'AGENT' && ( \
         destination.agent_registry.agent.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agents/burger-seller-agent' || \
         destination.agent_registry.agent.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agents/${BURGER_AGENT_ID}' || \
         destination.agent_registry.agent.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/${REGION}/agents/${BURGER_AGENT_ID}')"
      }
    }
  }
]
EOF

Zastosuj zaktualizowaną zasadę dostępu

Zastosuj skonsolidowany plik manifestu reguł deklaratywnie za pomocą polecenia --details-rules:

# update IAM access policy with consolidated manifest (Rule 1 + Rule 2)
gcloud iam access-policies update ${UAP_POLICY_NAME} \
  --details-rules=cfg/uap-rules-consolidated.json \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Sprawdzanie szczegółów zasady dostępu uprawnień

# inspect updated access policy
gcloud iam access-policies describe ${UAP_POLICY_NAME} \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Przykładowe dane wyjściowe:

details:
  rules:
  - conditions:
      iap.googleapis.com:
        expression: destination.is_registered == true && destination.agent_registry.resource_type
          == 'ENDPOINT' && (destination.agent_registry.endpoint.name == 'projects/${PROJECT_GOVERNANCE}/locations/us-central1/endpoints/core-gapi-services'
          || destination.agent_registry.endpoint.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/us-central1/endpoints/${ENDPOINT_ID}')
    description: 'Rule 1: Allow agent runtimes across all 3 projects to reach Core
      Google APIs'
    effect: ALLOW
    operation:
      permissions:
      - iap.googleapis.com/resources.egressViaIAP
    principals:
    - principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_GOVERNANCE}
    - principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}
    - principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_SELLERS}
  - conditions:
      iap.googleapis.com:
        expression: (destination.is_registered == true) && (destination.agent_registry.resource_type
          == 'AGENT') && (destination.agent_registry.agent.name == 'projects/${PROJECT_GOVERNANCE}/locations/us-central1/agents/burger-seller-agent'
          || destination.agent_registry.agent.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/us-central1/agents/${BURGER_AGENT_ID}')
    description: 'Rule 2: Allow Purchasing Concierge to invoke Burger Seller Agent
      via Central Gateway'
    effect: ALLOW
    operation:
      permissions:
      - iap.googleapis.com/resources.egressViaIAP
    principals:
    - principal://agents.global.org-${ORG_ID}.system.id.goog/resources/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}/locations/us-central1/reasoningEngines/${CONCIERGE_ENGINE_ID}
name: projects/${PROJECT_GOVERNANCE}/locations/global/accessPolicies/uap-policy-centralized-agw

To już koniec konfiguracji zasad. Przejdź do sekcji Testowanie i weryfikowanie zasad zarządzania.

10. Sprawdzanie zasad

Testowanie i weryfikowanie zasad zarządzania za pomocą Cloud Logging

W tej sekcji przetestujesz interakcje między agentami w różnych projektach w Laboratorium AI Agent Runtime, zaobserwujesz rzeczywiste blokowanie obwodowe HTTP 403 Forbidden w trybie ścisłym ENFORCE, zmodyfikujesz ujednolicone zasady dostępu na żywo i sprawdzisz natychmiastowe zatwierdzanie zamówień.

Krok 1. Otwórz Laboratorium AI w Agent Runtime w PROJECT_CONCIERGE

  1. Otwórz konsolę Google Cloud.
  2. Na górnym pasku selektora projektów przełącz się na PROJECT_CONCIERGE.
  3. W menu nawigacyjnym kliknij Agent Platform > Agenty > Wdrożenia.
  4. Kliknij purchasing-concierge-adk.
  5. Wybierz Playground, aby otworzyć interaktywny interfejs czatu po prawej stronie ekranu.

Krok 2. Przetestuj zamówienie burgera (dopasowanie do reguły 2 -> 200 OK)

W oknie czatu w Playground prześlij tego prompta:

I would like 10 Classic Cheeseburgers. Place this order now.

Jeśli wymagana jest odpowiedź z potwierdzeniem, prześlij tę odpowiedź:

Confirmed, please place the order.

Możesz też przetestować programowo z Cloud Shell lub terminala:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input={'message': 'I would like 22 Spicy Cajun Burgers please. Place this order now.'})
print(response)
"

Jeśli potrzebujesz odpowiedzi z potwierdzeniem, użyj tego polecenia:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input='Yes please place the order now.')
print(response['text'])
"

Co dzieje się za kulisami:

  1. Dynamiczne wykrywanie: podczas uruchamiania sesji usługa Purchasing Concierge wysłała zapytanie do centralnego rejestru agentów w regionie PROJECT_GOVERNANCE (za pomocą core-gapi-services przez Bramę agentów autoryzowaną przez regułę 1), aby wykryć regionalny punkt końcowy mTLS dla usługi burger-seller-agent.
  2. Rozpoznawanie intencji i wywoływanie A2A: Gemini w usłudze Purchasing Concierge analizuje intencję zamówienia jedzenia i wywołuje agenta Burger Seller Agent za pomocą wychodzącego wywołania RPC do https://${REGION}-aiplatform.mtls.googleapis.com/.../reasoningEngines/${BURGER_ENGINE_ID}.
  3. Przechwytywanie przez bramę i propagacja SPIFFE: ruch wychodzący jest przechwytywany przez agent_gateway_config i kierowany do centralnej bramy agentów w PROJECT_GOVERNANCE, przenosząc kryptograficzną tożsamość SPIFFE usługi Concierge (principal://...).
  4. Ocena zasad IAP w wersji 2: centralna brama agentów wywołuje rozszerzenie autoryzacji IAP (authzExtension). IAP w wersji 2 ocenia regułę 2 w ujednoliconych zasadach dostępu uprawnień. Ponieważ element wywołujący pasuje do ${CONCIERGE_SPIFFE_PRINCIPAL}, a element docelowy pasuje do burger-seller-agent, IAP zwraca ALLOW (granted: true).
  5. Wykonywanie między projektami: brama agentów przekazuje autoryzowane żądanie między projektami do PROJECT_SELLERS, gdzie silnik rozumowania Burger Seller przetwarza zamówienie i zwraca potwierdzenie.

Oczekiwana odpowiedź:

Your order for 10 Classic Cheeseburger(s) has been placed!
Here is a summary of your order:
- 10x Classic Cheeseburger @ IDR 85,000/each = IDR 850,000

Total: IDR 850,000
Your Order ID is: e8f9c732-f347-4cc4-acff-cfe09ccbeddd

Krok 3. Sprawdź dzienniki kontrolne bramy agentów i IAP v2 (HTTP 200 / ALLOWED)

Zapytanie o logi żądań bramy agentów w PROJECT_GOVERNANCE:

# query Agent Gateway logs for successful 200 OK requests
gcloud logging read "
  logName=\"projects/${PROJECT_GOVERNANCE}/logs/networkservices.googleapis.com%2Fgateway_requests\"
  AND jsonPayload.authzPolicyInfo.result=\"ALLOWED\"
" \
  --project="${PROJECT_GOVERNANCE}" \
  --limit=10 \
  --format="table(
    timestamp.date('%H:%M:%S'):label=TIME,
    httpRequest.requestMethod:label=METHOD,
    httpRequest.status:label=STATUS,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ,
    httpRequest.requestUrl:label=URL
  )"

Logi powinny rejestrować ruch wychodzący z obu projektów typu spoke (PROJECT_CONCIERGE i PROJECT_SELLERS) z polami ruchu wychodzącego, takimi jak wywołania wnioskowania Gemini (generateContent), dane telemetryczne Cloud Trace (/v1/traces) i wyszukiwania danych uwierzytelniających uprawnień, które są w przejrzysty sposób przechwytywane i autoryzowane przez regułę 1 (core-gapi-services).

Wyślij zapytanie do logów dostępu do danych logów kontrolnych Cloud IAP w wersji 2, aby sprawdzić wersję zasad POLICY_VERSION_V2:

# query IAP v2 audit logs with shortened principal and resource fields
gcloud logging read "
  logName=\"projects/${PROJECT_GOVERNANCE}/logs/cloudaudit.googleapis.com%2Fdata_access\"
  AND protoPayload.serviceName=\"iap.googleapis.com\"
" \
  --project="${PROJECT_GOVERNANCE}" \
  --limit=5 \
  --format="table(
    timestamp.date('%H:%M:%S'):label=TIME,
    protoPayload.authenticationInfo.principalSubject.sub('\.global\..*\/reasoningEngines\/', '.[...]/reasoningEngines/'):label=CALLER,
    protoPayload.authorizationInfo[0].granted:label=GRANTED,
    protoPayload.metadata.destination.agent_registry.resource_type.basename():label=TYPE,
    protoPayload.metadata.destination.agent_registry.resource_id.basename():label=RESOURCE_ID,
    protoPayload.authorizationInfo[0].permission.basename():label=PERMISSION
  )"

Przykładowe dane wyjściowe:

TIME      CALLER                                                            GRANTED  TYPE      RESOURCE_ID     PERMISSION
HH:MM:SS  principal://agents.[...]/reasoningEngines/${CONCIERGE_ENGINE_ID}  True     Endpoint  ${ENDPOINT_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${BURGER_ENGINE_ID}     True     Endpoint  ${ENDPOINT_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${CONCIERGE_ENGINE_ID}  True     Endpoint  ${ENDPOINT_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${BURGER_ENGINE_ID}     True     Endpoint  ${ENDPOINT_ID}  resources.egressViaIAP

Krok 4. Przetestuj zamawianie pizzy (domyślna odmowa -> HTTP 403 Forbidden ENFORCED)

W tym samym oknie czatu w Playground prześlij tego prompta z zamówieniem pizzy:

I would like 10 BBQ Chicken Pizzas. Place this order now.

Jeśli wymagana jest odpowiedź z potwierdzeniem, prześlij tę odpowiedź:

Confirmed, please place the order.

Możesz też przetestować programowo z Cloud Shell lub terminala:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input='I would like 8 Hawaiian pizzas, please. Place this order now.')
print(response)
"

Jeśli potrzebujesz odpowiedzi z potwierdzeniem, użyj tego polecenia:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input='Yes please place the order now.')
print(response['text'])
"

Oczekiwana odpowiedź:

I apologize, but I am unable to process that request at the moment. It seems
there was an issue connecting to the pizza seller agent. Please try again later.

Co dzieje się za kulisami:

  1. Dynamiczne wykrywanie: agent obsługi zakupów rozwiązał punkt końcowy pizza-seller-agent z centralnego rejestru agentów podczas uruchamiania.
  2. Rozpoznanie intencji i wywołanie A2A: Gemini w usłudze Concierge ds. zakupów próbuje wysłać prośbę o zamówienie pizzy do punktu końcowego sprzedawcy pizzy w PROJECT_SELLERS.
  3. Przechwytywanie przez bramę: wychodzące wywołanie RPC jest przechwytywane przez agent_gateway_config i kierowane do centralnej bramy agentów.
  4. Ocena zasad IAP w wersji 2 (domyślna odmowa): centralna brama agentów wywołuje IAP w wersji 2. Ponieważ w ujednoliconych zasadach dostępu nie ma reguły pasującej do pizza-seller-agent, IAP zwraca błąd DENY (granted: false).
  5. Rygorystyczna blokada obwodowa: ponieważ rozszerzenie autoryzacji jest w trybie WYMUSZANIA (failOpen: false), centralna brama agentów natychmiast przerywa połączenie wychodzące i zwraca błąd HTTP 403 Forbidden. Ruch nigdy nie opuszcza bramy i nie dociera do PROJECT_SELLERS.

Krok 5. Sprawdź dzienniki bramy agentów pod kątem zablokowanych żądań (HTTP 403 / DENIED)

# query Agent Gateway logs for blocked 403 requests
gcloud logging read "
  logName=\"projects/${PROJECT_GOVERNANCE}/logs/networkservices.googleapis.com%2Fgateway_requests\"
  AND httpRequest.status=403
" \
  --project="${PROJECT_GOVERNANCE}" \
  --limit=5 \
  --format="table(
    timestamp.date('%H:%M:%S'):label=TIME,
    httpRequest.requestMethod:label=METHOD,
    httpRequest.status:label=STATUS,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ,
    httpRequest.requestUrl:label=URL
  )"

Przykładowe dane wyjściowe logu odmowy:

TIME      METHOD  STATUS  AUTHZ   URL
HH:MM:SS  POST    403     DENIED  https://us-central1-aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_SELLERS}/locations/us-central1/reasoningEngines/${PIZZA_ENGINE_ID}:query

Wyślij zapytanie do logów kontrolnych dostępu do danych IAP w wersji 2, aby sprawdzić decyzję o odmowie:

# query IAP v2 audit logs with shortened principal and resource fields
gcloud logging read "
  logName=\"projects/${PROJECT_GOVERNANCE}/logs/cloudaudit.googleapis.com%2Fdata_access\"
  AND protoPayload.serviceName=\"iap.googleapis.com\"
" \
  --project="${PROJECT_GOVERNANCE}" \
  --limit=5 \
  --format="table(
    timestamp.date('%H:%M:%S'):label=TIME,
    protoPayload.authenticationInfo.principalSubject.sub('\.global\..*\/reasoningEngines\/', '.[...]/reasoningEngines/'):label=CALLER,
    protoPayload.authorizationInfo[0].granted:label=GRANTED,
    protoPayload.metadata.destination.agent_registry.resource_type.basename():label=TYPE,
    protoPayload.metadata.destination.agent_registry.resource_id.basename():label=RESOURCE_ID,
    protoPayload.authorizationInfo[0].permission.basename():label=PERMISSION
  )"

Przykładowe dane wyjściowe logu kontrolnego odmowy:

TIME      CALLER                                                            GRANTED  TYPE      RESOURCE_ID     PERMISSION
HH:MM:SS  principal://agents.[...]/reasoningEngines/${PIZZA_ENGINE_ID}      True     Endpoint  ${REGISTRY_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${PIZZA_ENGINE_ID}      True     Endpoint  ${REGISTRY_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${CONCIERGE_ENGINE_ID}  False    Agent     ${REGISTRY_ID}  resources.egressViaIAP
HH:MM:SS  principal://agents.[...]/reasoningEngines/${PIZZA_ENGINE_ID}      True     Endpoint  ${REGISTRY_ID}  resources.egressViaIAP

Krok 6. Dynamicznie przyznaj agentowi Pizza dostęp do ruchu wychodzącego

Utwórz nową cfg/uap-rules-update-3.json, aby uwzględnić Regułę 1 (podstawowe interfejsy API), Regułę 2 (agent sprzedawcy burgerów) i nową Regułę 3 (agent sprzedawcy pizzy).

# create addendum to update policy manifest with Rule 3 for Pizza Agent
cat > cfg/uap-rules-update-3.json << EOF
[
  {
    "description": "Rule 3: Allow Purchasing Concierge to invoke Pizza Seller Agent via Central Gateway",
    "effect": "ALLOW",
    "principals": [
      "${CONCIERGE_SPIFFE_PRINCIPAL}"
    ],
    "operation": {
      "permissions": [
        "iap.googleapis.com/resources.egressViaIAP"
      ]
    },
    "conditions": {
      "iap.googleapis.com": {
        "expression": \
        "destination.is_registered == true && \
         destination.agent_registry.resource_type == 'AGENT' && ( \
         destination.agent_registry.agent.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agents/pizza-seller-agent' || \
         destination.agent_registry.agent.name == 'projects/${PROJECT_GOVERNANCE}/locations/${REGION}/agents/${PIZZA_AGENT_ID}' || \
         destination.agent_registry.agent.name == 'projects/${PROJECT_NUMBER_GOVERNANCE}/locations/${REGION}/agents/${PIZZA_AGENT_ID}')"
      }
    }
  }
]
EOF

Zastosuj aktualizację zasad na żywo:

# update IAM access policy with Pizza rule
gcloud iam access-policies update ${UAP_POLICY_NAME} \
  --add-details-rules=cfg/uap-rules-update-3.json \
  --project=${PROJECT_GOVERNANCE} \
  --location=global

Krok 7. Ponownie wyślij zapytanie do agenta Pizza (natychmiastowy sukces 200 OK)

W oknie czatu w Playgroundzie ponownie prześlij prompta z zamówieniem pizzy:

I would like 10 BBQ Chicken Pizzas. Place this order now.

Jeśli wymagana jest odpowiedź z potwierdzeniem, prześlij tę odpowiedź:

Confirmed, please place the order.

Możesz też przetestować programowo z Cloud Shell lub terminala:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input='I would like 11 Veggie pizzas, please. Place this order now.')
print(response)
"

Jeśli potrzebujesz odpowiedzi z potwierdzeniem, użyj tego polecenia:

uv run --directory ./cross-project-multiagent python -c "
import vertexai
from vertexai.preview import reasoning_engines
vertexai.init(project='${PROJECT_CONCIERGE}', location='${REGION}')
agent = reasoning_engines.ReasoningEngine('projects/${PROJECT_CONCIERGE}/locations/${REGION}/reasoningEngines/${CONCIERGE_ENGINE_ID}')
response = agent.query(input='Yes please place the order now.')
print(response['text'])
"

Oczekiwana odpowiedź:

Your order has been placed!

**Order ID:** 8d6c13d7-31dc-4d80-b6a7-80d1e50b6411

**Order Details:**
*   10 x BBQ Chicken Pizza @ IDR 130,000 each = IDR 1,300,000

**Total: IDR 1,300,000**

Co dzieje się za kulisami:

  1. Dynamiczne odświeżanie zasad: aktualizacja ujednoliconych zasad dostępu uprawnień IAM zaczyna obowiązywać natychmiast w silniku oceny IAP bez przestojów i bez konieczności ponownego wdrażania kontenerów.
  2. Wywołanie A2A: Concierge wysyła żądanie przez centralną bramę agentów.
  3. Ocena zasad IAP w wersji 2 (zatwierdzenie): IAP w wersji 2 dopasowuje regułę 3, weryfikuje tożsamość elementu wywołującego i docelowe wyrażenie CEL, a następnie zwraca ALLOW (granted: true).
  4. Wykonywanie w różnych projektach: centralna brama agentów przekazuje autoryzowany ruch do PROJECT_SELLERS, gdzie sprzedawca pizzy przetwarza zamówienie.

Krok 8. Sprawdź dzienniki bramy agentów pod kątem przyznanych żądań dotyczących pizzy

# query Agent Gateway logs for successful 200 OK requests
gcloud logging read "
  logName=\"projects/${PROJECT_GOVERNANCE}/logs/networkservices.googleapis.com%2Fgateway_requests\"
  AND jsonPayload.authzPolicyInfo.result=\"ALLOWED\"
" \
  --project="${PROJECT_GOVERNANCE}" \
  --limit=10 \
  --format="table(
    timestamp.date('%H:%M:%S'):label=TIME,
    httpRequest.requestMethod:label=METHOD,
    httpRequest.status:label=STATUS,
    jsonPayload.authzPolicyInfo.result:label=AUTHZ,
    httpRequest.requestUrl:label=URL
  )"

Przykładowe dane wyjściowe logu przyznania dostępu:

TIME      METHOD  STATUS  AUTHZ    URL
HH:MM:SS  POST    200     ALLOWED  https://aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_SELLERS}/locations/global/publishers/google/models/gemini-3.5-flash:generateContent
HH:MM:SS  POST    200     ALLOWED  https://us-central1-aiplatform.mtls.googleapis.com/v1beta1/projects/${PROJECT_SELLERS}/locations/us-central1/reasoningEngines/${PIZZA_ENGINE_ID}:query

To koniec testowania i weryfikacji. Przejdź do sekcji Czyszczenie.

11. Czyszczenie

Aby uniknąć obciążenia konta Google Cloud opłatami za zasoby zużyte w tym ćwiczeniu, wykonaj czynności związane z usuwaniem w ścisłej kolejności odwrotnej do zależności:

1. Czyszczenie wdrożeń silnika rozumowania

Uruchom dołączony skrypt cleanup_old_deployments.py w obu projektach środowiska wykonawczego, aby usunąć silniki rozumowania i poczekać na zakończenie ich długo trwających operacji:

# delete all Reasoning Engines deployed in Concierge and Sellers projects
uv run --directory ./cross-project-multiagent python cleanup_old_deployments.py --project=${PROJECT_CONCIERGE} --region=${REGION}
uv run --directory ./cross-project-multiagent python cleanup_old_deployments.py --project=${PROJECT_SELLERS} --region=${REGION}

Możesz też wyświetlić listę silników rozumowania i usunąć je w tekście:

uv run --directory ./cross-project-multiagent python -c '
import vertexai
import os
from vertexai.preview import reasoning_engines

region = os.environ.get("REGION", "us-central1")
for proj in [os.environ.get("PROJECT_CONCIERGE"), os.environ.get("PROJECT_SELLERS")]:
    if not proj:
        continue
    print(f"Cleaning reasoning engines in {proj}...")
    vertexai.init(project=proj, location=region)
    for eng in reasoning_engines.ReasoningEngine.list():
        print(f"  Deleting {eng.resource_name} ({eng.display_name})...")
        eng.delete()
'

2. Usuwanie usług rejestru agentów

# delete agent registry services in Central Governance Project
for SERVICE in burger-seller-agent pizza-seller-agent purchasing-concierge-adk core-gapi-services; do
  gcloud agent-registry services delete ${SERVICE} \
    --project=${PROJECT_GOVERNANCE} \
    --location=${REGION} \
    --quiet || true
done

3. Usuwanie powiązania ujednoliconej zasady dostępu i zasady dostępu uprawnień

# 1. delete IAM policy binding
gcloud -q iam policy-bindings delete ${UAP_BINDING_NAME} \
  --project=${PROJECT_GOVERNANCE} \
  --location=global || true

# 2. delete IAM access policy
gcloud -q iam access-policies delete ${UAP_POLICY_NAME} \
  --project=${PROJECT_GOVERNANCE} \
  --location=global || true

4. Usuwanie bramy agentów i zasad zabezpieczeń

# 1. delete authorization policy
gcloud beta network-security authz-policies delete ${AGW_NAME}-authz-policy-profile-iap \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE} --quiet || true

# 2. delete authorization extension
gcloud service-extensions authz-extensions delete ${AGW_NAME}-svc-ext-authz-iap \
  --location=${REGION} \
  --project=${PROJECT_GOVERNANCE} --quiet || true
# 3. delete agent gateway
gcloud network-services agent-gateways delete ${AGW_NAME} \
  --project=${PROJECT_GOVERNANCE} \
  --location=${REGION} --quiet || true

5. Usuwanie międzyprojektowych powiązań uprawnień i roli niestandardowej

# 1. remove custom role and network viewer bindings for spoke service agents
for NUM in "${PROJECT_NUMBER_CONCIERGE}" "${PROJECT_NUMBER_SELLERS}"; do
  SA="service-${NUM}@gcp-sa-aiplatform.iam.gserviceaccount.com"
  
  gcloud projects remove-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="serviceAccount:${SA}" \
    --role="projects/${PROJECT_GOVERNANCE}/roles/ar_agw_cross_project_sa" --quiet || true

  gcloud projects remove-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="serviceAccount:${SA}" \
    --role="roles/networkservices.viewer" --quiet || true
done
# 2. remove registry viewer permissions across both spoke projects
for NUM in "${PROJECT_NUMBER_CONCIERGE}" "${PROJECT_NUMBER_SELLERS}"; do
  for MEMBER in \
    "serviceAccount:service-${NUM}@gcp-sa-aiplatform.iam.gserviceaccount.com" \
    "serviceAccount:service-${NUM}@gcp-sa-aiplatform-re.iam.gserviceaccount.com" \
    "serviceAccount:${NUM}-compute@developer.gserviceaccount.com" \
    "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${NUM}"; do
      gcloud projects remove-iam-policy-binding ${PROJECT_GOVERNANCE} \
        --member="${MEMBER}" \
        --role="roles/agentregistry.viewer" --quiet || true
  done
done
# 3. remove project viewer permissions
for MEMBER in \
  "serviceAccount:${PROJECT_NUMBER_CONCIERGE}-compute@developer.gserviceaccount.com" \
  "serviceAccount:service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform.iam.gserviceaccount.com"; do
    gcloud projects remove-iam-policy-binding ${PROJECT_GOVERNANCE} \
      --member="${MEMBER}" \
      --role="roles/viewer" --quiet || true
done
# 4. remove spoke-to-spoke delegation in Sellers project
for MEMBER in \
  "serviceAccount:service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform.iam.gserviceaccount.com" \
  "serviceAccount:service-${PROJECT_NUMBER_CONCIERGE}@gcp-sa-aiplatform-re.iam.gserviceaccount.com" \
  "serviceAccount:${PROJECT_NUMBER_CONCIERGE}-compute@developer.gserviceaccount.com" \
  "principalSet://agents.global.org-${ORG_ID}.system.id.goog/attribute.platformContainer/aiplatform/projects/${PROJECT_NUMBER_CONCIERGE}"; do
    gcloud projects remove-iam-policy-binding ${PROJECT_SELLERS} \
      --member="${MEMBER}" \
      --role="roles/aiplatform.user" --quiet || true
done
# 5. delete custom IAM role after all bindings have been unlinked
gcloud iam roles delete ar_agw_cross_project_sa \
  --project=${PROJECT_GOVERNANCE} --quiet || true

Jeśli podczas fazy konfiguracji przypisano role roles/iam.accessPolicyAdmin i roles/resourcemanager.projectIamAdmin, usuń je z aktywnego konta użytkownika, aby przywrócić jak najmniejsze uprawnienia:

# 6. remove Access Policy Admin and Project IAM Admin roles from user
for ROLE in "roles/iam.accessPolicyAdmin" "roles/resourcemanager.projectIamAdmin"; do
  gcloud projects remove-iam-policy-binding ${PROJECT_GOVERNANCE} \
    --member="user:$(gcloud config get-value account)" \
    --role="${ROLE}" \
    --condition=None --quiet || true
done

6. Cofanie logowania danych kontrolnych i ograniczeń zasad organizacji

# 1. Export current Central Governance IAM policy
gcloud projects get-iam-policy ${PROJECT_GOVERNANCE} --format=json > cfg/gov_iam_policy.json
# 2. Filter out iap.googleapis.com from auditConfigs
python3 -c "
import json
with open('cfg/gov_iam_policy.json') as f:
    policy = json.load(f)

if 'auditConfigs' in policy:
    # Remove iap.googleapis.com; if nothing else remains, clear the list
    policy['auditConfigs'] = [
        ac for ac in policy['auditConfigs'] if ac.get('service') != 'iap.googleapis.com'
    ]

with open('cfg/gov_iam_policy.json', 'w') as f:
    json.dump(policy, f, indent=2)
"
# 3. Apply the updated policy to revert audit logging to default
gcloud projects set-iam-policy ${PROJECT_GOVERNANCE} cfg/gov_iam_policy.json

7. Przywracanie ograniczeń wynikających z zasad organizacji

# revert iam v3 access policy binding org policy on project to org level setting
gcloud org-policies delete iam.managed.disableAccessPolicyBinding --project=${PROJECT_GOVERNANCE}

8. Usuwanie udostępnionego zasobnika przejściowego GCS i artefaktów lokalnych

# delete central staging bucket
gcloud storage rm -r gs://${PROJECT_GOVERNANCE}-shared-staging
# remove local configuration manifests, environment files, and application
rm -rf cfg/ cross-project-multiagent/ *.env

To koniec części dotyczącej czyszczenia. Przejdź do podsumowania.

12. Podsumowanie

Gratulacje! Udało Ci się wdrożyć architekturę Agent-to-Agent (A2A) obejmującą wiele projektów w Google Cloud i zarządzać nią przy użyciu środowiska wykonawczego agenta Vertex AI, centralnej bramy agentów, rejestru agentów i ujednoliconych zasad dostępu (UAP) uprawnień.

Podsumowanie kluczowych pojęć

  • Scentralizowany obwód ruchu wychodzącego: kontenery środowiska wykonawczego połączonego fragmentu (PROJECT_CONCIERGE, PROJECT_SELLERS) są kierowane przez centralną bramę agentów w PROJECT_GOVERNANCE za pomocą agentGatewayConfig.
  • Zarządzanie deklaratywne (UAP): zastąpiliśmy rozproszone powiązania poszczególnych zasobów jedną zasadą dostępu uprawnień, którą można kontrolować i która jest oceniana w bramie przez IAP w wersji 2.
  • Tożsamość kryptograficzna: wymuszanie jak najmniejszych uprawnień do ruchu wychodzącego przy użyciu tożsamości SPIFFE kontenera (principal://...) zamiast kluczy o długim okresie ważności.
  • Dynamiczne wykrywanie usług: punkty końcowe agentów równorzędnych są rozpoznawane w czasie działania za pomocą centralnego rejestru agentów, co eliminuje konieczność kodowania na stałe adresów URL i identyfikatorów projektów.
  • Elastyczność zasad środowiska wykonawczego: zmieniono pizza-seller-agent z domyślnej odmowy (403 Forbidden) na dozwolone (200 OK) w czasie rzeczywistym przez aktualizację zasad, bez ponownego uruchamiania kontenera.

cosmopup

Cosmopup mówi: „Agenci są świetni – wykonują całą pracę między projektami, podczas gdy ja mogę skupić się na moim głównym celu: drzemce”.

Dalsze kroki i dokumentacja