はじめに
工学院大学大学院修士 1 年の柴山優太です。
今回、Sreake 事業部で期限付きインターンをさせていただいている中で、Kubernetes の Agent Sandbox について技術調査を行いました。
近年、AIエージェントにコードを生成してもらいそれを実行させたり、ファイル操作やコマンド操作の実行を任せたりするユースケースが急速に増えています。こうした AIエージェントによる安全な実行には、隔離された実行環境を必要なタイミングで素早く用意できる仕組みが欠かせません。
AIエージェントの実行環境には、次のような性質が求められます。
- 隔離されている(isolated): LLM が生成した信頼できないコードを実行するため、ホストや他環境から強く隔離されている必要がある
- 長時間起動し続ける(long-running): 対話やタスクの間、同じ環境が生き続ける必要がある
- 状態を持つ(stateful): 作業ファイルやキャッシュが再起動で消えると毎回やり直しになる
- 1 台だけ存在する(singleton): 状態は複製しても意味がないため、増やすより「1つの環境に確実に戻れる」ことが重要
- 安定したネットワーク ID・永続ストレージを持つ: 繰り返しアクセスしても接続先とデータが変わらない
Agent Sandbox は、こうした性質を持つ実行環境を Kubernetes 上で扱いやすくするための仕組みです。
Agent Sandbox は、Deployment が想定する「ステートレスでレプリカ可能な Pod 群」や、StatefulSet が想定する「番号付きの安定した Pod 群」とは異なり、「1 つの作業環境」を起動・停止・期限管理することに焦点を当てた仕組みです。そのため、スケールアウトよりも、同じ名前の環境に戻れることや、不要になった実行環境を安全に片付けることが重視されています。
本記事では、Agent Sandbox を構成する各コンポーネント(Sandbox / SandboxTemplate / SandboxClaim / SandboxWarmPool とそれぞれの Controller)が、どのような役割を持ち、どのように連携して動作しているのかを、順に見ていきます。
概要
Agent Sandbox は、ひとつの大きなアプリケーションではなく、複数の独立した Controller(Reconciler)が、それぞれ別の CRD を監視し、Kubernetes API Server を介して動作するという、Kubernetes Operator パターンの典型的な構成を取っています。
リポジトリの README に掲載されている公式アーキテクチャ図を見ると、全体像を把握しやすくなります。

