こんにちは!
KDDIアイレットの新卒3年目までを対象とした取り組みとして、本日8月17日から8月28日までの期間、「Google Cloud 夏休み自由研究ブログリレー」を開催します!
このブログリレーでは、新卒3年目までのメンバーがGoogle Cloudのサービスや機能を実際に試し、その内容をアウトプットしていきます。
記念すべき初日の今回は、「ADK(Agent Development Kit)」をテーマに、実際に検証した内容をご紹介します。


ADKで作った小さなエージェントを Agent Runtime(旧 Vertex AI Agent Engine)に載せるところまでを、インフラ目線でやってみました。動かして詰まりどころを拾うことに重点を置いています。

…と、きれいに書きましたが、実態は Agent Identity の権限付与でハマり倒した記録です。構成はシンプルなのに学びが濃かったので、そのまま共有します。


この記事でやること

  • エージェント1個cloudrun-inspector)を作る
  • ツール1個list_cloud_run_services:Cloud Run のサービス一覧を read-only で取るだけ)を持たせる
  • それを Agent Runtime にデプロイして、デプロイ済みエンドポイントに1回問い合わせる

「このプロジェクトの Cloud Run サービス一覧を教えて」と聞くと、エージェントが自分でツールを呼んで答える構成です。

インフラ目線での見どころは、こう置きました。

  • 何を管理しなくて済むか(コンテナ / スケーリング / セッション)
  • 代わりに何を握るのか(エージェントの実行時 ID と、そこに付ける IAM)
  • デプロイで実際に要求されたもの、コスト、そしてハマりどころ

先に結論だけ言うと、構成はシンプルなのにハマりどころは5つ出ました。特に最後の Agent Identity への IAM 付与が本記事の山場です。


ディレクトリ構成

06.adk-agent-engine/
├── .env                 # プロジェクト / リージョン / モデル等
├── .env.example
├── agent/
│   └── agent.py         # root_agent(エージェント本体+ツール定義)
└── scripts/
    ├── _config.py       # .env を読む共通処理
    ├── 00_setup.sh      # API有効化 / GCSバケット / IAM(+ grant サブコマンド)
    ├── 01_local.py      # ローカルで動作確認
    ├── 02_deploy.py     # Agent Runtime へデプロイ
    ├── 03_query.py      # デプロイ済みへ1回問い合わせ
    └── 04_delete.py     # 後片付け

番号順に叩いていくだけ、という素直な作りです。


準備

パッケージ

Python 3.13 の venv に入れたのはこのあたりです。

pip install "google-cloud-aiplatform[agent_engines,adk]" google-cloud-run

主要なバージョンは以下でした。

google-adk               2.6.3
google-cloud-aiplatform  1.163.0
google-cloud-run         0.16.1

認証とプロジェクト

今回は個人の Google アカウント+個人プロジェクトで実施しています。

gcloud auth application-default login
gcloud config set project <YOUR_PROJECT_ID>

.env

.env.example をコピーして埋めます。ポイントは GOOGLE_GENAI_USE_VERTEXAI=TRUE(AI Studio の API キーではなく Gemini Enterprise Agent Platform〈旧 Vertex AI〉経由でモデルを呼ぶ。環境変数名は旧名のまま)です。

GOOGLE_CLOUD_PROJECT=<YOUR_PROJECT_ID>
GOOGLE_CLOUD_LOCATION=us-central1
STAGING_BUCKET=gs://<YOUR_PROJECT_ID>-agent-engine-staging
GOOGLE_GENAI_USE_VERTEXAI=TRUE

CLOUD_RUN_REGION=asia-northeast1
MODEL=gemini-3.5-flash
USER_ID=local-test-user

CLOUD_RUN_REGION は「ツールが Cloud Run 一覧を取りに行く先」です。ここに自分がサービスを立てているリージョンを指定します。


フェーズ1:準備

API 有効化・ステージング用 GCS バケット作成・自分への IAM 付与をまとめてやります。

bash scripts/00_setup.sh

出力:

== API 有効化 ==
Operation "operations/acat...." finished successfully.
== ステージング GCS バケット ==
Creating gs://<YOUR_PROJECT_ID>-agent-engine-staging/...
== デプロイ実行者へのロール付与 ==
  granted roles/aiplatform.user to <you>
  granted roles/storage.admin to <you>

