콘텐츠로 이동

NextPDF Artisan을 위한 Chrome 렌더러 설정

브리지는 chrome-php/chrome를 통해 로컬 Chrome/Chromium 프로세스를 시작하고 제어합니다. 이 페이지에서는 렌더링이 성공하도록 해당 런타임을 설정하는 방법과 컨테이너 및 샌드박스 관련 결정을 다룹니다.

브리지가 Chrome과 통신하는 방식

섹션 제목: “브리지가 Chrome과 통신하는 방식”

BrowserPoolchrome-php/chromeBrowserFactory를 생성하고(선택적으로 명시적인 바이너리 경로를 지정), 고정된 플래그 세트로 Chrome을 시작합니다: headless: true, keepAlive: true, windowSize: [1200, 800], sendSyncDefaultTimeout: renderTimeout * 1000, 그리고 /integrations/artisan/configuration/ 페이지에 나열된 사용자 지정 플래그입니다. 그다음 브리지는 Chrome DevTools Protocol을 통해 시작된 프로세스를 제어합니다. 별도로 실행 중인 Chrome에 원격 디버깅 포트로 연결하지 않으므로, 노출하거나 인증해야 할 네트워크 엔드포인트는 없습니다. Chrome은 PHP 워커의 자식 프로세스로 실행됩니다. tests/Unit/Artisan/BrowserPoolTest.php::getBrowserCreatesAndReusesInstanceWithExpectedOptions 테스트는 이러한 시작 옵션을 정확히 검증합니다.

워커 사용자가 실행할 수 있는 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

워커 사용자로 헤드리스 실행이 가능한지 확인합니다:

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

빈 DOM과 함께 종료 코드 0이 반환되면 바이너리와 필요한 공유 라이브러리가 존재한다는 뜻입니다. 0이 아닌 종료 코드는 브리지가 ChromeRenderException으로 표출하는 실패와 동일합니다. 먼저 이 단계에서 문제를 해결해야 합니다.

브리지가 사용할 바이너리 지정

섹션 제목: “브리지가 사용할 바이너리 지정”

자동 감지(chrome-php/chrome 기본값)는 바이너리가 표준 경로에 있을 때 작동합니다. 프로덕션에서 결정론적 동작을 보장하려면 명시적으로 경로를 고정합니다:

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

또는 배열 구성으로 지정합니다:

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

컨테이너 프로비저닝 및 샌드박스 결정

섹션 제목: “컨테이너 프로비저닝 및 샌드박스 결정”

컨테이너에서 Chrome OS 샌드박스는 추가 커널 기능 없이 root / PID 1로 초기화되지 못하는 경우가 많습니다. 선택지는 두 가지입니다:

  1. 샌드박스 유지(권장). 워커를 비 root 사용자로 실행하고 Chrome 샌드박스에 필요한 기능을 컨테이너에 부여합니다(일반적으로 SYS_ADMIN, 또는 사용자 네임스페이스 생성을 허용하는 seccomp 프로필). 이렇게 하면 Chrome 프로세스 격리가 그대로 유지됩니다.
  2. 샌드박스 비활성화. no_sandbox: true를 설정합니다. Chrome은 --no-sandbox로 시작됩니다. 이는 Chrome의 프로세스 격리 샌드박스를 제거합니다 — 단순한 외형상의 플래그가 아니라 격리 수준을 실질적으로 낮추는 설정입니다. 샌드박스를 활성화할 수 없는 경우에만 사용하고, 제한된 컨테이너 안에서 비 root 사용자로 Chrome을 실행하며, 해당 배포는 입력에 더 높은 신뢰를 요구하는 배포로 취급해야 합니다. 브리지의 네트워크 차단(CSP + CDP 차단)은 어느 쪽이든 그대로 적용되지만, 프로세스 격리를 대체하지는 않습니다. 이는 신뢰할 수 없는 콘텐츠 렌더링에 대한 OWASP ASVS 최소 권한 및 격리 지침과 일치합니다.

샌드박스가 보호하는 것과 보호하지 않는 것에 대한 전체적인 경계 설명은 /integrations/artisan/security-and-operations/ 페이지에 있습니다. 이 페이지는 샌드박스 비활성화가 안전하다고 주장하지 않습니다.

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.

워커를 root가 아니라 worker(uid 10001)로 실행합니다. 브리지는 이미 --disable-dev-shm-usage 플래그를 적용하므로, 추가 튜닝 없이도 작은 /dev/shm 때문에 컨테이너에서 흔히 발생하는 충돌을 방지합니다.

브리지는 원격 글꼴 가져오기를 차단합니다(--disable-remote-fonts 및 CSP). 필요한 글꼴은 OS 계층에 설치하거나, data: URI @font-face 소스로 defaultCss 또는 HTML 내부에 임베드합니다. CJK 출력에는 이미지에 설치된 CJK 글꼴 패키지(예: fonts-noto-cjk)가 필요합니다.

호스트 애플리케이션 없이 전체 브리지 경로를 점검하려면 다음 독립 실행형 프로브를 사용합니다:

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/ 페이지에서 해당 예외를 찾아 대조합니다. 오케스트레이션된 배포에서는 이를 준비 상태 점검(readiness check)에 연결합니다.

  • Chrome을 전용 비 root 사용자로 실행합니다.
  • 호스트 메모리 제한을 적용합니다. 브리지는 100회 렌더링마다 재시작해 메모리 증가를 억제하지만, 호스트 상한은 여전히 필요합니다.
  • 신뢰할 수 없는 입력이 도달할 수 있는 모든 경로에서 render_timeout을 상위 요청 예산(budget)과 함께 사용합니다.
  • Chrome 원격 디버깅 포트를 노출하지 않습니다. 브리지는 이를 사용하지 않으며, 열린 CDP 포트는 인증되지 않은 제어 채널입니다.
증상유력한 원인참고할 위치
ChromeNotAvailableExceptionchrome-php/chrome가 설치되어 있지 않음/integrations/artisan/install/ 페이지
ChromeRenderException 첫 렌더링 시 발생바이너리 누락 / 샌드박스 초기화 실패이 페이지; /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/ 페이지