こんにちは!
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
.env の MODEL=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: 1 で hello-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点だけ。
- モデル名の 404:クイックスタートの
gemini-3.5-flashが、そのプロジェクト/リージョンでは使えず 404。確実に使える世代(gemini-2.5-flash)に落として回避。 - 予約語の env:
GOOGLE_CLOUD_PROJECTをenv_varsに渡すとcreate()が 400。これらはランタイムが自動で持つので渡さない。 - requirements missing 警告:
cloudpickle/pydanticが missing と出るが、ランタイム側に入っているためデプロイは成功する。 - Agent Identity への IAM 付与:
principal://iam.googleapis.com/は誤りで、これを付けると CLI・コンソールともunknown typeで全拒否される。個人プロジェクトはproj-<番号>形式で、これでも通る(組織必須ではない)。 - deprecation 警告:
vertexai.Clientは将来agentplatform.Clientへ。今は動く。
特に4番は、公式手順の“隙間”を踏むと沼にハマります。エラーメッセージ(unknown type)が「型プレフィックスを付けろ」と言うので、つい principal://iam.googleapis.com/ のような“それっぽいプレフィックス”を足したくなりますが、それが逆に原因、という引っかけでした。
まとめ:インフラ目線で見た Agent Runtime
管理しなくて済んだものは多いです。コンテナのビルド、レジストリ、スケーリング、セッション管理——このあたりは Agent Runtime が引き受けてくれて、こちらは extra_packages にコードを渡すだけでした。「サーバーレスでエージェントを本番運用する」という体験は、確かにとっつきやすくてよかったです。
デプロイしただけで、コンソールにレイテンシ・リクエスト数・エラー率・トークン使用量のダッシュボードが自動で出ています。
監視まわりを一切組んでいないのに、この粒度で見えるのは楽でいいですね。

↑ cloudrun-inspector のダッシュボード。今回投げた3リクエスト分のレイテンシやトークン使用量が、設定なしで可視化されている。
新機能を「シンプルな構成かつ、インフラ目線で」触ると、こういう“公式手順に書ききれていない実地の癖”が拾えて面白いです。同じようにエージェントを Agent Runtime に載せる方の手伝いになれば嬉しいです。