準備完了:
  project        = <YOUR_PROJECT_ID>
  agent location = us-central1
  staging bucket = gs://<YOUR_PROJECT_ID>-agent-engine-staging

フェーズ2:ローカルで動かす

デプロイ前に、手元でエージェントが動くか確認します。

python scripts/01_local.py

ハマりどころ①:クイックスタートのモデル名が 404

.envMODEL=gemini-3.5-flash は参考にした公式クイックスタートの記載どおりだったのですが、私のプロジェクトでは 404 になりました。

google.genai.errors.ClientError: 404 NOT_FOUND.
Publisher model `.../models/gemini-3.5-flash` was not found
or your project does not have access to it.

gemini-3.5-flash はモデルとしては実在します。ですが「そのプロジェクト・リージョンで有効化されている/アクセスできる」とは限りません。クイックスタートのモデル名をコピペしたら自分の環境では 404、だったので、確実に使える世代に落とします。

# .env のモデルを 2.5-flash に変更(macOS の sed)
sed -i '' 's/^MODEL=gemini-3.5-flash/MODEL=gemini-2.5-flash/' .env

再実行すると、今度は通りました。

python scripts/01_local.py

出力:

[local] project=<YOUR_PROJECT_ID> location=us-central1 model=gemini-2.5-flash
[local] cloud run region=asia-northeast1
[local] Q: このプロジェクトの Cloud Run サービス一覧を教えて。
[tool call] list_cloud_run_services
[tool result] {
  'status': 'ok', 'project': '<YOUR_PROJECT_ID>',
  'region': 'asia-northeast1', 'count': 0, 'services': []
}
このプロジェクトにはCloud Runサービスがありません。
  • [tool call] → エージェントが自分でツールを呼んだ
  • [tool result] → ツールが Gemini Enterprise Agent Platform 経由で GCP を叩いて応答が返った
  • 最後の日本語 → モデルがツール結果を読んでまとめた

count: 0 はエラーではなく、単にこのリージョンにまだ Cloud Run サービスが無いだけです。せっかくなので、ダミーを1個立てておきます。

gcloud run deploy hello-demo \
  --image=us-docker.pkg.dev/cloudrun/container/hello \
  --region=asia-northeast1 --allow-unauthenticated

フェーズ3:Agent Runtime へデプロイ

本番ランタイムへ。

python scripts/02_deploy.py

ハマりどころ②:GOOGLE_CLOUD_PROJECT は予約語

02_deploy.py.env の内容を実行時の環境変数(env_vars)として create() に渡していました。そこに GOOGLE_CLOUD_PROJECT が混ざっていると、400 で弾かれます。

google.genai.errors.ClientError: 400 FAILED_PRECONDITION.
Environment variable name 'GOOGLE_CLOUD_PROJECT' is reserved.
Please rename the variable in `spec.deployment_spec.env`.

GOOGLE_CLOUD_PROJECT / GOOGLE_CLOUD_LOCATION は Agent Runtime 側が自動的に持たせてくれるので、こちらから渡す必要がありませんenv_vars から予約語を外します。

# 修正前
"env_vars": {
    "GOOGLE_CLOUD_PROJECT": cfg.PROJECT_ID,   # ← これが予約語で怒られる
    "CLOUD_RUN_REGION": cfg.CLOUD_RUN_REGION,
    "MODEL": cfg.MODEL,
},

# 修正後(予約語を削除)
"env_vars": {
    "CLOUD_RUN_REGION": cfg.CLOUD_RUN_REGION,
    "MODEL": cfg.MODEL,
},

再実行すると、今度は通りました。

python scripts/02_deploy.py

出力(抜粋):

[deploy] project=<YOUR_PROJECT_ID> location=us-central1
[deploy] staging_bucket=gs://<YOUR_PROJECT_ID>-agent-engine-staging
[deploy] アップロード中... 数分かかります
[deploy] 完了
[deploy] resource name:
  projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>

リソース名が返って deployed_agent.txt に保存されるので、以降のスクリプトはこれを自動参照します。

出るけど無視してよかった警告2つ

デプロイ中、次の警告が出ましたが、どちらもデプロイは成功しました。

The following requirements are missing: {'cloudpickle', 'pydantic'}

これらは Agent Runtime 側に元々入っているため実害なし。気になるなら requirements に明示追加できます。

