Pular para o conteúdo

Configuração do renderizador Chrome no NextPDF Artisan

A ponte inicia e controla um processo local do Chrome/Chromium por meio do chrome-php/chrome. Use esta página para configurar esse runtime de modo que as renderizações em Portable Document Format (PDF) sejam concluídas com sucesso e para orientar as decisões corretas sobre contêiner e sandbox.

BrowserPool constrói um chrome-php/chromeBrowserFactory (opcionalmente com um caminho de binário explícito) e inicia o Chrome com um conjunto fixo de flags: headless: true, keepAlive: true, windowSize: [1200, 800], sendSyncDefaultTimeout: renderTimeout * 1000 e as flags personalizadas listadas na página /integrations/artisan/configuration/. Em seguida, a ponte controla o processo iniciado por meio do Chrome DevTools Protocol (CDP). Ela não se conecta a um processo Chrome separado por uma porta de depuração remota; portanto, não há endpoint de rede a expor ou autenticar. O Chrome é executado como um processo filho do worker PHP. O teste tests/Unit/Artisan/BrowserPoolTest.php::getBrowserCreatesAndReusesInstanceWithExpectedOptions verifica exatamente essas opções de inicialização.

Instale uma build do Chrome ou Chromium que o usuário do worker consiga executar:

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

Verifique se ele é executado em modo headless como o usuário do worker:

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

Código de saída 0 com um Document Object Model (DOM) vazio significa que o binário e suas bibliotecas compartilhadas estão presentes. Uma saída diferente de zero corresponde à mesma falha que a ponte apresenta como uma ChromeRenderException. Corrija isso aqui primeiro.

A detecção automática (o padrão do chrome-php/chrome) funciona quando há um binário em um caminho padrão. Para ter comportamento determinístico em produção, fixe-o explicitamente:

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

ou por meio de configuração em array:

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

Provisionamento de contêiner e a decisão sobre o sandbox

Seção intitulada “Provisionamento de contêiner e a decisão sobre o sandbox”

Em um contêiner, o sandbox de sistema operacional do Chrome muitas vezes não consegue inicializar como root / process identifier (PID) 1 sem capacidades de kernel adicionais. Você tem duas opções:

  1. Mantenha o sandbox (preferível). Execute o worker como um usuário não root e conceda ao contêiner as capacidades de que o sandbox do Chrome precisa (geralmente SYS_ADMIN ou um perfil seccomp que permita a criação de user-namespace). Isso preserva o isolamento de processos do Chrome.
  2. Desabilite o sandbox. Defina no_sandbox: true. O Chrome é iniciado com --no-sandbox. Isso remove o sandbox de isolamento de processos do Chrome: é uma redução real de contenção, não uma flag cosmética. Use isso apenas quando o sandbox não puder ser habilitado, execute o Chrome como um usuário não root dentro de um contêiner restrito e trate a implantação como exigindo maior confiança na entrada. As barreiras de rede da ponte, a Content Security Policy (CSP) e o bloqueio do CDP permanecem em vigor de qualquer forma, mas não substituem o isolamento de processos. Isso está alinhado com as orientações de menor privilégio e isolamento do OWASP ASVS para renderizar conteúdo não confiável.

A declaração completa de limites, incluindo o que o sandbox protege e o que não protege, está na página /integrations/artisan/security-and-operations/. Esta página não afirma que seja seguro desabilitar o 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.

Execute o worker como worker (user ID 10001), não como root. A ponte já aplica a flag --disable-dev-shm-usage, que evita o crash causado por /dev/shm pequeno, comum em contêineres sem ajustes adicionais.

A ponte bloqueia buscas de fontes remotas (--disable-remote-fonts e CSP). Instale as fontes necessárias na camada do sistema operacional ou incorpore-as como fontes data: Uniform Resource Identifier (URI) @font-face dentro do defaultCss ou do Hypertext Markup Language (HTML). A saída em chinês, japonês e coreano (CJK) requer um pacote de fontes CJK (por exemplo fonts-noto-cjk) instalado na imagem.

Use esta sonda independente para exercitar o caminho completo da ponte sem a aplicação host:

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 confirma a inicialização, a renderização e a importação. Qualquer exceção lançada representa a falha exata. Identifique-a na página /integrations/artisan/troubleshooting/. Conecte esta sonda como uma verificação de prontidão em implantações orquestradas.

  • Execute o Chrome como um usuário não root dedicado.
  • Aplique um limite de memória no host; a ponte limita o crescimento reiniciando a cada 100 renderizações, mas um teto no host ainda é necessário.
  • Combine render_timeout com um orçamento de requisição upstream em qualquer caminho acessível a entradas não confiáveis.
  • Não exponha uma porta de depuração remota do Chrome. A ponte não usa nenhuma, e uma porta CDP aberta é um canal de controle não autenticado.
SintomaCausa provávelOnde procurar
ChromeNotAvailableExceptionchrome-php/chrome não instalado/integrations/artisan/install/
ChromeRenderException na primeira renderizaçãoBinário ausente / o sandbox não consegue inicializarEsta página; /integrations/artisan/troubleshooting/
PDF vazioNenhuma caixa visível / crash do Chrome/integrations/artisan/troubleshooting/
Imagens remotas em brancoRede bloqueada por design/integrations/artisan/security-and-operations/
Pico de latência periódicoReinicialização a cada 100 renderizações/integrations/artisan/production-usage/
  • /integrations/artisan/install/
  • /integrations/artisan/configuration/
  • /integrations/artisan/security-and-operations/
  • /integrations/artisan/troubleshooting/
  • /integrations/artisan/production-usage/