[アップデート] GKE Agent Sandboxを使ってみた

[アップデート] GKE Agent Sandboxを使ってみた

2026.05.04

はじめに

皆様こんにちは、あかいけです。

「AIエージェントが生成したコードを安全に実行したい」と思ったことはありますか?私はあります。
特にコード生成・実行機能を提供するAIサービスを展開する企業にとって、エンドユーザーが生成したコードを安全に実行することは重要な課題ではないでしょうか。

そして先日開催されたGoogle Cloud Next 2026でGKEに関する様々なアップデートが発表され、その中に「GKE Agent Sandbox」というものがありました。

https://cloud.google.com/blog/products/containers-kubernetes/whats-new-in-gke-at-next26?hl=en

GKE Agent Sandbox はAIエージェントが生成した信頼できないコードを安全に実行するために設計されたサンドボックス環境を提供する機能であり、まさに前述の課題をGKEで解決するための機能と言えそうです。

というわけで本ブログでは、GKE Agent Sandbox 実際に使ってみた結果をまとめます。

GKE Agent Sandboxについて

https://docs.cloud.google.com/kubernetes-engine/docs/concepts/machine-learning/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が担います。

https://github.com/kubernetes-sigs/agent-sandbox/

どんなユースケースがありそう?

ドキュメントでは以下のようなユースケースが挙げられています。

  • AIエージェントランタイム
  • 開発環境
  • ノートブックと研究ツール
  • プログラム的な環境管理

個人的には、ChatGPTのCode InterpreterやGeminiのコード実行機能のように、AIサービスとしてユーザーにコード実行環境を提供する場面で特に使えそうだなと感じました。
チャットAIがコードを生成して、そのまま安全に実行してユーザーに結果を返すといった機能を自社サービスに組み込む際に、このサンドボックス基盤はよくマッチすると思います。

一方、開発環境やJupyterのユースケースは、Cloud ShellやGitHub Codespaces、Google Colabなどすでに使い慣れた選択肢があるので、GKE Agent Sandboxを選ぶ積極的な理由は少し薄いかなという印象です。
ただし既存のGKE基盤と統合する場合などは、選択肢として十分ありだと思います。

ざっくりアーキテクチャ

GKE Agent Sandboxは以下のコンポーネントで構成されます。

大まかな動作フローは以下の通りです。

  1. クライアントがSandboxClaimを作成してサンドボックスをリクエストします
  2. Agent Sandbox ControllerがSandboxClaimを検知し、SandboxWarmPoolからSandboxを即座に割り当て、PodをSandboxに紐づけます
  3. クライアントがSandbox RouterにコードのHTTPリクエストを送ります
  4. RouterがSandboxClaimに対応するSandbox PodへリクエストをHTTPプロキシします
  5. 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またはリージョン限定の可用性制限がある場合あり

検証

全体的に公式ドキュメントを参考にして検証しています。
また若干設定を変えてデプロイしている箇所もあるので、その部分は補足します。

https://docs.cloud.google.com/kubernetes-engine/docs/how-to/how-install-agent-sandbox
https://docs.cloud.google.com/kubernetes-engine/docs/how-to/agent-sandbox
https://docs.cloud.google.com/kubernetes-engine/docs/how-to/agent-sandbox-pod-snapshots

1.サンドボックスを実行してみる

https://docs.cloud.google.com/kubernetes-engine/docs/how-to/how-install-agent-sandbox
https://docs.cloud.google.com/kubernetes-engine/docs/how-to/agent-sandbox

クラスター・ノードプールの作成

まず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.スナップショット機能を使ってみる

https://docs.cloud.google.com/kubernetes-engine/docs/how-to/agent-sandbox-pod-snapshots

注意点:ドキュメント記載の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)経由で連携させてみます。
以下リポジトリに各種ファイルを入れているので、ご自由にご利用ください。

https://github.com/Lamaglama39/gke-agent-sandbox-demo

デモアプリのデプロイ

まず専用の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

スクリーンショット 2026-05-03 2.52.35

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

スクリーンショット 2026-05-03 2.55.51

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」のネットワークポリシーを実装しています。

https://docs.cloud.google.com/kubernetes-engine/docs/concepts/machine-learning/agent-sandbox#network-isolation

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のタイムスタンプ基準(creationTimestamplastTransitionTime)でも計測しました。

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サービスでユーザーにコード実行環境を提供するといった特定のユースケースには刺さる機能だと思うので、気になる方はぜひ触ってみてください。

この記事をシェアする

関連記事