プラグインサンドボックス
このページで分かること
- サンドボックスランナーの役割と、Cloudflare Workers(Dynamic Worker、Workersの有料プランが必要)とNode.js(
workerdを子プロセスとして起動)の違い - Cloudflare Workers(
LOADERバインディング、PluginBridgeのエクスポート)とNode.jsそれぞれの設定手順と、workerdプロセスの動き方 - [object Object]
このページの目次
サンドボックス型プラグイン(sandboxed plugin)は、プラグインの宣言に加えて、プラットフォームごとのランナーを必要とします。マーケットプレイスとプラグインレジストリからインストールしたプラグインは常にこのランナーを使い、sandboxed: [] に並べたプラグインも同様です。plugins: [] に並べたネイティブ型プラグイン(native plugin)はEmDashのサーバーのプロセス内で動き、サンドボックスによる隔離は受けません。
ランナーはデプロイ先のプラットフォームによって決まります。Cloudflare Workersでは、各プラグインはWorker Loaderバインディングを通じて作成されるDynamic Workerとして動きます。Node.jsでは、サーバーがオープンソースのWorkersランタイムであるworkerdを子プロセスとして起動し、各プラグインをその中のサービスとして動かします。emdash() の sandboxRunner オプションはランナーを選択し、ホスティングされたレジストリのカタログを有効にします。このオプションがない場合、sandboxed: [] に並べたプラグインは読み込まれません。明示的に設定したレジストリは引き続き閲覧できますが、サンドボックス型プラグインのインストールや更新は SANDBOX_NOT_AVAILABLE で失敗します。
次の表は、各ランナーに必要なものと、各ランナーが適用する制限をまとめたものです。
| Cloudflare Workers | Node.js | |
|---|---|---|
sandboxRunner |
@emdash-cms/cloudflare の sandbox() |
"@emdash-cms/sandbox-workerd/sandbox" |
| 必要なもの | Workersの有料プラン、worker_loaders バインディング、Workerのエントリーポイントからの PluginBridge のエクスポート |
workerd パッケージ |
| データベースへのアクセス | 設定したアダプターに関係なく、DB という名前のD1バインディング |
設定したデータベース |
| 適用される制限 | CPU時間、サブリクエスト、実時間 | 実時間 |
やさしい解説
WordPressのプラグインは、WordPress本体と同じ権限で動きます。EmDashのサンドボックス型プラグインは、本体から切り離された場所(サンドボックス)で動き、使える機能や時間が制限されます。その切り離された場所を用意するのが「サンドボックスランナー」です。Cloudflare WorkersではWorkersの有料プランのDynamic Worker、Node.jsでは workerd というプログラムを使います。ランナーを設定しないと、サンドボックス型プラグインは読み込まれません。
Cloudflare Workers
Dynamic WorkerはWorkersの有料プランで使えます。*-cloudflare テンプレートには下記のエントリーポイントのエクスポートが含まれていますが、バインディングはコメントアウトされています。そのため、雛形の作成時にサンドボックス型プラグインを有効にしない限り、新しいプロジェクトはWorkersの無料プランでデプロイできます。
-
wrangler.jsoncでWorker Loaderバインディングを有効にします。ランナーはこれをLOADERという名前で読み込み、このバインディングがある場合にだけCloudflareのサンドボックスを選択します。wrangler.jsonc { "worker_loaders": [ { "binding": "LOADER", }, ], }Wranglerの設定で名前付きの環境を使っている場合は、Astroのビルド時に
CLOUDFLARE_ENVを設定します。これにより、CloudflareのViteプラグインとsandbox()が同じ環境を読み込みます。バインディングは継承されないため、サンドボックス型プラグインを動かす名前付きの環境それぞれにLOADERを追加します。 -
Workerのエントリーポイントから
PluginBridgeをエクスポートし、mainにそのファイルを指定します。PluginBridgeは、サンドボックス型プラグインがコンテンツ、メディア、ストレージ、メールにアクセスするための入り口です。ランナーは、エントリーモジュールのエクスポートの中からこれを探します。src/worker.ts import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker"; export { PluginBridge }; export default { ...handler, scheduled: createScheduledHandler(), } satisfies ExportedHandler;wrangler.jsonc { "main": "./src/worker.ts", } -
emdash()インテグレーションでランナーを選択します。astro.config.mjs import { d1, r2, sandbox } from "@emdash-cms/cloudflare"; emdash({ database: d1({ binding: "DB" }), storage: r2({ binding: "MEDIA" }), sandboxRunner: sandbox(), });
Node.js
-
ランナーと、ピア依存関係である
workerdを一緒にインストールします。npm install @emdash-cms/sandbox-workerd workerdworkerdパッケージは、任意の依存関係(optional dependency)を通じて、実行中のプラットフォーム用のバイナリをインストールします(x64ではLinux、macOS、Windows、arm64ではLinuxとmacOS)。任意の依存関係を有効にしたまま、サーバーを動かすプラットフォーム上でインストールします。マルチステージのDockerビルドでは、実行用のステージと同じプラットフォームのステージでインストールします。 -
emdash()インテグレーションでランナーを選択します。astro.config.mjs import { sqlite } from "emdash/db"; emdash({ database: sqlite({ url: "file:./data/emdash.db" }), sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox", });
ランナーはMiniflareを任意の依存関係として宣言しています。パッケージマネージャーは、デフォルトでこれをインストールします。NODE_ENV が development の場合(astro dev はこの値を設定します)、ランナーはプラグインをMiniflareに渡し、Miniflareが独自の workerd プロセスを管理します。この場合、後述のクラッシュ時の方針は適用されません。任意の依存関係を省いてインストールした場合、ランナーは代わりに workerd を使います。astro preview は NODE_ENV を production に設定し、node ./dist/server/entry.mjs は設定しません。どちらも workerd を使います。
workerd プロセスの動き方
EmDashは、サイトへの最初のリクエストで初期化するときに、サンドボックス型プラグインを読み込んだあとで workerd を起動し、プラグインのサービスが応答するまで最大10秒待ちます。管理画面からプラグインをインストールまたは更新すると、workerd は再起動します。workerd が標準出力(stdout)や標準エラー出力(stderr)に書き込んだ内容はすべて、[emdash:workerd] という接頭辞を付けてサーバーの出力に表示されます。
プラグインのサービスは 127.0.0.1 で待ち受け、サーバーへ戻る通信路にはUnixドメインソケットを使います(Windowsでは 127.0.0.1 のTCPポート)。外部からの接続用のポートを開ける必要はありません。
子プロセスがサーバーの環境から受け取る環境変数は PATH、HOME、TMPDIR、TMP、TEMP、LANG、LC_ALL だけです。そのため、サーバーの環境にあるシークレットはサンドボックスに渡りません。ほかの変数も渡すには、EMDASH_WORKERD_PASSTHROUGH_ENV に変数名をカンマ区切りで設定します。
workerd が予期せず終了した場合、ランナーは [emdash:workerd] workerd exited with <reason> をログに出力し、次の呼び出し時に workerd を再起動します。再起動までの待ち時間は1秒から始まり、30秒まで倍々に延びます。workerd が60秒以内に5回を超えてクラッシュすると、ランナーは再起動をやめ、[emdash:workerd] workerd crashed 5 times in 60 seconds, giving up をログに出力します。それ以降、サンドボックス型プラグインのフックとルートはすべて Plugin sandbox unavailable for <plugin>: workerd crashed 5 times in 60 seconds and the runner stopped retrying; restart the server で失敗します。サーバーを再起動すると workerd が再び起動します。管理画面からプラグインをインストールまたは更新した場合も同様です。サーバーに SIGTERM を送ると、workerd も一緒に終了します。
リソース制限
どのランナーも、プラグインの呼び出しごとに同じ一連の制限を適用します。制限の値は固定で、emdash() インテグレーションにはこれを変えるオプションがありません。
| 制限 | 値 | Cloudflare Workers | Node.js |
|---|---|---|---|
| CPU時間 | 50 ms | Worker Loaderが適用。上限に達するとプラグインが例外を投げる | 適用されない |
| サブリクエスト | 10 | Worker Loaderが適用。上限に達するとプラグインが例外を投げる | 適用されない |
| メモリー | 128 MB | プラグインごとには適用されない。プラットフォームのisolateのメモリー上限が適用される | 適用されない |
| 実時間 | 30 s | ランナーが適用 | ランナーが適用 |
フックやルートが実時間の制限を超えると、その呼び出しは Plugin <id> exceeded wall-time limit of 30000ms during hook:<name>(または route:<name>)で失敗します。フックの場合、EmDashは EmDash: Sandboxed plugin <id> という接頭辞を付けて失敗をログに出力し、そのプラグインの結果を使わずにリクエストの処理を続けます。制限を超えたプラグインのルートは、呼び出し元に対して失敗を返します。
やさしい解説
WordPressでは、動作の重いプラグインがあると、サイト全体が遅くなることがあります。EmDashのサンドボックス型プラグインには、1回の呼び出しごとに上限があり、どの環境でも30秒を超えると打ち切られます。Cloudflare Workersでは、CPU時間(50ミリ秒)と外部へのリクエストの回数(10回)にも上限があります。フックが打ち切られても、そのプラグインの結果を使わずにページの処理は続きます。
ランナーが使えないときの動作
Cloudflare Workersでは、sandbox() がビルド時に wrangler.jsonc を確認します。LOADER という名前の worker_loaders バインディングがない場合、ランナーを設定しないまま、次の警告をログに出力します。
[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding. Worker Loader requires a Workers paid plan.
ランナーを選択していても、実行時に使えないことがあります。Cloudflare Workersでは、デプロイした環境に LOADER バインディングまたは PluginBridge のエクスポートがない場合です。Node.jsでは、workerd がインストールされていない場合、またはそのバイナリが動かない場合です。このとき、EmDashは警告をログに出力し、コロンのあとにランナーが報告した原因を示します。Cloudflare Workersでバインディングがない場合は、次の警告が出力されます。
EmDash: Plugin sandbox is configured but not available on this platform: the worker has no worker_loaders binding named LOADER. Sandboxed plugins will not be loaded.
この場合、sandboxed: [] に並べたプラグインは読み込まれず、インストール済みのマーケットプレイスやレジストリのプラグインは動かず、管理画面からの新しいインストールはエラーコード SANDBOX_NOT_AVAILABLE で失敗します。サイトのほかの部分には影響しません。
サンドボックス型プラグインをプロセス内で動かす
emdash() で sandbox: false を設定すると、sandboxed: [] に並べたプラグインとインストール済みのマーケットプレイスのプラグインを、隔離も制限もなしにサーバーのプロセス内で動かします。これはデバッグ用のオプションで、不具合の原因がプラグインにあるのかサンドボックスにあるのかを切り分けるために使います。次の設定は、Node.jsのサイトでサンドボックスを無効にします。
emdash({
sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox",
sandbox: false,
});
Cloudflare Workersでは、ランタイムが sandbox: false is not supported in Cloudflare Workers で起動を拒否します。
トラブルシューティング
各項目の見出しは、サーバーがログに出力するメッセージ、または管理画面が返すエラーコードです。
「[emdash] Sandboxed plugins are disabled because wrangler.jsonc has no LOADER Worker Loader binding」
ビルド時のWranglerの設定に LOADER という名前の worker_loaders バインディングがないため、Cloudflareのアダプターがサンドボックスランナーを選択しませんでした。Workersの無料プランでは、これが想定どおりの設定です。Workersの有料プランでは、wrangler.jsonc でバインディングを有効にし、サイトを再ビルドします。
「Plugin sandbox is configured but not available on this platform」
コロンのあとの文が原因を示します。Cloudflare Workersで the worker has no worker_loaders binding named LOADER と表示された場合は、wrangler.jsonc に LOADER という名前の worker_loaders バインディングが必要です。the worker entrypoint does not export PluginBridge と表示された場合は、main に指定したファイルから PluginBridge をエクスポートする必要があります。バインディングをデプロイするには、Workersの有料プランが必要です。
Node.jsで workerd is missing or its binary does not run on this platform と表示された場合は、ランナーが workerd を実行できませんでした。確認のときに不足しているパッケージがダウンロードされないよう、インストール済みのバイナリを直接実行します。
./node_modules/.bin/workerd --version
Windowsでは node_modules\\.bin\\workerd.cmd --version を実行します。コマンドが失敗した場合は、node_modules に workerd がないか、インストールされたバイナリがこのプラットフォームで動きません。任意の依存関係を有効にして、実行先のプラットフォーム上でインストールし直します。
「workerd failed to start within 10 seconds」
子プロセスは起動しましたが、プラグインのサービスが10秒以内に応答しませんでした。このメッセージより前にある [emdash:workerd] で始まる行が、設定や起動時のエラーを含む workerd 自身の出力です。ランナーは次の呼び出し時に再試行します。
「workerd crashed 5 times in 60 seconds, giving up」
ランナーは workerd の再起動をやめています。このメッセージより前にある [emdash:workerd] workerd exited with <reason> の行が、クラッシュごとの終了コードまたはシグナルを示します。原因を修正してから、サーバーを再起動します。
プラグインのインストール時の SANDBOX_NOT_AVAILABLE
ランナーがないか使えないため、管理画面からのインストールのリクエストが拒否されました。ランナーを設定している場合、エラーメッセージの末尾には、前述の起動時の警告と同じ原因が示されます。プラットフォームに合わせてランナーを設定するか、その原因を修正し、再デプロイします。