| レイヤー | CRD | 対応するController | 役割 |
|---|---|---|---|
| Core API (api/v1beta1) | Sandbox | SandboxReconciler (controllers/sandbox_controller.go) | Pod / Service / PVC を作成・管理する最小単位 |
| Extensions API (extensions/api/v1beta1) | SandboxTemplate | SandboxTemplateReconciler | Sandbox の設定テンプレートと NetworkPolicy を管理 |
| Extensions API | SandboxClaim | SandboxClaimReconciler | ユーザーのリクエストを受け、Sandbox を採用/新規作成 |
| Extensions API | SandboxWarmPool | SandboxWarmPoolReconciler | 事前にウォーミングされた Sandbox のプールを維持 |
これらの Reconciler は cmd/agent-sandbox-controller/main.go で controller-runtime の Manager に登録されます。–extensions フラグが有効な場合のみ、Extensions 系の 3 つの Controller が追加で起動します。
Sandbox の構成サンプルは以下のようになります。
apiVersion: agents.x-k8s.io/v1beta1
kind: Sandbox
metadata:
name: my-agent-sandbox
namespace: default
spec:
service: true
# Pod の定義(通常の PodSpec をほぼそのまま書ける)
podTemplate:
spec:
containers:
- name: agent
image: python:3.12-slim
command: ["/bin/sh", "-c", "pip install -q fastapi uvicorn && uvicorn agent:app --host 0.0.0.0 --port 8080"]
ports:
- containerPort: 8080
volumeMounts:
- name: workspace # エージェントの作業ファイル(永続化したい)
mountPath: /workspace
- name: model-cache # モデルキャッシュ(再ダウンロードを避ける)
mountPath: /root/.cache
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
# PVC
volumeClaimTemplates:
- metadata:
name: workspace
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
- metadata:
name: model-cache
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd # モデルキャッシュは高速なストレージクラスを指定
resources:
requests:
storage: 50Gi
# Retain: Pod/Service は消えるが PVC と Sandbox リソース自体は残る(データを保持したい場合)
shutdownPolicy: Retain # 未指定時は Retain
# 期限: この時刻を過ぎると Pod/Service が自動削除される(片付け忘れ防止)
shutdownTime: "2026-07-07T00:00:00Z"
Sandboxは何を管理しているのか
Agent Sandbox の中核となるのが Sandbox CRD です。Sandbox を一言で表すと、「Pod 1 台 + (任意で) Service + (任意で) PVC 群」をワンセットで宣言的に管理するためのリソースです。
まずは、Sandbox リソースを apply したときに、実際にどの K8s リソースが作られるのか、その実態を見ていきます。
Sandbox が管理する実リソース
| 管理対象 | 実体となるK8sリソース | 作られる条件 |
|---|---|---|
| Pod | Pod 1台 | spec.operatingMode が Running |
| Service | headless Service | spec.service が true のとき新規作成される。未設定の場合は新規作成しないが、既にcontroller所有のServiceがある場合は維持される。 |
| PVC | PersistentVolumeClaim (複数可) | spec.volumeClaimTemplates に定義された分だけ |
pod
Sandbox の spec には podTemplate というフィールドがあり、通常の PodSpec とほぼ同じ感覚でコンテナイメージ、コマンド、リソース制限などを定義できます。
Sandbox が特徴的なのは、Pod を複数台にスケールさせるのではなく、「1 台の作業環境」として起動・停止する点です。v1beta1 ではこの状態を spec.operatingMode(Running / Suspended、デフォルトはRunning)で制御します。Suspended にすると所有 Pod は削除されますが、Sandbox 本体と PVC は残るため、後から Running に戻すことで同じデータを使って Pod を再作成できます。
💡 なお、この起動・停止の制御は、旧 v1alpha1 では spec.replicas(0/1)が担っていました。現在も conversion webhook によってreplicas=0 は Suspended、それ以外(未指定を含む)は Running として受け付けられますが、現行の v1beta1 における正規フィールドは spec.operatingMode です。
(関連 KEP:https://github.com/kubernetes-sigs/agent-sandbox/tree/main/docs/keps/694-kep-for-suspend-and-resume-for-beta)
Service
spec.service を true にすると、Sandbox は自分の Pod に対応する headless Service (ClusterIP を持たない Service) を作成します。
headless Service を使う目的は、ClusterIP プロキシを介さず Pod IP へ直接解決することです。Sandbox が再作成されて Pod の IP が変わっても、<sandbox 名>.<namespace>.svc.<clusterDomain> という名前で同じ Sandbox にアクセスできます。AIエージェントの実行環境のように「外部から繰り返しアクセスする」用途では、この安定したアドレスが重要になります。
PVC(PersistentVolumeClaim)
spec.volumeClaimTemplates には、Sandbox が使うストレージの定義 (PVC テンプレート) を複数並べることができます。それぞれの PVC テンプレートに対して、<PVC テンプレート名>-<sandbox 名> という固定名の PVC が 1 つずつ作られます。
複数の PVC を持てるようになっている理由は、1 つの Sandbox の中でも用途によって適したストレージが異なるためです。例えば、
- AIエージェントが書いたファイルなどを置く、永続化したい workspace 用の PVC
- 推論を高速化するための model-cache 用の PVC (再ダウンロードを避けたい)
といった形で、ストレージクラスやサイズが異なる複数の PVC を、それぞれ固定名で管理できるイメージです。Pod が再作成されても、同じ名前の PVC に再接続されるため、データは Pod のライフサイクルとは独立して保持されます。
それぞれの関係を図で表すと以下のようになります。

ここまで見てきたように、Sandboxが管理するリソースの中心はあくまでPodです。ServiceはPodへの安定したアクセス手段、PVCはPodのデータの永続化手段という位置づけで、いずれも「Podをどう便利に使うか」を補助する役割を持っています。
なぜSandboxという単位が必要なのか
Pod・Service・PVCを組み合わせて使うこと自体は、Deployment+手書きのService+手書きのPVCでも実現できます。それでもAgent SandboxとしてCRDを用意されているのは、「1つの名前(Sandbox名)に対して、アクセス先(Service/DNS)とデータ(PVC)を固定で紐付け続けたまま、起動・一時停止・破棄を1単位として扱いたい」というニーズに応えるためです。
ここで言う「ID」は、実際には役割の異なる2つの値の組み合わせです。
- name(Sandbox名): ユーザーが指定する文字列。PVC・DNS名はここから導出される
- uid: Kubernetesがオブジェクトの実体ごとに発行するランダムな値。「それが本当に今生きているSandboxの持ち物か」を検証するために使われる。実際にはOwnerReferenceのuidフィールドとして子リソースに記録される
Sandboxは、「名前からPVC/DNSを安定して導出する」という発想を1台構成に絞り込み、そこにshutdownTimeやshutdownPolicyといったCRDレベルの寿命管理を組み合わせたものだと捉えると理解しやすくなります。
一方で、同じ名前のリソースが削除後に再作成される可能性もあるため、OwnerReferenceに記録されたuidを使って「現在のSandboxに属するリソースか」を確認します。別のSandboxが所有するリソースと判断した場合はエラーで拒否するため、古いリソースを誤って引き取ることを防いでいます。
Sandbox applyすると何が起きるのか
Sandboxは「Pod + Service + PVC群」をワンセットで管理しています。では、実際にkubectl apply -f sandbox.yamlを実行すると、これらのリソースはどのような順序で作られるのでしょうか。
初回applyしたとき
SandboxReconcilerは、Sandboxリソースの変更を検知すると、子リソースを次の順序で調整します
- PVCを作成(spec.volumeClaimTemplatesの数だけ)
- Podを作成
- Serviceを作成(spec.serviceがtrueの場合)
作成された Pod・Service・PVC には、Sandbox と対応付けるためのラベルや OwnerReference が設定されます。これにより、Agent Sandbox コントローラーは次回以降の reconcile でも同じリソース群を識別できます。
# Pod / Service / PVCで付与される
metadata:
labels:
agents.x-k8s.io/sandbox-name-hash: 202547f1 # Sandbox名をハッシュ化した値
ownerReferences:
- apiVersion: agents.x-k8s.io/v1beta1
kind: Sandbox
name: my-agent-sandbox
uid: 8bc21aa7-d96d-43a4-a052-d2b05c5d30ed
controller: true
blockOwnerDeletion: true
設定を変えて再applyしたとき
Reconciliation loop は差分だけを適用するため、spec 変更なしの再 apply では何も起きません。また、spec.podTemplate のコンテナイメージや環境変数を変更して再 apply しても、既存 Pod の PodSpec は基本的に更新されません。PodSpec の多くは作成後に変更できないため、新しい設定を反映したい場合は Pod を作り直す必要があります。
Sandboxも既存のPodのSpecは更新されません。コードにも未解決のTODOとして残っています。
// sandbox_controller.go
// TODO - Do we enfore (change) spec if a pod exists ?
// r.Patch(ctx, pod, client.Apply, ...)
一方で、Sandbox の podTemplate.metadata に指定した Pod 向けのラベル・アノテーションや、Service の controller 管理ラベル・selector などは補正されます。
Sandboxの削除
apply時の挙動を見ましたが、ここではその逆、Sandboxを削除したとき、またshutdownTimeが切れたときに、各リソースがどうなるのかを見ていきます。
kubectl delete sandbox によるSandboxリソースの削除
SandboxのPod・Service・PVCには、いずれもOwnerReferenceとしてSandbox自身がcontroller: trueで設定されています。そのため、Sandbox本体を直接削除すると、Kubernetesのガベージコレクションによって子リソースもすべて連動して削除されます。
| リソース | delete時の挙動 |
|---|---|
| Pod | 削除される(OwnerReferenceによる連動削除) |
| Service | 削除される(OwnerReferenceによる連動削除) |
| PVC | 削除される(OwnerReferenceによる連動削除) |
つまりkubectl delete sandboxは、Pod・Service・PVCを含めて丸ごと消す操作です。AIエージェントが作業したファイルなどが入ったPVCも一緒に削除されるため、データを残したい場合は、削除前にPVCのOwnerReferenceを外す(引き取り解除)などの対応が必要になります。
リソース管理のライフサイクルにおける削除
Sandbox 上では、AIエージェントなどが生成したコードを実行することがあります。このようなコードは、処理が正常に終了するとは限らず、意図せず長時間動き続ける可能性があります。そこで Agent Sandbox では、shutdownTime によって Sandbox に期限を設定し、期限切れ時の挙動を shutdownPolicy で制御できるようになっています。
Retain の場合、Pod・Service は削除されますが、Sandbox リソースと PVC は残ります。そのため、実行中のコンピューティングリソースは片付けつつ、作業データは保持できます。一方 Delete の場合は、Sandbox リソース自体も削除されるため、OwnerReference によるガベージコレクションで PVC も削除されます。つまり、作業データを残したい場合は Retain、Sandbox ごと完全に片付けたい場合は Delete を選ぶ、という違いになります。
shutdownPolicyによる一連の流れは以下のようになります。

SandboxのStatus
Sandbox の status.conditions には、現在の状態を表す Condition が入ります。Condition は Ready・Suspended・Finished の3種類があり、それぞれ独立して評価されるため複数が同時に立つことがあります。各Condition は reason フィールドを持ち、その理由も確認できます。
それぞれのConditionとreasonの状態遷移を図で表すと以下のようになります。
Ready Condition

Suspended Condition

Ready Condition が SandboxSuspended になると、その内部状態として Suspended Condition が生まれます。Pod がまだ残っている間は PodNotTerminated、Pod が削除されると PodTerminated に遷移します。
Finished Condition

Pod が Succeeded または Failed になると、Finished Condition が追加されます。 一方で、後続の reconcile で terminal な Pod が存在しなくなった場合は、Finished Condition が削除されることがあります。
Extensionsについて少しだけ
ここまではSandbox単体の話でしたが、Agent SandboxにはSandboxの一段上にSandboxTemplate・SandboxWarmPool・SandboxClaimという3つのExtensions系CRDがあります。
- SandboxTemplate: Sandboxを新規作成する際に使うpodTemplateなどの設定をひな形として保持するリソース。Template から作られる Sandbox に共通の NetworkPolicy をまとめて適用することもできるため、Sandbox ごとの設定漏れを防げる
- SandboxWarmPool: SandboxTemplateの内容で、あらかじめ起動済みのSandboxを一定数維持しておくリソース。Sandboxの新規作成にはPVC/Pod/Serviceの作成待ちが発生しますが、WarmPoolに在庫があればその待ち時間をスキップして、起動済みのSandboxをすぐに使い始めることができる
- SandboxClaim: ユーザーがSandboxを要求するためのリソース。すでに自分が管理しているSandboxがあればそれを使い、なければWarmPoolから採用し、それも無ければTemplateから新規作成する。ユーザー側は「既存があるか」「在庫があるか」を意識せずSandboxClaimに要求するだけでよい
つまりExtensions層が担っているのは、Sandbox自身が持つ「Pod/Service/PVCをどう管理するか」というロジックではなく、その手前の「どのSandboxを、いつ、どの設定で用意するか」という実用的な判断です。ユーザーはこの判断の裏側を意識しなくても、SandboxClaimに要求するだけでSandboxを使うことができます。
終わりに
本記事では、Agent SandboxのコアであるSandbox CRDを中心に、その挙動を見てきました。
SandboxはPod・Service・PVCという既存のKubernetesリソースを組み合わせているだけですが、「名前から安定してPVC/DNSを導出する」「uidで本当に自分の持ち物かを検証する」「shutdownTimeとshutdownPolicyで寿命を管理する」といった仕組みを1台構成に絞り込んで提供することで、AIエージェントの実行環境特有の「状態を持つ・1台だけ存在する・繰り返しアクセスする」というニーズに応えています。
Deployment/StatefulSetといった既存のワークロードリソースでは表現しづらかった「AIエージェント1台分の作業環境」という単位を、Kubernetesネイティブな形で扱えるようにしているのがAgent Sandboxの価値だと言えます。Agent Sandboxはまだ開発が進められている段階のプロジェクトであり、これから挙動が変わっていく部分も残っています。
Pod・Service・PVCというありふれたリソースの組み合わせの中に、AIエージェント特有の要件に応えるための工夫が詰まっていると感じました。