跳到內容

NextPDF Artisan 的 Chrome renderer(渲染器)設定

這個 bridge 會透過 chrome-php/chrome 啟動並驅動本機 Chrome/Chromium 處理程序。本頁說明如何備妥這套執行環境,讓渲染能順利完成,並說明容器與 sandbox 的取捨。

BrowserPool 會建構一個 chrome-php/chromeBrowserFactory(可選擇指定明確的執行檔路徑),並以一組固定旗標啟動 Chrome:headless: truekeepAlive: truewindowSize: [1200, 800]sendSyncDefaultTimeout: renderTimeout * 1000,以及 /integrations/artisan/configuration/ 頁面所列的自訂旗標。接著 bridge 會透過 Chrome DevTools Protocol 驅動這個已啟動的處理程序。它不會透過遠端除錯連接埠連到另一個獨立執行的 Chrome,因此沒有需要對外開放或驗證的網路 endpoint。Chrome 會以 PHP worker 的子處理程序執行。測試 tests/Unit/Artisan/BrowserPoolTest.php::getBrowserCreatesAndReusesInstanceWithExpectedOptions 會精確斷言這些啟動選項。

安裝一個 worker 使用者可執行的 Chrome 或 Chromium 版本:

Terminal window
# Debian / Ubuntu
apt-get install -y chromium
# RHEL / Fedora
dnf install -y chromium
# Alpine (containers)
apk add --no-cache chromium nss freetype harfbuzz ttf-freefont

請以 worker 使用者身分確認它能以 headless 模式執行:

Terminal window
chromium --headless --dump-dom about:blank

結束代碼為 0 且 DOM 為空,表示執行檔與其相依的共用函式庫都已就緒。結束代碼非零時,就是 bridge 會以 ChromeRenderException 呈現的同一類失敗。請先在這裡修正。

當執行檔位於標準路徑時,自動偵測(chrome-php/chrome 的預設行為)即可運作。為了讓正式環境具備確定性,請明確指定路徑:

$config = new ChromeRendererConfig(
chromeBinaryPath: '/usr/bin/chromium',
);

也可以透過陣列組態指定:

$config = ChromeRendererConfig::fromArray([
'chrome_binary' => '/usr/bin/chromium',
]);

在容器中,若沒有額外的核心 capability,Chrome 的作業系統 sandbox 通常無法在 root/PID 1 身分下初始化。你有兩種做法可選:

  1. 保留 sandbox(建議)。 以非 root 使用者執行 worker,並授予容器中 Chrome sandbox 所需的 capability(通常是 SYS_ADMIN,或允許建立 user namespace 的 seccomp 設定檔)。這可讓 Chrome 的處理程序隔離維持完整。
  2. 停用 sandbox。 設定 no_sandbox: true。Chrome 會以 --no-sandbox 啟動。這會移除 Chrome 的處理程序隔離 sandbox:它確實會降低圍堵能力,並不是裝飾性的旗標。只有在無法啟用 sandbox 時才使用它,並讓 Chrome 在受限容器內以非 root 使用者執行;同時,請把這套部署視為對輸入信任程度要求更高的情境。bridge 的網路屏障(CSP 加上 CDP 封鎖)在兩種做法下仍會生效,但無法取代處理程序隔離。這與 OWASP ASVS 對於渲染不受信任內容時採取最小權限與隔離的指引一致。

完整的邊界陳述(sandbox 保護什麼、不保護什麼)請見 /integrations/artisan/security-and-operations/ 頁面。本頁並未宣稱停用 sandbox 是安全做法。

FROM php:8.4-cli
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -u 10001 worker
USER worker
ENV CHROME_BINARY=/usr/bin/chromium
# Set CHROME_NO_SANDBOX=1 only if the sandbox cannot be enabled in your runtime.

請以 worker(uid 10001)身分執行 worker,而不是 root。bridge 已套用 --disable-dev-shm-usage 旗標,可避免容器中常見、因 /dev/shm 過小且未進一步調校而發生的當機。

bridge 會封鎖遠端字型抓取(--disable-remote-fonts 加上 CSP)。請在作業系統層安裝所需字型,或將 data: URI 形式的 @font-face 來源內嵌到 defaultCss 或 HTML 中。若要輸出 CJK,映像中需安裝 CJK 字型套件(例如 fonts-noto-cjk)。

使用這個獨立探針,可以在不啟動主應用程式的情況下,演練完整的 bridge 路徑:

chrome-health.php
<?php
declare(strict_types=1);
use NextPDF\Artisan\ChromeHtmlRenderer;
use NextPDF\Artisan\ChromeRendererConfig;
require __DIR__ . '/vendor/autoload.php';
$renderer = new ChromeHtmlRenderer(
ChromeRendererConfig::fromArray([
'chrome_binary' => getenv('CHROME_BINARY') ?: null,
'no_sandbox' => (bool) getenv('CHROME_NO_SANDBOX'),
]),
);
$result = $renderer->render('<p>ok</p>', 200.0, 0.0);
fwrite(STDOUT, strlen($result->getPdfData()) > 0 ? "CHROME_OK\n" : "CHROME_EMPTY\n");
$renderer->close();

CHROME_OK 代表啟動、渲染與匯入都成功。拋出的例外就是確切的失敗原因。請在 /integrations/artisan/troubleshooting/ 頁面比對它。在編排式部署中,請將它接成就緒檢查。

  • 以專屬的非 root 使用者執行 Chrome。
  • 套用主機記憶體上限;bridge 會透過每 100 次渲染重啟來限制成長,但仍需要主機層級的上限。
  • 對於任何不受信任輸入可達的路徑,請把 render_timeout 與上游的請求預算搭配使用。
  • 不要對外開放 Chrome 的遠端除錯連接埠。bridge 並不使用它,且開放的 CDP 連接埠就是未經驗證的控制通道。
症狀可能原因查看位置
ChromeNotAvailableExceptionchrome-php/chrome 尚未安裝參閱 /integrations/artisan/install/ 一節
首次渲染時出現 ChromeRenderException找不到執行檔/sandbox 無法初始化本頁;/integrations/artisan/troubleshooting/ 一節
PDF 為空看不到方框/Chrome 當機參閱 /integrations/artisan/troubleshooting/ 一節
遠端影像為空白網路依設計被封鎖參閱 /integrations/artisan/security-and-operations/ 一節
週期性延遲尖峰每 100 次渲染後重啟參閱 /integrations/artisan/production-usage/ 一節
  • 參閱 /integrations/artisan/install/ 一節
  • 參閱 /integrations/artisan/configuration/ 一節
  • 參閱 /integrations/artisan/security-and-operations/ 一節
  • 參閱 /integrations/artisan/troubleshooting/ 一節
  • 參閱 /integrations/artisan/production-usage/ 一節