ข้ามไปยังเนื้อหา

การตั้งค่า Chrome renderer สำหรับ NextPDF Artisan

บริดจ์จะเปิดและควบคุมกระบวนการ Chrome/Chromium ภายในเครื่องผ่าน chrome-php/chrome ใช้หน้านี้เพื่อตั้งค่ารันไทม์ดังกล่าวให้เรนเดอร์ Portable Document Format (PDF) ได้สำเร็จ และช่วยตัดสินใจเรื่องคอนเทนเนอร์กับแซนด์บ็อกซ์ได้อย่างถูกต้อง

BrowserPool จะสร้าง chrome-php/chromeBrowserFactory (สามารถระบุพาธไบนารีอย่างชัดเจนได้) และเปิด Chrome ด้วยชุดแฟล็กตายตัว ได้แก่ headless: true, keepAlive: true, windowSize: [1200, 800], sendSyncDefaultTimeout: renderTimeout * 1000 และแฟล็กกำหนดเองที่ระบุไว้ในหน้า /integrations/artisan/configuration/ จากนั้นบริดจ์จะควบคุมกระบวนการที่เปิดอยู่ผ่าน Chrome DevTools Protocol (CDP) บริดจ์จะไม่เชื่อมต่อกับกระบวนการ Chrome แยกต่างหากผ่านพอร์ตดีบักระยะไกล จึงไม่มีปลายทางเครือข่ายที่ต้องเปิดเผยหรือยืนยันตัวตน Chrome ทำงานเป็นกระบวนการลูกของ PHP worker การทดสอบ tests/Unit/Artisan/BrowserPoolTest.php::getBrowserCreatesAndReusesInstanceWithExpectedOptions ยืนยันตัวเลือกการเปิดใช้งานเหล่านี้ไว้อย่างแม่นยำ

ติดตั้งบิลด์ Chrome หรือ Chromium ที่ผู้ใช้ worker สามารถรันได้:

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

ตรวจสอบว่าไบนารีสามารถรันแบบ headless ในฐานะผู้ใช้ worker ได้:

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

โค้ดออก 0 พร้อม Document Object Model (DOM) ว่างเปล่า หมายความว่าไบนารีและไลบรารีที่ใช้ร่วมกันมีครบถ้วน โค้ดออกที่ไม่ใช่ศูนย์คือความล้มเหลวแบบเดียวกับที่บริดจ์จะแสดงเป็น ChromeRenderException ให้แก้ไขที่จุดนี้ก่อน

การตรวจหาอัตโนมัติ (ค่าเริ่มต้นของ chrome-php/chrome) ใช้งานได้เมื่อไบนารีอยู่ในพาธมาตรฐาน แต่เพื่อให้พฤติกรรมในการใช้งานจริงคาดเดาได้ ควรกำหนดพาธอย่างชัดเจน:

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

หรือผ่านการกำหนดค่าแบบอาร์เรย์:

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

การจัดเตรียมคอนเทนเนอร์และการตัดสินใจเรื่องแซนด์บ็อกซ์

หัวข้อที่มีชื่อว่า “การจัดเตรียมคอนเทนเนอร์และการตัดสินใจเรื่องแซนด์บ็อกซ์”