FutureWarning: The vertexai.Client class is deprecated. Please use agentplatform.Client instead.

こちらは「将来 agentplatform.Client に移行してね」という予告。今は動きます。SDK も含めたサービス再編(Vertex AI → Gemini Enterprise Agent Platform、Agent Engine → Agent Runtime)の流れの一部だと思います。

インフラ目線メモ:ここまでで、コンテナのビルドもレジストリも VM も一切触っていません。extra_packages./agent を渡すと、あとはアップロード → ビルド → 起動まで Agent Runtime が面倒を見てくれます。「管理しなくて済むもの」がかなり多いのが実感できます。


フェーズ4:実行時 ID への権限付与

ここからが本題です。デプロイしただけでは、エージェントの実行時 ID には何の権限もありません

今回のデプロイでは identity_type=AGENT_IDENTITY を指定しています。これは SPIFFE ベースの新しい IAM プリンシパル種別で、エージェント固有の ID を持ち、初期権限ゼロ。必要なものだけを明示的に足す、という権限モデルです。サービスアカウントより粒度が細かく、エージェントを消せば ID も消えます。

…のですが、その「必要な権限を足す」でハマりました。

まず 403

権限を付けずに問い合わせると、当然こうなります。

python scripts/03_query.py
[tool result] {'status': 'error', ...
 'error_message': "PermissionDenied: 403
 Permission 'run.services.list' denied ..."}

ツールが Cloud Run を list する権限(run.viewer)を実行時 ID が持っていないためです。付けにいきます。

罠A:principal://iam.googleapis.com/ は間違い

デプロイ済みエージェントの Agent Identity は、REST の spec.effectiveIdentity から取れます。値はこんな形でした。

agents.global.proj-<PROJECT_NUMBER>.system.id.goog
  /resources/aiplatform/projects/<PROJECT_NUMBER>
  /locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>

IAM の --member には型プレフィックスが要る…と考えて、principal://iam.googleapis.com/ を頭に付けて渡したところ、こう弾かれました。

gcloud projects add-iam-policy-binding <YOUR_PROJECT_ID> \
  --member="principal://iam.googleapis.com/agents.global.proj-<PN>.system.id.goog/resources/aiplatform/..." \
  --role="roles/run.viewer"
ERROR: (gcloud.projects.add-iam-policy-binding) INVALID_ARGUMENT:
The member principal://iam.googleapis.com/agents.global.proj-...
is of an unknown type.
Please set a valid type prefix for the member.

add-iam-policy-binding でも set-iam-policy(ポリシー全体を JSON で流し込む方式)でも、さらにコンソールの「アクセスを許可」からでも、同じ unknown type で拒否されました。CLI 固有の未対応ではなく、渡している識別子の綴りそのものが違う、という切り分けです。

罠B:正しい綴りは principal://agents.global....iam.googleapis.com/ は付けない)

公式ドキュメントを確認すると、Agent Identity のプリンシパル識別子はこの形でした。

principal://agents.global.org-<ORGANIZATION_ID>.system.id.goog
  /resources/aiplatform/projects/<PROJECT_NUMBER>
  /locations/<LOCATION>/reasoningEngines/<AGENT_ENGINE_ID>

principal:// の直後は iam.googleapis.com/ ではなく、いきなり agents.global....id.goog/... が来るのが正解でした。私が余計な iam.googleapis.com/ を挟んでいたのが、unknown type の唯一の原因です。

もう1点、ドキュメントの例は org-(組織 ID)ですが、今回のように組織に属さない個人プロジェクトでは proj- になります。「組織必須の機能かも?」と一瞬疑いましたが、結論は違って、proj- 形式でも普通に通りました。原因は最初から綴りだけでした。

正しい綴りで叩き直します。

gcloud projects add-iam-policy-binding <YOUR_PROJECT_ID> \
  --member="principal://agents.global.proj-<PROJECT_NUMBER>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>" \
  --role="roles/run.viewer" --condition=None

今度は受理されました。ポリシーの roles/run.viewer の members に、Agent Identity のプリンシパルが入っているのが確認できます。

- members:
  - principal://agents.global.proj-<PROJECT_NUMBER>.system.id.goog/resources/aiplatform/.../reasoningEngines/<AGENT_ENGINE_ID>
  role: roles/run.viewer

モデル呼び出し・クォータ利用向けに、同じ綴りでもう1本付けておきます。

gcloud projects add-iam-policy-binding <YOUR_PROJECT_ID> \
  --member="principal://agents.global.proj-<PROJECT_NUMBER>.system.id.goog/resources/aiplatform/projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>" \
  --role="roles/serviceusage.serviceUsageConsumer" --condition=None

principal://agents.global.proj-███.system.id.goog/.../reasoningEngines/███ に、Cloud Run 閲覧者Service Usage コンシューマー が付いた状態。この綴りに iam.googleapis.com/ を挟むと弾かれることに気づくのに時間がかかりました、、、


完走

権限反映を少し待って、最後の問い合わせ。

python scripts/03_query.py

出力:

[remote] projects/<PROJECT_NUMBER>/locations/us-central1/reasoningEngines/<AGENT_ENGINE_ID>
[remote] Q: このプロジェクトの Cloud Run サービス一覧を教えて。
[tool call] list_cloud_run_services
[tool result] {
  'status': 'ok', 'project': '<YOUR_PROJECT_ID>', 'region': 'asia-northeast1',
  'count': 1, 'services': [{'name': 'hello-demo', 'uri': 'https://hello-demo-....run.app', ...}]
}
このプロジェクトには、以下の Cloud Run サービスが 1 つあります。
サービス名: hello-demo
...

count: 1hello-demo が返りました。デプロイ済みエージェントが、Agent Identity の権限で、東京リージョンの Cloud Run を read-only で調べて答えた、という一連が本番ランタイム越しに通ったことになります。


後片付け

検証用リソースは課金が続くので、確認できたら消します。

# Agent Runtime のデプロイを削除
python scripts/04_delete.py

# デモ用に立てた Cloud Run も削除
gcloud run services delete hello-demo --region=asia-northeast1 --quiet

ステージング用 GCS バケットに小さいオブジェクトが残る程度なので、気になれば併せて削除します。

gsutil rm -r gs://<YOUR_PROJECT_ID>-agent-engine-staging

ハマりどころまとめ

構成はシンプルな割に、実地の学びが濃かったので5点だけ。

  1. モデル名の 404:クイックスタートの gemini-3.5-flash が、そのプロジェクト/リージョンでは使えず 404。確実に使える世代(gemini-2.5-flash)に落として回避。
  2. 予約語の envGOOGLE_CLOUD_PROJECTenv_vars に渡すと create() が 400。これらはランタイムが自動で持つので渡さない。
  3. requirements missing 警告cloudpickle / pydantic が missing と出るが、ランタイム側に入っているためデプロイは成功する。
  4. Agent Identity への IAM 付与principal://iam.googleapis.com/ は誤りで、これを付けると CLI・コンソールとも unknown type で全拒否される。個人プロジェクトは proj-<番号> 形式で、これでも通る(組織必須ではない)。
  5. deprecation 警告vertexai.Client は将来 agentplatform.Client へ。今は動く。

特に4番は、公式手順の“隙間”を踏むと沼にハマります。エラーメッセージ(unknown type)が「型プレフィックスを付けろ」と言うので、つい principal://iam.googleapis.com/ のような“それっぽいプレフィックス”を足したくなりますが、それが逆に原因、という引っかけでした。


まとめ:インフラ目線で見た Agent Runtime

管理しなくて済んだものは多いです。コンテナのビルド、レジストリ、スケーリング、セッション管理——このあたりは Agent Runtime が引き受けてくれて、こちらは extra_packages にコードを渡すだけでした。「サーバーレスでエージェントを本番運用する」という体験は、確かにとっつきやすくてよかったです。

デプロイしただけで、コンソールにレイテンシ・リクエスト数・エラー率・トークン使用量のダッシュボードが自動で出ています。
監視まわりを一切組んでいないのに、この粒度で見えるのは楽でいいですね。

cloudrun-inspector のダッシュボード。今回投げた3リクエスト分のレイテンシやトークン使用量が、設定なしで可視化されている。

新機能を「シンプルな構成かつ、インフラ目線で」触ると、こういう“公式手順に書ききれていない実地の癖”が拾えて面白いです。同じようにエージェントを Agent Runtime に載せる方の手伝いになれば嬉しいです。