플러그인 샌드박스 구성

이 페이지

샌드박스된 플러그인은 플러그인 선언 외에 플랫폼 러너가 필요합니다. 마켓플레이스 및 레지스트리 설치는 항상 해당 러너를 사용하며, sandboxed: []에 나열된 플러그인도 마찬가지입니다. plugins: []의 네이티브 플러그인은 EmDash 서버 프로세스에서 실행되며 샌드박스 격리를 받지 않습니다.

러너는 배포 플랫폼에 따라 다릅니다. Cloudflare Workers에서는 각 플러그인이 Worker Loader 바인딩을 통해 생성된 Dynamic Worker로 실행됩니다. Node.js에서는 서버가 오픈소스 Workers 런타임인 workerd를 자식 프로세스로 시작하고 각 플러그인을 그 안에서 서비스로 실행합니다. emdash()의 sandboxRunner 옵션이 러너를 선택하고 호스팅된 레지스트리 카탈로그를 활성화합니다. 이것 없이는 sandboxed: []의 플러그인이 로드되지 않습니다. 명시적으로 구성된 레지스트리는 계속 탐색할 수 있지만, 샌드박스된 플러그인의 설치 또는 업데이트는 SANDBOX_NOT_AVAILABLE로 실패합니다.

다음 표는 각 러너가 필요로 하고 적용하는 것을 요약합니다.

Cloudflare WorkersNode.js
sandboxRunner@emdash-cms/cloudflare의 sandbox()"@emdash-cms/sandbox-workerd/sandbox"
요구 사항Workers 유료 플랜, worker_loaders 바인딩, Worker 진입점에서 PluginBridge 내보내기workerd 패키지
데이터베이스 접근DB D1 바인딩 (구성된 어댑터와 독립적)구성된 데이터베이스
적용되는 제한CPU 시간, 서브리퀘스트, 월 타임월 타임

Cloudflare Workers

Dynamic Workers는 Workers 유료 플랜에서 사용 가능합니다. *-cloudflare 템플릿에는 아래의 진입점 내보내기가 포함되어 있지만 바인딩은 주석 처리되어 있으므로, 스캐폴딩 중 샌드박스된 플러그인을 활성화하지 않는 한 새 프로젝트는 Workers 무료 플랜으로 배포됩니다.

  1. wrangler.jsonc에서 Worker Loader 바인딩을 활성화합니다. 러너는 LOADER라는 이름으로 이를 읽으며, 이 바인딩이 존재할 때만 Cloudflare 샌드박스를 선택합니다:

    {
    	"worker_loaders": [
    		{
    			"binding": "LOADER",
    		},
    	],
    }

    Wrangler 구성이 명명된 환경을 사용하는 경우, Astro 빌드 중에 CLOUDFLARE_ENV를 설정하세요. Cloudflare Vite 플러그인과 sandbox()가 동일한 환경을 읽습니다. 바인딩은 상속되지 않으므로, 샌드박스된 플러그인을 실행하는 각 명명된 환경에 LOADER를 추가하세요.

  2. 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",
    }
  3. emdash() 통합에서 러너를 선택합니다:

    import { d1, r2, sandbox } from "@emdash-cms/cloudflare";
    
    emdash({
    	database: d1({ binding: "DB" }),
    	storage: r2({ binding: "MEDIA" }),
    	sandboxRunner: sandbox(),
    });

Node.js

  1. 피어 의존성인 workerd와 함께 러너를 설치합니다:

    npm install @emdash-cms/sandbox-workerd workerd

    workerd 패키지는 선택적 의존성을 통해 현재 플랫폼용 바이너리(x64의 Linux, macOS, Windows; arm64의 Linux와 macOS)를 설치합니다. 서버가 실행되는 플랫폼에서 선택적 의존성을 활성화하여 설치하세요. 멀티 스테이지 Docker 빌드에서는 런타임 스테이지와 동일한 플랫폼의 스테이지에서 설치를 실행하세요.

  2. emdash() 통합에서 러너를 선택합니다:

    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가 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초까지 두 배로 늘어납니다. 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 WorkersNode.js
CPU 시간50 msWorker Loader에 의해 적용; 플러그인이 제한에 도달하면 예외 발생적용되지 않음
서브리퀘스트10Worker Loader에 의해 적용; 플러그인이 제한에 도달하면 예외 발생적용되지 않음
메모리128 MB플러그인별로 적용되지 않음; 플랫폼의 isolate 메모리 상한 적용적용되지 않음
월 타임30 s러너에 의해 적용러너에 의해 적용

훅이나 라우트가 월 타임 제한을 초과하면, 호출은 Plugin <id> exceeded wall-time limit of 30000ms during hook:<name> (또는 route:<name>)으로 실패합니다. 훅의 경우, EmDash는 EmDash: Sandboxed plugin <id> 접두사로 실패를 로그에 기록하고 해당 플러그인의 결과 없이 요청을 계속합니다. 제한을 초과한 플러그인 라우트는 호출자에 대해 실패합니다.

러너를 사용할 수 없는 경우

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을 실행하세요. 명령이 실패하면 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

관리자의 설치 요청이 러너가 누락되었거나 사용할 수 없어 거부되었습니다. 러너가 구성된 경우 에러 메시지는 위의 시작 경고와 동일한 원인으로 끝납니다. 플랫폼용 러너를 구성하거나 해당 원인을 수정하고 재배포하세요.