ในคอนเทนเนอร์ แซนด์บ็อกซ์ระดับระบบปฏิบัติการของ Chrome มักเริ่มทำงานในฐานะ root / process identifier (PID) 1 ไม่ได้หากไม่มี kernel capability เพิ่มเติม มีสองแนวทางดังนี้:

  1. คงแซนด์บ็อกซ์ไว้ (แนะนำ) รัน worker ในฐานะผู้ใช้ที่ไม่ใช่ root และมอบ capability ที่แซนด์บ็อกซ์ของ Chrome ต้องการให้คอนเทนเนอร์ (โดยทั่วไปคือ SYS_ADMIN หรือโปรไฟล์ seccomp ที่อนุญาตให้สร้าง user-namespace ได้) วิธีนี้ช่วยให้การแยกกระบวนการของ Chrome ยังคงสมบูรณ์
  2. ปิดใช้งานแซนด์บ็อกซ์ ตั้งค่า no_sandbox: true Chrome จะเปิดด้วย --no-sandbox การตั้งค่านี้เป็นการถอดแซนด์บ็อกซ์สำหรับแยกกระบวนการของ Chrome ออกจริง จึงลดการกักกันลงจริง ไม่ใช่เพียงแฟล็กที่ไม่มีผลในทางปฏิบัติ ใช้วิธีนี้เฉพาะเมื่อไม่สามารถเปิดใช้งานแซนด์บ็อกซ์ได้ ให้รัน Chrome ในฐานะผู้ใช้ที่ไม่ใช่ root ภายในคอนเทนเนอร์ที่จำกัด และถือว่าการนำไปใช้งานรูปแบบนี้ต้องอาศัยความเชื่อถือในอินพุตที่สูงขึ้น เครื่องกั้นเครือข่ายของบริดจ์ ได้แก่ Content Security Policy (CSP) และการปิดกั้น CDP ยังคงมีผลบังคับใช้ในทุกกรณี แต่สิ่งเหล่านี้ใช้แทนการแยกกระบวนการไม่ได้ แนวทางนี้สอดคล้องกับคำแนะนำเรื่องสิทธิ์ขั้นต่ำ (least-privilege) และการแยกส่วน (isolation) ของ 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.

รัน worker ในฐานะ worker (user ID 10001) ไม่ใช่ root บริดจ์ใช้แฟล็ก --disable-dev-shm-usage อยู่แล้ว ซึ่งช่วยหลีกเลี่ยงการแครชจาก /dev/shm ที่มีขนาดเล็ก ปัญหานี้พบได้บ่อยในคอนเทนเนอร์ที่ไม่ได้ปรับแต่งเพิ่มเติม

บริดจ์จะปิดกั้นการดึงฟอนต์จากระยะไกล (--disable-remote-fonts และ CSP) ให้ติดตั้งฟอนต์ที่ต้องการในระดับระบบปฏิบัติการ หรือฝังฟอนต์เป็นแหล่ง data: Uniform Resource Identifier (URI) ของ @font-face ภายใน defaultCss หรือ Hypertext Markup Language (HTML) ผลลัพธ์ภาษาจีน ญี่ปุ่น และเกาหลี (CJK) ต้องมีแพ็กเกจฟอนต์ CJK (เช่น fonts-noto-cjk) ติดตั้งอยู่ในอิมเมจ

ใช้ probe แบบสแตนด์อโลนนี้เพื่อทดสอบเส้นทางบริดจ์ครบทั้งสายโดยไม่ต้องใช้แอปพลิเคชันโฮสต์:

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 ยืนยันการเปิดใช้งาน การเรนเดอร์ และการนำเข้า exception ที่ถูกโยนออกมาคือความล้มเหลวจริง เทียบเคียงกับข้อมูลในหน้า /integrations/artisan/troubleshooting/ เชื่อมต่อ probe นี้เป็นการตรวจสอบความพร้อม (readiness check) ในการนำไปใช้งานที่มีการกำกับการทำงาน (orchestrated deployment)

  • รัน Chrome ในฐานะผู้ใช้เฉพาะที่ไม่ใช่ root
  • กำหนดขีดจำกัดหน่วยความจำของโฮสต์ บริดจ์จำกัดการเติบโตด้วยการรีสตาร์ททุก 100 การเรนเดอร์ แต่ยังคงต้องมีเพดานในระดับโฮสต์
  • จับคู่ render_timeout กับงบประมาณคำขอในระดับต้นทาง (upstream) บนทุกเส้นทางที่อินพุตที่ไม่น่าเชื่อถือเข้าถึงได้
  • อย่าเปิดเผยพอร์ตดีบักระยะไกลของ 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/ (การใช้งานจริง)