解决 Node 下 Puppeteer 在 Docker 容器内无法正常启动的问题
目录
最近的有个项目生成 PDF 报告实现用的 Node.js + Puppeteer , 本地开发的时候, Mac 在使用 npm 安装 Puppeteer 会自动安装一个 Google Chrome for Testing 浏览器, 使用 Puppeteer 控制浏览器生成 PDF 整个过程没问题。
但就在构建 Docker 镜像后, 启动 Docker 用起来就会遇到 Puppeteer 无法正常启动的问题, 以下是日志里的报错信息:
Error: Failed to launch the browser process! spawn /root/.cache/puppeteer/chrome/linux-124.0.6367.207/chrome-linux64/chrome ENOENT
...
原因 #
查了下在 Docker 容器里通常是因为浏览器缺少依赖等问题造成的, 也就是说我在构建时使用的基础镜像 node:lts-alpine3.19
要安装的太多了。
我之前有试过在容器里直接用无头模式执行命令, 直接安装 chromium apk update && apk upgrade && apk add chromium
, 解决完安装还需要考虑生成的 PDF 拉丁字符都显示不出来, 需要将主机的字体 /usr/share/fonts
拷贝进去或者 sudo apt-get install fonts-noto-cjk
(思源字体),
这次是用 Node.js 所以用上了 Puppeteer , 我以为生成镜像时执行 npm install
能像我开发时一样可以安装呢。
Puppeteer 官方给的安装方法, 还蛮头大的, 最新版的不一定能用, 决定更换一个包含 Chrome 的基础镜像, 于是看上了 zenika/alpine-chrome
:
https://github.com/Zenika/alpine-chrome/ - GitHub 页
解决 #
https://hub.docker.com/r/zenika/alpine-chrome - DockerHub 页
以下是我原先的 Dockerfile :
FROM node:lts-alpine3.19 AS release
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY data/ ./data/
COPY index.js .
VOLUME /usr/src/app/data
EXPOSE 7788
CMD ["node", "index.js"]
参考 文档给的例子 经过一番修改, 最终的 Dockerfile 长这样:
# FROM node:lts-alpine3.19 AS release 换基础镜像
FROM zenika/alpine-chrome:124-with-node AS release
# 跳过 chromium 下载, 指定 chromium 浏览器路径的环境变量
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD 1
ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/chromium-browser
# 设置工作目录
WORKDIR /usr/src/app
# 将依赖文件拷贝到工作目录, copy 都加上 --chown=chrome 应该是给权限
COPY --chown=chrome package*.json ./
# 安装依赖
RUN npm install --production
# 拷贝程序文件到工作目录
COPY --chown=chrome data/ ./data/
COPY --chown=chrome index.js .
# 持久化 data 目录
VOLUME /usr/src/app/data
EXPOSE 7788
ENTRYPOINT ["tini", "--"]
CMD ["node", "index.js"]
构建镜像到本地:
docker build -t uuphy/u_name:0.2.0 --output type=docker .
启动:
docker run -d \
--network u_network \
-v u_data:/usr/src/app/data \
--name u_name \
uuphy/u_name:0.2.0
踩坑 #
就在我以为一切该结束时, 开始踩坑, 生成 PDF 时又开始报错:
Error: Failed to launch the browser process! Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted [18:18:0517/054046.375909:FATAL:zygote_host_impl_linux.cc(201)] Check failed: . : Operation not permitted (1) [0517/054046.381462:WARNING:process_reader_linux.cc(95)] sched_getscheduler: Function not implemented (38) [0517/054046.381577:WARNING:process_reader_linux.cc(95)] sched_getscheduler: Function not implemented (38) TROUBLESHOOTING: https://pptr.dev/troubleshooting at ChildProcess.onClose (/usr/src/app/node_modules/@puppeteer/browsers/lib/cjs/launch.js:310:24) at ChildProcess.emit (node:events:530:35) at ChildProcess._handle.onexit (node:internal/child_process:294:12)
大致意思就命名空间的问题, 应该是浏览器权限上的问题, 开了下特权模式启动, 看了下作者给的 readme 里, 有用 SYS_ADMIN
, 后面去掉 --privileged
参数改用 --cap-add=SYS_ADMIN
, 再次测试没有问题, 权限能给最小就最小, 用 SYS_ADMIN
好了。
总结 #
基础镜像用内置浏览器的确能节省不少时间, 感谢作者 zenika 等开源作者的付出, 如果 HTML 转 PDF 样式固定, 没有其他特殊用途, 可以在 DockerHub 里选用旧一点的 Chrome 版本作为基础镜像。
最终的启动命令为:
docker run -d \
--cap-add=SYS_ADMIN \
--network u_network \
-v u_data:/usr/src/app/data \
--name u_name \
uuphy/u_name:0.2.0