1.0 プレビューでは、サンドボックスを、明示的なプログラムで操作するコンピューターとして扱います。
各 exec() は argv から 新しい監視付きプロセス を起動します。呼び出しは、プロセスの終了時ではなく、起動が成功したときに解決します(id と pid プロパティを持つプロセスハンドルを受け取ります)。各起動は独立しています。プロセスに必要なときは cwd と env を渡すか、複数ステップのシェル構文を明示的なシェル argv にまとめます。対話型 PTY には ターミナル API を使います。
長時間の作業は、短い Worker リクエストをまたぐことがよくあります。プロセス ID が有効なのは、同じコンテナ にそのプロセスがあるあいだだけです。アイドル停止、障害、置き換えをまたぐ場合は、起動内容全体(コマンド、オプション、アプリのチェックポイント)を保存して、再起動できるようにします。リクエストをまたいで作業を続ける を参照してください。
ほとんどのアプリケーションは ユーザーまたはタスクごとに 1 つのサンドボックス を使います。
const sandbox = getSandbox(env.Sandbox, "user-123");const sandbox = getSandbox(env.Sandbox, "user-123");次の 3 つが関係します。
| 用語 | 意味 |
|---|---|
| Sandbox ID | アプリがそのサンドボックスを再検索するために使う安定した文字列(例: "user-123")。 |
| Container | そのサンドボックスの作業を現在実行している Containers インスタンス。サンドボックスはコンテナ上で動きます。サンドボックス ID は安定しています。背後のコンテナインスタンスは、常に同じとは限りません。 |
| Process | 現在のコンテナ内で exec() により起動するプログラム。ハンドルと process.id は「このコンテナ内のこのプログラム」を意味し、「このサンドボックス ID が永続的に指すもの」ではありません。 |
同じサンドボックス ID でも、同じコンテナとは限りません。 プロセスは、起動したコンテナの中にだけ存在します。新しいコンテナがその ID を担当したあとは、新しいプロセスを起動します。古いプロセスは再開しません。サンドボックスの全体像は サンドボックスのライフサイクル を参照してください。
コマンドモデルは、現行の安定版パッケージと比べて 1.0 プレビューで変わります。
| 現行の安定版パッケージ | 1.0 プレビュー |
|---|---|
exec(string) はコマンド完了時に解決する |
exec(argv) はプロセス起動時に解決する |
デフォルトセッションは cd / export を保持できる |
各起動は独立している |
ほかの形には startProcess / execStream |
短い処理も長時間処理も 1 つのプロセスハンドルで扱う |
単一のバイナリには argv を使います。
const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });シェル構文が必要なときは、明示的なシェルを使います。
const process = await sandbox.exec([
"/bin/bash",
"-lc",
"cd /workspace/app && npm test",
]);const process = await sandbox.exec([
"/bin/bash",
"-lc",
"cd /workspace/app && npm test",
]);前のコマンドに頼らず、起動時に cwd と env を渡す方法もあります。
const process = await sandbox.exec(["npm", "test"], {
cwd: "/workspace/app",
env: { NODE_ENV: "test" },
});const process = await sandbox.exec(["npm", "test"], {
cwd: "/workspace/app",
env: { NODE_ENV: "test" },
});サンドボックス全体の値には setEnvVars を使います。環境変数 を参照してください。
await sandbox.exec(argv) は プロセスハンドル を返します。
| 機能 | メンバー |
|---|---|
| 識別 | id、pid |
| 観測 | status()、logs()、output()、waitForExit()、waitForLog()、waitForPort()、exitCode |
| 制御 | 数値シグナルの kill(signal?)(デフォルトは 15) |
観測のタイムアウトと AbortSignal の値は、その待機またはストリームだけ をキャンセルします。プロセスは停止しません。停止するときは kill() を呼び出します。
exec(argv, { timeout }) は リモート寿命 の期限を設定します。スーパーバイザーがその期限でプロセスを停止した場合、完了結果に timedOut: true が含まれることがあります。
短いコマンドでは output() で十分です。大きい出力や長時間の出力では、logs({ since, replay, follow }) を使い、最新の カーソル を保持します。後続のリクエストは、プロセスが現在のコンテナに残っているあいだストリームを再開できます。API の詳細は Processes API を参照してください。
プロセスが生きているのは、そのサンドボックスの 現在のコンテナ内 で動き続けているあいだだけです。コンテナが停止したあと、古いプロセス ID は、同じサンドボックス ID を担当する後続のコンテナでは無効です。
サンドボックスのコンテナは、永続的に動く想定ではありません。何もすることがない期間が続くと、Cloudflare は停止することがあります。障害のあとや、通常運用中の置き換え(一部のデプロイ後など)でも停止します。
そのとき:
- アプリは同じサンドボックス ID(
user-123)を使い続けます。 - 古いコンテナで動いていたプロセスは終了しています。そのコンテナのプロセス ID とライブログバッファーはなくなります。
- 次にサンドボックスで実際の作業をするとき、Cloudflare は同じサンドボックス ID 向けに 新しい コンテナを起動することがあります。そこで新しいプロセスを起動します。前のコンテナのプロセス ID には再接続しません。古いコンテナのファイルは、アプリが復元しない限り残りません(バックアップやマウントしたバケットなど)。
コンテナの停止と置き換えは 1.0 で新しくなったものではありません。プレビューでは、所有していたコンテナがなくなったあと、プロセスハンドルは fail closed になります。SDK は、同じサンドボックス ID の新しいコンテナに対して、古いプロセス ID を付け替えません。
| 試すこと | 結果 |
|---|---|
| プロセスが現在のコンテナでまだ動いている | getProcess(id) がそれを返し、通常どおりログの読み取りと待機ができます |
| サンドボックス向けのコンテナがまだ動いていない | getProcess と listProcesses は null / [] を返します。参照のためだけにコンテナは 起動しません |
| コンテナ停止前のハンドルをまだ持っている | そのハンドルへの呼び出しは StaleProcessHandleError で失敗します |
| 停止後に同じ ジョブ が必要 | 保存した起動内容とチェックポイントから新しい exec() を開始します |
復旧手順は エラーと復旧 を参照してください。
プロセスまたはターミナルがアクティブなあいだ、コンテナは動き続け、リクエストをまたいで作業を続けられます。何もアクティブでないと、アイドル時間のあと再び停止することがあります。長時間のプロダクトフローでは、意味のある作業を動かし続けるか、チェックポイントと再起動に頼ります。
Worker リクエストは短いです。サンドボックスのプロセスは長く続くことがよくあります。後続のリクエストが 同じプロセスを再開 するか ジョブを再起動 できるように、ジョブを設計します。
| 常に役立つもの | ログをストリームするとき |
|---|---|
| サンドボックス ID | 配信済みイベントからの最新ログ カーソル |
完全な exec argv |
|
起動に必要な cwd と env |
|
| アプリケーションのチェックポイント(リポジトリパス、ステップ、エージェント状態) |
プロセス ID は 現在の コンテナ向けの再開キーです。コンテナが停止したあとにジョブを再起動するには足りません。
作業がまだ動いていて、コンテナが置き換えられていないときに使います。たとえば、ビルドやサーバーが動いている数秒後に別のリクエストが来る場合です。
const process = await sandbox.getProcess(storedProcessId);
if (process) {
const stream = await process.logs({
since: storedCursor,
replay: true,
follow: true,
});
// consume events; keep the latest cursor from each event
return;
}const process = await sandbox.getProcess(storedProcessId);
if (process) {
const stream = await process.logs({
since: storedCursor,
replay: true,
follow: true,
});
// consume events; keep the latest cursor from each event
return;
}そのハンドルで status()、waitForPort()、waitForExit()、kill() も呼び出せます。ログカーソルが有効なのは、このプロセスがこのコンテナに存在するあいだだけです。
getProcess が null を返す、呼び出しが StaleProcessHandleError を投げる、またはコンテナが停止・置き換えされた可能性があるほど時間が経ったときに使います。
const process = await sandbox.exec(storedCommand, {
cwd: storedCwd,
env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });const process = await sandbox.exec(storedCommand, {
cwd: storedCwd,
env: storedEnv,
});
// persist process.id (and clear any old cursor)
await process.waitForPort(3000, { timeout: 60_000 });ジョブが前のコンテナにだけあったファイルにも依存する場合は、ツリーを再び使う前に、それらのディレクトリを バックアップして復元 するか、永続ストレージをマウントします。バックアップと復元はファイルシステムの状態を置き換えます。古いプロセス ID やログバッファーは戻りません。
コンテナの準備ができていない場合は ContainerUnavailableError になることがあります。バックオフして、同じ作業単位を再実行します。エラーと復旧 を参照してください。
async function continueJob(sandbox, job) {
if (job.processId) {
const existing = await sandbox.getProcess(job.processId);
if (existing) {
return existing; // Case 1 — same container, same process
}
// null: no container, or this ID is not in the current container
}
// Case 2 — relaunch from stored command and checkpoint
const process = await sandbox.exec(job.command, {
cwd: job.cwd,
env: job.env,
});
job.processId = process.id;
job.cursor = undefined;
return process;
}async function continueJob(sandbox: Sandbox, job: StoredJob) {
if (job.processId) {
const existing = await sandbox.getProcess(job.processId);
if (existing) {
return existing; // Case 1 — same container, same process
}
// null: no container, or this ID is not in the current container
}
// Case 2 — relaunch from stored command and checkpoint
const process = await sandbox.exec(job.command, {
cwd: job.cwd,
env: job.env,
});
job.processId = process.id;
job.cursor = undefined;
return process;
}コンテナ停止前のハンドルオブジェクトをまだ持っている場合、そのハンドルへの呼び出しは StaleProcessHandleError を投げます。リクエストをまたいで古いハンドルを再利用せず、新しいリクエストごとに getProcess(id) を使います。
プロセス(exec) |
ターミナル | |
|---|---|---|
| 役割 | 監視付きの argv プロセス | 対話型 PTY |
| 入力 | 起動時の argv | PTY 入力(write / ブラウザの connect) |
| 停止 | kill(signal?) |
interrupt() / terminate() |
| 参照 | getProcess / listProcesses |
getTerminal / listTerminals |
どちらも同じ コンテナ寿命のルール に従います。ターミナルのドキュメント: ターミナル。API: Terminals API。
output() は stdout と stderr をバッファーし、truncated: true を設定することがあります。出力が大きい場合や、プロセスが 1 つの Worker リクエストより長く動く場合は logs() を使います。
const stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latestconst stream = await process.logs({ follow: true, replay: true });
// each data/terminal event includes a cursor — store the latest同じ、まだ動いているコンテナ に対する後続のリクエストでは、getProcess(id) を呼び出し、logs({ since: cursor, replay: true, follow: true }) で再開します。サンドボックス向けに新しいコンテナが起動したあとは、新しいプロセスを起動します。古いカーソルは使えません。
イベントの形、待機オプション、準備完了の確認: Processes API。