샌드박스된 플러그인은 샌드박스 러너가 제공하는 격리된 런타임에서 실행됩니다. 마켓플레이스와 레지스트리 설치는 항상 샌드박스에서 실행되며, emdash() 통합의 sandboxed: []에 나열된 플러그인도 마찬가지입니다. plugins: []에 나열된 플러그인은 서버 프로세스에서 실행되며 러너를 사용하지 않습니다.
러너는 배포 플랫폼에 따라 다릅니다. Cloudflare Workers에서 각 플러그인은 Worker Loader 바인딩을 통해 생성된 Dynamic Worker로 실행됩니다. Node.js에서 서버는 오픈소스 Workers 런타임인 workerd를 자식 프로세스로 시작하고 각 플러그인을 그 안의 서비스로 실행합니다. emdash()의 sandboxRunner 옵션이 러너를 선택합니다. 이것 없이는 sandboxed: []의 플러그인이 로드되지 않으며, 구성된 marketplace는 “Marketplace requires sandboxRunner to be configured”로 빌드를 실패시킵니다.
다음 표는 각 러너가 필요로 하고 적용하는 것을 요약합니다.
| Cloudflare Workers | Node.js | |
|---|---|---|
sandboxRunner | @emdash-cms/cloudflare의 sandbox() | "@emdash-cms/sandbox-workerd/sandbox" |
| 요구사항 | Workers Paid 플랜, worker_loaders 바인딩, Worker 엔트리 포인트에서 내보낸 PluginBridge | workerd 패키지 |
| 데이터베이스 액세스 | DB D1 바인딩 (구성된 어댑터와 무관) | 구성된 데이터베이스 |
| 적용되는 제한 | CPU 시간, 서브리퀘스트, 월 타임 | 월 타임 |
Cloudflare Workers
Dynamic Workers는 Workers Paid 플랜에서 사용할 수 있습니다. 러너에는 아래의 바인딩과 엔트리 포인트 내보내기가 필요합니다. *-cloudflare 템플릿에는 둘 다 포함되어 있습니다.
-
Worker Loader 바인딩을
wrangler.jsonc에 추가합니다. 러너는LOADER라는 이름으로 읽습니다:{ "worker_loaders": [ { "binding": "LOADER", }, ], } -
Worker 엔트리 포인트에서
PluginBridge를 내보내고,main이 해당 파일을 가리키도록 합니다.PluginBridge는 샌드박스된 플러그인이 콘텐츠, 미디어, 스토리지, 이메일에 접근하는 엔트리 포인트입니다. 러너는 엔트리 모듈의 내보내기에서 이를 찾습니다:import handler, { createScheduledHandler, PluginBridge } from "@emdash-cms/cloudflare/worker"; export { PluginBridge }; export default { ...handler, scheduled: createScheduledHandler(), } satisfies ExportedHandler;{ "main": "./src/worker.ts", } -
emdash()통합에서 러너를 선택합니다: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패키지는 선택적 의존성을 통해 현재 플랫폼(x64의 Linux, macOS, Windows; arm64의 Linux, macOS)용 바이너리를 설치합니다. 서버가 실행되는 플랫폼에서 선택적 의존성을 활성화하여 설치하세요. 멀티 스테이지 Docker 빌드에서는 런타임 스테이지와 같은 플랫폼의 스테이지에서 설치를 실행하세요. -
emdash()통합에서 러너를 선택합니다:import { sqlite } from "emdash/db"; emdash({ database: sqlite({ url: "file:./data/emdash.db" }), sandboxRunner: "@emdash-cms/sandbox-workerd/sandbox", }); -
개발을 위해
miniflare를 개발 의존성으로 설치합니다:npm install -D miniflareNODE_ENV가development(astro dev가 설정)이고miniflare가 설치된 경우, 러너는 플러그인을 Miniflare에 전달하고, Miniflare가 자체workerd프로세스를 관리합니다. 아래의 크래시 정책은 적용되지 않습니다.astro preview는NODE_ENV를production으로 설정하고node ./dist/server/entry.mjs는 설정하지 않습니다. 둘 다workerd를 사용합니다.
workerd 프로세스 실행 방법
EmDash는 사이트 첫 번째 요청 시 초기화하는 동안 workerd를 시작하고, 샌드박스된 플러그인이 로드된 후 플러그인 서비스가 응답할 때까지 최대 10초 대기합니다. 관리자에서 플러그인을 설치하거나 업데이트하면 재시작됩니다. 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>를 기록하고 다음 호출 시 재시작하며, 지연은 1초에서 시작하여 30초까지 두 배로 증가합니다. 60초 이내에 workerd가 5번 이상 크래시하면 러너는 재시작을 중단하고 [emdash:workerd] workerd crashed 5 times in 60 seconds, giving up을 기록합니다. 그 이후로 서버가 재시작될 때까지 모든 샌드박스된 플러그인 훅과 라우트는 Plugin sandbox unavailable for <plugin>: workerd is not running으로 실패합니다. 서버에 SIGTERM을 보내면 workerd도 함께 종료됩니다.
리소스 제한
각 러너는 플러그인 호출당 동일한 제한 세트를 적용합니다. 제한은 고정되어 있으며, emdash() 통합에는 이를 위한 옵션이 없습니다.
| 제한 | 값 | Cloudflare Workers | Node.js |
|---|---|---|---|
| CPU 시간 | 50 ms | Worker Loader가 적용; 플러그인이 제한에 도달하면 throw | 적용되지 않음 |
| 서브리퀘스트 | 10 | Worker Loader가 적용; 플러그인이 제한에 도달하면 throw | 적용되지 않음 |
| 메모리 | 128 MB | 플러그인별로 적용되지 않음; 플랫폼의 아이솔레이트 메모리 상한이 적용 | 적용되지 않음 |
| 월 타임 | 30 s | 러너가 적용 | 러너가 적용 |
훅이나 라우트가 월 타임 제한을 초과하면 호출은 Plugin <id> exceeded wall-time limit of 30000ms during hook:<name> (또는 route:<name>)으로 실패합니다. 훅의 경우, EmDash는 EmDash: Sandboxed plugin <id> 접두사로 실패를 기록하고 해당 플러그인의 결과 없이 요청을 계속합니다. 제한을 초과한 플러그인 라우트는 호출자에게 실패합니다.
러너를 사용할 수 없는 경우
구성된 러너도 사용할 수 없을 수 있습니다: Cloudflare Workers에서 worker_loaders 바인딩이나 PluginBridge 내보내기가 누락된 경우, Node.js에서 workerd가 설치되지 않았거나 바이너리가 실행되지 않는 경우입니다. EmDash는 런타임 시작 시 다음 경고를 기록합니다:
EmDash: Plugin sandbox is configured but not available on this platform. Sandboxed plugins will not be loaded. If using @emdash-cms/sandbox-workerd/sandbox, ensure workerd is installed.
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로 시작을 거부합니다.
문제 해결
각 항목은 서버가 기록하는 메시지 또는 관리자가 반환하는 오류 코드로 시작합니다.
”Plugin sandbox is configured but not available on this platform”
Cloudflare Workers에서 두 가지 요구사항을 확인하세요: wrangler.jsonc에 LOADER라는 이름의 worker_loaders 바인딩이 있고, main이 PluginBridge를 내보내는 파일을 가리키는지 확인합니다. 바인딩 배포에는 Workers Paid 플랜이 필요합니다.
Node.js에서 러너가 사용하는 바이너리를 실행합니다:
npx workerd --version
명령이 실패하면 workerd가 node_modules에 없거나 설치된 바이너리가 이 플랫폼에서 실행되지 않습니다. 대상 플랫폼에서 선택적 의존성을 활성화하여 다시 설치하세요.
”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
러너가 누락되거나 사용할 수 없어 관리자의 설치 요청이 거부되었습니다. 플랫폼용 러너를 구성하거나 위의 시작 경고 원인을 수정하고 재배포하세요.