![[アップデート] GKE Agent Sandboxを使ってみた](https://images.ctfassets.net/ct0aopd36mqt/rj0bPf9FBBkly9rT3IIkW/00a32d06aeb2bde2058545d747b15f2a/googlecloud-kubernetesengine-gke.png?w=3840&fm=webp)
[アップデート] GKE Agent Sandboxを使ってみた
はじめに
皆様こんにちは、あかいけです。
「AIエージェントが生成したコードを安全に実行したい」と思ったことはありますか?私はあります。
特にコード生成・実行機能を提供するAIサービスを展開する企業にとって、エンドユーザーが生成したコードを安全に実行することは重要な課題ではないでしょうか。
そして先日開催されたGoogle Cloud Next 2026でGKEに関する様々なアップデートが発表され、その中に「GKE Agent Sandbox」というものがありました。
GKE Agent Sandbox はAIエージェントが生成した信頼できないコードを安全に実行するために設計されたサンドボックス環境を提供する機能であり、まさに前述の課題をGKEで解決するための機能と言えそうです。
というわけで本ブログでは、GKE Agent Sandbox 実際に使ってみた結果をまとめます。
GKE Agent Sandboxについて
本機能の主な特徴は以下の3点です。
- gVisorによるカーネルレベルの分離
- LLMが生成した信頼できないコードをカーネルレベルで強力に分離
- WarmPoolによる高速起動
- 事前起動済みPodを割り当てることで1秒未満での起動を実現
- KubernetesネイティブなAPI
- CRDベースで管理でき、既存のGKE基盤やCI/CDとそのまま統合可能
個人的な所感としては、gVisorやCRDなど既存の技術をAIエージェント用途向けにいい感じに整備したものという印象です。
そのためGKE Agent Sandboxの独自性は、SandboxClaim・WarmPool・スナップショットといったライフサイクル管理をCRDとして体系化した点にあると思います。
なお、GKE Agent Sandboxのコントローラー部分はOSSプロジェクトとして公開されており、GKEアドオンはそのリリースサイクルに追従する形で提供されています。
またマネージドアドオンとして提供されているため、コントローラーのライフサイクル管理(自動アップグレードやセキュリティパッチの適用など)はGoogleが担います。
どんなユースケースがありそう?
ドキュメントでは以下のようなユースケースが挙げられています。
- AIエージェントランタイム
- 開発環境
- ノートブックと研究ツール
- プログラム的な環境管理
個人的には、ChatGPTのCode InterpreterやGeminiのコード実行機能のように、AIサービスとしてユーザーにコード実行環境を提供する場面で特に使えそうだなと感じました。
チャットAIがコードを生成して、そのまま安全に実行してユーザーに結果を返すといった機能を自社サービスに組み込む際に、このサンドボックス基盤はよくマッチすると思います。
一方、開発環境やJupyterのユースケースは、Cloud ShellやGitHub Codespaces、Google Colabなどすでに使い慣れた選択肢があるので、GKE Agent Sandboxを選ぶ積極的な理由は少し薄いかなという印象です。
ただし既存のGKE基盤と統合する場合などは、選択肢として十分ありだと思います。
ざっくりアーキテクチャ
GKE Agent Sandboxは以下のコンポーネントで構成されます。
大まかな動作フローは以下の通りです。
- クライアントが
SandboxClaimを作成してサンドボックスをリクエストします - Agent Sandbox Controllerが
SandboxClaimを検知し、SandboxWarmPoolからSandboxを即座に割り当て、PodをSandboxに紐づけます - クライアントがSandbox RouterにコードのHTTPリクエストを送ります
- RouterがSandboxClaimに対応するSandbox PodへリクエストをHTTPプロキシします
- gVisorで分離されたSandbox Pod内でコードが実行され、結果が返されます
主要リソース一覧
GKEクラスター内に作成されるKubernetesリソースの役割は以下の通りです。
| リソース | 種別 | 役割 |
|---|---|---|
| Agent Sandbox Controller | StatefulSet | SandboxClaimを監視し、WarmPoolからSandboxを割り当てるライフサイクル管理 |
| Sandbox Router | Deployment + Service | クライアントのリクエストをSandbox PodへHTTPプロキシ |
| SandboxTemplate | CRD | サンドボックスのPod仕様(イメージ・リソース・セキュリティ設定など)を定義 |
| SandboxWarmPool | CRD | SandboxTemplateをもとに事前起動済みPodを管理 |
| SandboxClaim | CRD | サンドボックスのリクエスト。1Claim = 1 gVisor Pod |
前提条件と制限事項
- GKEバージョン
- 1.35.2-gke.1269000以降(スナップショット機能を含むフル機能を利用する場合)
- ノードプール
- N2マシンタイプなどの特定ノード構成に最適化 (スナップショット機能使用時はE2未対応)
- ランタイム
- gVisorなどのセキュリティ強化ランタイムの使用が前提
- スナップショット機能
- 執筆時点ではPreviewまたはリージョン限定の可用性制限がある場合あり
検証
全体的に公式ドキュメントを参考にして検証しています。
また若干設定を変えてデプロイしている箇所もあるので、その部分は補足します。
1.サンドボックスを実行してみる
クラスター・ノードプールの作成
まずGKEクラスターを作成し、gVisor対応のノードプールを追加します。
ノードプール作成時に--sandbox=type=gvisorを指定することで、そのノードプール上のPodがgVisorランタイムが動作するようになります。
また検証用途なので「ゾーンクラスタ+Spot VM」の構成にしています。
export PROJECT_ID=$(gcloud config get project)
export CLUSTER_NAME="agent-sandbox-cluster"
export LOCATION="asia-northeast1"
export CLUSTER_VERSION="1.35.2-gke.1269000"
export NODE_POOL_NAME="agent-sandbox-pool"
export MACHINE_TYPE="e2-standard-2"
gcloud beta container clusters create ${CLUSTER_NAME} \
--location=${LOCATION}-a \
--num-nodes=1 \
--cluster-version=${CLUSTER_VERSION} \
--spot
gcloud container node-pools create ${NODE_POOL_NAME} \
--cluster=${CLUSTER_NAME} \
--machine-type=${MACHINE_TYPE} \
--location=${LOCATION}-a \
--num-nodes=1 \
--image-type=cos_containerd \
--sandbox=type=gvisor \
--spot
ノードが2つ(defaultプール + gVisorプール)起動すれば準備完了です。
kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-agent-sandbox-cl-agent-sandbox-po-fdb757d7-dj75 Ready <none> 8m55s v1.35.2-gke.1269000
gke-agent-sandbox-cluste-default-pool-5e316db7-p6n5 Ready <none> 14m v1.35.2-gke.1269000
Agent Sandbox機能の有効化
クラスターにAgent Sandbox機能を有効化します。
gcloud beta container clusters update ${CLUSTER_NAME} \
--location=${LOCATION}-a \
--enable-agent-sandbox
以下のコマンドでTrueが返れば有効化完了です。
gcloud beta container clusters describe ${CLUSTER_NAME} \
--location=${LOCATION}-a \
--format="value(addonsConfig.agentSandboxConfig.enabled)"
True
SandboxTemplateとSandboxWarmPoolの作成
SandboxTemplateでサンドボックスの仕様を定義し、SandboxWarmPoolであらかじめPodを起動・待機させます。
SandboxTemplateにはいくつか必須フィールドがあるので、そこは必ず設定してください。(Requiredの箇所)
kubectl apply -f - <<EOF
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxTemplate
metadata:
name: python-runtime-template
namespace: default
spec:
podTemplate:
metadata:
labels:
sandbox: python-sandbox-example
spec:
runtimeClassName: gvisor
automountServiceAccountToken: false # Required
securityContext:
runAsNonRoot: true # Required
nodeSelector:
sandbox.gke.io/runtime: gvisor # Required
tolerations:
- key: "sandbox.gke.io/runtime"
value: "gvisor"
effect: "NoSchedule" # Required
containers:
- name: python-runtime
image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0
ports:
- containerPort: 8888
readinessProbe:
httpGet:
path: "/"
port: 8888
initialDelaySeconds: 0
periodSeconds: 1
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi" # Required
securityContext:
capabilities:
drop: ["ALL"] # Required
restartPolicy: "OnFailure"
---
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxWarmPool
metadata:
name: python-sandbox-warmpool
namespace: default
spec:
replicas: 2
sandboxTemplateRef:
name: python-runtime-template
EOF
Sandbox Routerのデプロイ
クライアントからのリクエストをSandbox PodへルーティングするSandbox RouterをDeploymentとClusterIP Serviceとしてデプロイします。
kubectl apply -f - <<EOF
# A ClusterIP Service to provide a stable endpoint for the router pods.
apiVersion: v1
kind: Service
metadata:
name: sandbox-router-svc
namespace: default
spec:
type: ClusterIP
selector:
app: sandbox-router
ports:
- name: http
protocol: TCP
port: 8080 # The port the service will listen on
targetPort: 8080 # The port the router container listens on (from the sandbox_router/Dockerfile)
---
# The Deployment to manage and run the router pods.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sandbox-router-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: sandbox-router
template:
metadata:
labels:
app: sandbox-router
spec:
# Ensure pods are spread across different zones for HA
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: sandbox-router
containers:
- name: router
image: us-central1-docker.pkg.dev/k8s-staging-images/agent-sandbox/sandbox-router:latest-main
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: "100m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
securityContext:
runAsUser: 1000
runAsGroup: 1000
EOF
Python SDKで動作確認
Python SDK(k8s-agent-sandbox)を使ってサンドボックスを起動し、コードを実行してみます。
またSDKはkubectl port-forwardを自動的に起動してクラスター内のSandbox Routerと通信するため、クラスター外からでも透過的にアクセスできます。
python3 -m venv .venv
source .venv/bin/activate
pip install k8s-agent-sandbox
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxLocalTunnelConnectionConfig
# Automatically tunnels to svc/sandbox-router-svc
client = SandboxClient(
connection_config=SandboxLocalTunnelConnectionConfig()
)
sandbox = client.create_sandbox(template="python-runtime-template", namespace="default")
try:
print(sandbox.commands.run("echo 'Hello from Local!'").stdout)
except Exception as e:
print(f"An error occurred: {e}")
上記を実行して、以下レスポンスが帰ってくれば、サンドボックスが正常に起動してコードが実行できています。
Hello from Local!
2.スナップショット機能を使ってみる
注意点:ドキュメント記載のGKEバージョン
ドキュメントではバージョン1.35.0-gke.1795000が記載されていましたが、執筆時点で利用できませんでした。
gcloud container get-server-config --location=${GKE_LOCATION} --format="yaml(channels)" | grep "1.35."
Fetching server config for asia-northeast1
defaultVersion: 1.35.3-gke.1234000
- 1.35.3-gke.1389000
- 1.35.3-gke.1234000
defaultVersion: 1.35.3-gke.1522000
upgradeTargetVersion: 1.35.3-gke.1522000
- 1.35.3-gke.1737000
- 1.35.3-gke.1522000
defaultVersion: 1.35.3-gke.1234000
upgradeTargetVersion: 1.35.3-gke.1234000
- 1.35.3-gke.1389000
- 1.35.3-gke.1234000
- 1.35.2-gke.1962000
- 1.35.2-gke.1269001
そのため利用可能なバージョンを確認し、RAPIDチャンネルの最新版を使用しました。
今回は1.36.0-gke.1379000を使用しました。
gcloud container get-server-config --location=${GKE_LOCATION} --format="yaml(channels)" | grep -A 5 "RAPID"
- channel: RAPID
defaultVersion: 1.35.3-gke.1522000
validVersions:
- 1.36.0-gke.1379000
- 1.35.3-gke.1737000
クラスター作成(スナップショット対応)
スナップショット機能には--enable-pod-snapshotsと--workload-pool(Workload Identity)の設定が必要です。
また検証用途なので「ゾーンクラスタ+Spot VM」の構成にしています。
export PROJECT_ID=$(gcloud config get project)
export CLUSTER_NAME="agent-sandbox-cluster"
export GKE_LOCATION="asia-northeast1"
export GKE_VERSION="1.36.0-gke.1379000"
export AGENT_SANDBOX_VERSION="v0.1.0"
export NODE_POOL_NAME="agent-sandbox-node-pool"
export MACHINE_TYPE="n2-standard-2"
export SNAPSHOTS_BUCKET_NAME="agent-sandbox-snapshots-${PROJECT_ID}"
export SNAPSHOT_NAMESPACE="pod-snapshots-ns"
export SNAPSHOT_KSA_NAME="pod-snapshot-sa"
export SNAPSHOT_FOLDER="my-snapshots"
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
gcloud beta container clusters create ${CLUSTER_NAME} \
--location=${GKE_LOCATION}-a \
--cluster-version=${GKE_VERSION} \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--workload-metadata=GKE_METADATA \
--num-nodes=1 \
--enable-pod-snapshots \
--spot
gcloud container node-pools create ${NODE_POOL_NAME} \
--cluster=${CLUSTER_NAME} \
--location=${GKE_LOCATION}-a \
--machine-type=${MACHINE_TYPE} \
--node-version=${GKE_VERSION} \
--image-type=cos_containerd \
--num-nodes=1 \
--sandbox type=gvisor \
--spot
GCSバケットとIAM設定
スナップショットの保存先となるGCSバケットと利用するフォルダを作成します。階層型名前空間(HNS)の有効化が必要です。
gcloud storage buckets create "gs://${SNAPSHOTS_BUCKET_NAME}" \
--uniform-bucket-level-access \
--enable-hierarchical-namespace \
--soft-delete-duration=0d \
--location="${GKE_LOCATION}"
gcloud storage managed-folders create "gs://${SNAPSHOTS_BUCKET_NAME}/${SNAPSHOT_FOLDER}/"
次にKubernetesサービスアカウントにGCSへの読み書き権限を付与します。
スナップショットの読み書きにはカスタムロールが必要なため、podSnapshotGcsReadWriterロールを作成してバインドします。
gcloud iam roles create podSnapshotGcsReadWriter \
--project="${PROJECT_ID}" \
--permissions="storage.objects.get,storage.objects.create,storage.objects.delete,storage.folders.create"
kubectl create namespace "${SNAPSHOT_NAMESPACE}"
kubectl create serviceaccount "${SNAPSHOT_KSA_NAME}" \
--namespace "${SNAPSHOT_NAMESPACE}"
gcloud storage buckets add-iam-policy-binding "gs://${SNAPSHOTS_BUCKET_NAME}" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/namespace/${SNAPSHOT_NAMESPACE}" \
--role="roles/storage.bucketViewer"
gcloud storage buckets add-iam-policy-binding "gs://${SNAPSHOTS_BUCKET_NAME}" \
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/${SNAPSHOT_NAMESPACE}/sa/${SNAPSHOT_KSA_NAME}" \
--role="projects/${PROJECT_ID}/roles/podSnapshotGcsReadWriter"
gcloud storage buckets add-iam-policy-binding "gs://${SNAPSHOTS_BUCKET_NAME}" \
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/${SNAPSHOT_NAMESPACE}/sa/${SNAPSHOT_KSA_NAME}" \
--role="roles/storage.objectUser"
gcloud storage buckets add-iam-policy-binding "gs://${SNAPSHOTS_BUCKET_NAME}" \
--member="serviceAccount:service-${PROJECT_NUMBER}@container-engine-robot.iam.gserviceaccount.com" \
--role="roles/storage.objectUser"
注意点:PodSnapshot CRDのAPIバージョン
おそらくGKEのバージョンがドキュメントと異なるためだと思いますが、
ドキュメント記載のPodSnapshot CRDのAPIバージョン(podsnapshot.gke.io/v1alpha1)が実際のバージョンが異なっていたため、kubectl api-resourcesで正しいバージョンを確認しました。
kubectl api-resources | grep podsnapshot
podsnapshotmanualtriggers psmt podsnapshot.gke.io/v1 true PodSnapshotManualTrigger
podsnapshotpolicies psp podsnapshot.gke.io/v1 true PodSnapshotPolicy
podsnapshots ps podsnapshot.gke.io/v1 true PodSnapshot
podsnapshotstorageconfigs pssc podsnapshot.gke.io/v1 false PodSnapshotStorageConfig
正しいAPIバージョンはpodsnapshot.gke.io/v1でした。
PodSnapshotStorageConfigとPodSnapshotPolicyの作成
スナップショットの保存先設定とポリシーを作成します。
kubectl apply -f - <<EOF
apiVersion: podsnapshot.gke.io/v1
kind: PodSnapshotStorageConfig
metadata:
name: cpu-pssc-gcs
spec:
snapshotStorageConfig:
gcs:
bucket: "${SNAPSHOTS_BUCKET_NAME}"
path: "${SNAPSHOT_FOLDER}"
EOF
sleep 5
kubectl apply -f - <<EOF
apiVersion: podsnapshot.gke.io/v1
kind: PodSnapshotPolicy
metadata:
name: cpu-psp
namespace: ${SNAPSHOT_NAMESPACE}
spec:
storageConfigName: cpu-pssc-gcs
selector:
matchLabels:
app: agent-sandbox-workload
triggerConfig:
type: manual
postCheckpoint: resume
EOF
SandboxTemplateの作成
スナップショット作成の対象となる、サンドボックスを作成しておきます。
kubectl apply -f - <<EOF
---
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxTemplate
metadata:
name: python-runtime-template
namespace: ${SNAPSHOT_NAMESPACE}
spec:
podTemplate:
metadata:
labels:
app: agent-sandbox-workload
spec:
serviceAccountName: ${SNAPSHOT_KSA_NAME}
runtimeClassName: gvisor
containers:
- name: my-container
image: python:3.10-slim
command: ["python3", "-c"]
args:
- |
import time
i = 0
while True:
print(f"Count: {i}", flush=True)
i += 1
time.sleep(1)
---
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxClaim
metadata:
name: python-sandbox-example
namespace: ${SNAPSHOT_NAMESPACE}
labels:
app: agent-sandbox-workload
spec:
sandboxTemplateRef:
name: python-runtime-template
EOF
スナップショットの動作確認
では実際にスナップショットを作ってみます。
まず先ほどデプロイしたサインドボックスですが、1秒ごとにカウントを増やし続けるPythonスクリプトを実行しています。
kubectl logs python-sandbox-example --namespace "${SNAPSHOT_NAMESPACE}" --tail=5
Count: 0
Count: 1
Count: 2
Count: 3
次に上記サンドボックスを指定し、PodSnapshotManualTriggerを作成してスナップショットを取得します。
kubectl apply -f - <<EOF
apiVersion: podsnapshot.gke.io
kind: PodSnapshotManualTrigger
metadata:
name: cpu-snapshot-trigger
namespace: ${SNAPSHOT_NAMESPACE}
spec:
targetPod: python-sandbox-example
EOF
その後PodSnapshotManualTriggerのステータスを確認して、
Completeになっていればスナップショットが作成されています。
kubectl get podsnapshotmanualtriggers.podsnapshot.gke.io \
--namespace "${SNAPSHOT_NAMESPACE}"
NAME TARGET POD STATUS AGE
cpu-snapshot-trigger python-sandbox-example Complete 16s
次に作成したスナップショットからSandboxClaimを作成して復元します。
kubectl apply -f - <<EOF
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxClaim
metadata:
name: python-sandbox-from-snapshot
namespace: ${SNAPSHOT_NAMESPACE}
labels:
app: agent-sandbox-workload
spec:
sandboxTemplateRef:
name: python-runtime-template
EOF
復元したサンドボックスを見てみるとカウントは以下であり、
kubectl logs python-sandbox-from-snapshot --namespace "${SNAPSHOT_NAMESPACE}"
Count: 63
Count: 64
Count: 65
Count: 66
Count: 67
スナップショット作成元のサンドボックスを見てみると、カウントは以下であり別々の環境であることが確認できます。
kubectl logs python-sandbox-example --namespace "${SNAPSHOT_NAMESPACE}" --tail=5
Count: 148
Count: 149
Count: 150
Count: 151
Count: 152
以上のことから、Podの実行状態をGCSに保存し、その状態から新しいPodを即座に復元できることが確認できました。
スナップショットさえ取っておけば、次回起動時にインストール済みの状態から即座に使い始められるため、起動高速化に活用できそうですね。
3.Claude Codeからサンドボックスを呼び出してみる
Python SDKの基本動作を確認できたので、次はClaude CodeとMCP(Model Context Protocol)経由で連携させてみます。
以下リポジトリに各種ファイルを入れているので、ご自由にご利用ください。
デモアプリのデプロイ
まず専用のNamespace、SandboxTemplate、SandboxWarmPool、Sandbox Routerをデプロイします。
なおGKEクラスタは「1.サンドボックスを実行してみる」で作成したものを利用しています。
kubectl apply -f manifests/sample-app-namespace.yaml
kubectl apply -f manifests/sandbox-template.yaml
kubectl apply -f manifests/sandbox-router.yaml
namespace/sample-app created
sandboxtemplate.extensions.agents.x-k8s.io/sample-app-template created
sandboxwarmpool.extensions.agents.x-k8s.io/sample-app-warmpool created
service/sandbox-router-svc created
deployment.apps/sample-app-router created
テストアプリの動作確認
まずPython SDKを使ったスタンドアロンのテストアプリとして動作を確認します。
このアプリはフィボナッチ数列計算・統計処理・ファイル操作・gVisor分離確認の4種類のタスクを、それぞれ独立したサンドボックスで実行できます。
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python3 app/agent.py
実行してみると以下のようにsandbox-claimのIDが表示され、実行結果が帰ってくればOKです。
=======================================================
Agent Sandbox サンプルアプリ
=======================================================
AIが生成したコードを安全なサンドボックスで実行します
-------------------------------------------------------
1. 数値計算 - フィボナッチ数列を計算する
...
実行するタスクを選択: 1
サンドボックスを起動中...
起動完了 (id: sandbox-claim-3d9a48d4)
実行結果:
フィボナッチ数列 (15項): [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
第15項: 377
サンドボックスを削除しました (id: sandbox-claim-3d9a48d4)
Claude CodeのMCPサーバーとして登録
もう少し実用的な使い方をしたいので、次はClaude Codeから使ってみます。
まずMCPサーバー(mcp_server.py)をClaude Codeに登録します。
このMCPサーバーはexecute_pythonというツールを公開しており、Claude Codeがこのツールを呼び出すと、渡されたPythonコードがgVisorサンドボックス上で実行されます。
claude mcp add sandbox-executor \
-s user \
-- \
$(pwd)/.venv/bin/python \
$(pwd)/app/mcp_server.py
Added stdio MCP server sandbox-executor with command: ...
以下コマンドで確認して、MCPサーバーとして登録されていれば準備はOKです。
claude mcp list | grep sandbox-executor
sandbox-executor: /Users/... - ✓ Connected

登録後はClaude Codeのチャット内でPythonコードの実行が必要な場面(「このコードを実行して」「計算してみて」など)でサンドボックスが自動的に使われるようになります。
️

WarmPoolの自動補充を観察
サンドボックスが起動・終了するたびにWarmPoolが自動的に補充される様子をウォッチで確認できます。
kubectl get pod -n sample-app -w
sample-app-warmpool-fpfrr 1/1 Running 0 3m35s
sample-app-warmpool-nb4dd 1/1 Running 0 55s
sample-app-warmpool-ld9tz 0/1 Pending 0 0s # 新規作成
sample-app-warmpool-ld9tz 0/1 ContainerCreating 0 0s
sample-app-warmpool-fpfrr 1/1 Terminating 0 3m50s # 実行完了のため削除
sample-app-warmpool-ld9tz 1/1 Running 0 4s
SandboxClaimにPodが割り当てられて消費されると、すぐに新しいPodがWarmPoolとして補充されていることが確認できます。
これにより常に一定数のPodが待機状態を保ち、次のリクエストにも高速に応答できる仕組みになっています。
その他通常のPodとの違いについて
最後に、gVisorサンドボックスと通常Pod(runc)の違いを比較した結果をまとめます。
カーネルバージョンの違い
gVisorはホストのカーネルを直接使わず、独自の仮想カーネルを提供します。uname -rの結果を比較すると違いが明確です。
echo "=== サンドボックス(gVisor)==="
kubectl exec "${SANDBOX_POD}" -- uname -r
echo "=== 通常Pod(runc)==="
kubectl exec "${NORMAL_POD}" -- uname -r
同じノード上で動いていますが、サンドボックスからはgVisor固有の仮想カーネルバージョン(4.4.0)が見えます。
=== サンドボックス(gVisor)===
4.4.0
=== 通常Pod(runc)===
6.12.55+
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
gke-agent-sandbox-cl-agent-sandbox-po-1fe4d535-9pw5 Ready <none> 52m v1.35.2-gke.1269000 10.128.0.7 34.60.216.150 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
gke-agent-sandbox-cl-agent-sandbox-po-e0e8c434-l0gp Ready <none> 52m v1.35.2-gke.1269000 10.128.0.6 34.29.140.24 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
gke-agent-sandbox-cl-agent-sandbox-po-f7410886-zdqx Ready <none> 52m v1.35.2-gke.1269000 10.128.0.8 136.113.28.95 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
gke-agent-sandbox-cluste-default-pool-101fbe29-99z1 Ready <none> 55m v1.35.2-gke.1269000 10.128.0.4 34.28.5.246 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
gke-agent-sandbox-cluste-default-pool-5af1a017-4bjv Ready <none> 55m v1.35.2-gke.1269000 10.128.0.5 34.30.255.103 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
gke-agent-sandbox-cluste-default-pool-9f9ef4c1-xqc7 Ready <none> 55m v1.35.2-gke.1269000 10.128.0.3 34.66.27.157 Container-Optimized OS from Google 6.12.55+ containerd://2.1.5
GCEメタデータサーバーへのアクセス
GKE Agent Sandboxはデフォルトで「Default Deny」のネットワークポリシーを実装しています。
GKE Agent Sandbox implements a Default Deny network security posture for all sandboxed environments. This ensures that untrusted code executed inside a sandbox cannot access unauthorized internal networks or the GKE control plane by default.
GCEメタデータサーバー(169.254.169.254)はGCPトークン取得に使われるエンドポイントであり、実際にアクセスを試みると、挙動が異なります。
コマンド
GCP_API_CODE="
import urllib.request, socket, json
socket.setdefaulttimeout(5)
# Step1: メタデータサーバーからトークンを取得
token_req = urllib.request.Request(
'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token',
headers={'Metadata-Flavor': 'Google'}
)
try:
token_res = urllib.request.urlopen(token_req)
token = json.loads(token_res.read().decode())['access_token']
print('Step1: トークン取得成功')
except Exception as e:
print(f'Step1: トークン取得BLOCKED: {type(e).__name__}')
exit()
# Step2: 取得したトークンでCloud Storage APIを呼び出す(バケット一覧)
project_req = urllib.request.Request(
'http://169.254.169.254/computeMetadata/v1/project/project-id',
headers={'Metadata-Flavor': 'Google'}
)
project_id = urllib.request.urlopen(project_req).read().decode()
gcs_req = urllib.request.Request(
f'https://storage.googleapis.com/storage/v1/b?project={project_id}&maxResults=5',
headers={'Authorization': f'Bearer {token}'}
)
try:
gcs_res = urllib.request.urlopen(gcs_req)
data = json.loads(gcs_res.read().decode())
buckets = [b['name'] for b in data.get('items', [])]
print(f'Step2: GCSバケット一覧取得成功 => {buckets}')
except Exception as e:
print(f'Step2: GCS API BLOCKED: {type(e).__name__}')
"
echo "=== サンドボックス(gVisor)==="
kubectl exec "${SANDBOX_POD}" -- python3 -c "${GCP_API_CODE}"
echo "=== 通常Pod(runc)==="
kubectl exec "${NORMAL_POD}" -- python3 -c "${GCP_API_CODE}"
=== サンドボックス(gVisor)===
Step1: トークン取得BLOCKED: URLError
=== 通常Pod(runc)===
Step1: トークン取得成功
Step2: GCSバケット一覧取得成功 => ['agent-sandbox-storage-lamaglama39']
通常Podではトークン取得からCloud Storage APIの呼び出しまで一連で実行できていますが、サンドボックスでは最初のアクセスがブロックされます。
WarmPoolによる起動時間
WarmPoolの有無でkubectl waitコマンド込みの起動時間を計測しました。
# WarmPool有り
START_MS=$(python3 -c "import time; print(int(time.time() * 1000))")
kubectl apply -f sandbox-claim.yaml
kubectl wait sandboxclaim/sandbox-claim --for=condition=Ready --timeout=120s
END_MS=$(python3 -c "import time; print(int(time.time() * 1000))")
echo "起動時間(WarmPool 有り): $((END_MS - START_MS))ms"
起動時間(WarmPool 有り): 3179ms
# WarmPool無し(replicas=0に変更後)
起動時間(WarmPool 無し): 6155ms
kubectl wait自体のオーバーヘッドを除くため、K8sのタイムスタンプ基準(creationTimestamp→lastTransitionTime)でも計測しました。
kubectl get sandboxclaim sandbox-claim -o json | python3 -c "
import sys, json
from datetime import datetime
data = json.load(sys.stdin)
created = datetime.fromisoformat(data['metadata']['creationTimestamp'].replace('Z', '+00:00'))
conditions = data.get('status', {}).get('conditions', [])
ready = next((c for c in conditions if c['type'] == 'Ready'), None)
if ready and ready['status'] == 'True':
ready_time = datetime.fromisoformat(ready['lastTransitionTime'].replace('Z', '+00:00'))
elapsed_ms = int((ready_time - created).total_seconds() * 1000)
print(f'creationTimestamp : {created}')
print(f'lastTransitionTime: {ready_time}')
print(f'起動時間(K8sタイムスタンプ基準): {elapsed_ms}ms')
"
creationTimestamp : 2026-04-25 09:16:40+00:00
lastTransitionTime: 2026-04-25 09:16:41+00:00
K8sのタイムスタンプは秒単位の解像度ではありますが、結果は1秒差でした。
また100msポーリング方式での計測では1207msでした。WarmPoolがない場合はPodのコールドスタートが発生するため、倍近い時間がかかっています。
マルチサンドボックスの同時起動
WarmPoolに3個のPodが待機している状態で、10個のSandboxClaimを同時に作成して全起動完了までの時間を計測しました。
kubectl get pod -l sandbox-type=python-runtime
NAME READY STATUS RESTARTS AGE
python-runtime-warmpool-jxmhf 1/1 Running 0 77m
python-runtime-warmpool-pwmp8 1/1 Running 0 77m
python-runtime-warmpool-qlhdl 1/1 Running 0 76m
コマンド
START_MS=$(python3 -c "import time; print(int(time.time() * 1000))")
for i in $(seq 1 10); do
kubectl apply -f - <<EOF
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxClaim
metadata:
name: sandbox-claim-${i}
namespace: default
spec:
sandboxTemplateRef:
name: python-runtime-template
EOF
done
for i in $(seq 1 10); do
kubectl wait sandboxclaim "sandbox-claim-${i}" --for=condition=Ready --timeout=120s &
done
wait
END_MS=$(python3 -c "import time; print(int(time.time() * 1000))")
echo "全体の起動時間: $((END_MS - START_MS))ms"
echo "1サンドボックスあたりの平均: $(((END_MS - START_MS) / 10))ms"
全体の起動時間: 25604ms
1サンドボックスあたりの平均: 2560ms
WarmPool(3個)に収まる分は高速に処理され、残り7個はコールドスタートとなりましたが、全体で約26秒・平均2.5秒で起動しました。
さいごに
以上、GKE Agent Sandboxを使ってみた検証記事でした。
設定としては「GKEのアドオンを有効化+いくつかのCRDをデプロイ」するだけで導入でき、シンプルで良いと思いました。
なおかなりピンポイントな機能なので実際のユースケースは限られそうですが、
ChatGPTのCode InterpreterやGeminiのコード実行機能のように、AIサービスでユーザーにコード実行環境を提供するといった特定のユースケースには刺さる機能だと思うので、気になる方はぜひ触ってみてください。










