跳转到内容

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/ 一节