Node.js + Puppeteer在Docker上,无可用沙盒

18

我正在构建一个Node.js LTS应用程序。 我遵循了Puppeteer文档,因此我的Dockerfile具有以下内容:

FROM node:12.18.0

WORKDIR /home/node/app
ADD package*.json ./

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# Install node modules
RUN npm i

# Add user so we don't need --no-sandbox.
RUN groupadd -r -f audio \
    && groupadd -r -f video \
    && usermod -a -G audio,video node \
    && mkdir -p /home/node/Downloads \
    && chown -R node:node /home/node

USER node

CMD ["google-chrome-unstable"]

应用程序构建和运行良好,但是一旦我尝试使用await puppeteer.launch();启动浏览器,就会出现以下错误:

pdf    | Error: Failed to launch the browser process!
pdf    | [0612/133635.958777:FATAL:zygote_host_impl_linux.cc(116)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using --no-sandbox.
pdf    | #0 0x5638d5faa399 base::debug::CollectStackTrace()
pdf    | #1 0x5638d5f0b2a3 base::debug::StackTrace::StackTrace()
pdf    | #2 0x5638d5f1cc95 logging::LogMessage::~LogMessage()
pdf    | #3 0x5638d77f940e service_manager::ZygoteHostImpl::Init()
pdf    | #4 0x5638d5ad5060 content::ContentMainRunnerImpl::Initialize()
pdf    | #5 0x5638d5b365e7 service_manager::Main()
pdf    | #6 0x5638d5ad3631 content::ContentMain()
pdf    | #7 0x5638d5b3580d headless::(anonymous namespace)::RunContentMain()
pdf    | #8 0x5638d5b3550c headless::HeadlessShellMain()
pdf    | #9 0x5638d35295a7 ChromeMain
pdf    | #10 0x7fc01f0492e1 __libc_start_main
pdf    | #11 0x5638d35293ea _start
pdf    | 
pdf    | Received signal 6
pdf    | #0 0x5638d5faa399 base::debug::CollectStackTrace()
pdf    | #1 0x5638d5f0b2a3 base::debug::StackTrace::StackTrace()
pdf    | #2 0x5638d5fa9f35 base::debug::(anonymous namespace)::StackDumpSignalHandler()
pdf    | #3 0x7fc0255f30e0 (/lib/x86_64-linux-gnu/libpthread-2.24.so+0x110df)
pdf    | #4 0x7fc01f05bfff gsignal
pdf    | #5 0x7fc01f05d42a abort
pdf    | #6 0x5638d5fa8e95 base::debug::BreakDebugger()
pdf    | #7 0x5638d5f1d132 logging::LogMessage::~LogMessage()
pdf    | #8 0x5638d77f940e service_manager::ZygoteHostImpl::Init()
pdf    | #9 0x5638d5ad5060 content::ContentMainRunnerImpl::Initialize()
pdf    | #10 0x5638d5b365e7 service_manager::Main()
pdf    | #11 0x5638d5ad3631 content::ContentMain()
pdf    | #12 0x5638d5b3580d headless::(anonymous namespace)::RunContentMain()
pdf    | #13 0x5638d5b3550c headless::HeadlessShellMain()
pdf    | #14 0x5638d35295a7 ChromeMain
pdf    | #15 0x7fc01f0492e1 __libc_start_main
pdf    | #16 0x5638d35293ea _start
pdf    |   r8: 0000000000000000  r9: 00007ffcd14664d0 r10: 0000000000000008 r11: 0000000000000246
pdf    |  r12: 00007ffcd1467788 r13: 00007ffcd1466760 r14: 00007ffcd1467790 r15: aaaaaaaaaaaaaaaa
pdf    |   di: 0000000000000002  si: 00007ffcd14664d0  bp: 00007ffcd1466710  bx: 0000000000000006
pdf    |   dx: 0000000000000000  ax: 0000000000000000  cx: 00007fc01f05bfff  sp: 00007ffcd1466548
pdf    |   ip: 00007fc01f05bfff efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
pdf    |  trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
pdf    | [end of stack trace]
pdf    | Calling _exit(1). Core file will not be generated.
pdf    | 
pdf    | 
pdf    | TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md
pdf    | 
pdf    |     at onClose (/home/node/app/node_modules/puppeteer/lib/launcher/BrowserRunner.js:159:20)
pdf    |     at Interface.<anonymous> (/home/node/app/node_modules/puppeteer/lib/launcher/BrowserRunner.js:149:65)
pdf    |     at Interface.emit (events.js:327:22)
pdf    |     at Interface.close (readline.js:416:8)
pdf    |     at Socket.onend (readline.js:194:10)
pdf    |     at Socket.emit (events.js:327:22)
pdf    |     at endReadableNT (_stream_readable.js:1221:12)
pdf    |     at processTicksAndRejections (internal/process/task_queues.js:84:21)

哦,是的,容器名称是 pdf

我尝试查看建议的 Puppeteer 故障排除页面,但没有找到任何解决方案。

有什么建议吗?


你需要安装一些窗口管理器,Docker镜像只带有标准tty。 - Danizavtz
4个回答

34

启动浏览器时,请使用--no-sandbox,--disable-setuid-sandbox参数。这是我的Docker文件和小脚本。它已经成功运行。

您可以通过以下参考了解有关使用Docker的Puppeteer的更多信息。

  1. https://github.com/buildkite/docker-puppeteer
  2. https://github.com/alekzonder/docker-puppeteer

Dockerfile


FROM node:12.18.0

RUN  apt-get update \
     && apt-get install -y wget gnupg ca-certificates \
     && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
     && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
     && apt-get update \
     # We install Chrome to get all the OS level dependencies, but Chrome itself
     # is not actually used as it's packaged in the node puppeteer library.
     # Alternatively, we could could include the entire dep list ourselves
     # (https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix)
     # but that seems too easy to get out of date.
     && apt-get install -y google-chrome-stable \
     && rm -rf /var/lib/apt/lists/* \
     && wget --quiet https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /usr/sbin/wait-for-it.sh \
     && chmod +x /usr/sbin/wait-for-it.sh

# Install Puppeteer under /node_modules so it's available system-wide
ADD package.json package-lock.json /
RUN npm install

CMD ["node", "index.js"]

索引.js

const puppeteer = require('puppeteer');

(async() => {

    const browser = await puppeteer.launch({
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox'
        ]
    });

    const page = await browser.newPage();

    await page.goto('https://www.google.com/', {waitUntil: 'networkidle2'});

    browser.close();

})();

1
终于在折腾了4个小时后搞定了,谢谢。 - Srinivas Rathikrindi
2
非常感谢!我尝试了大约20个“解决方案”,除了你的之外,没有一个有效! - yooloobooy
@Ahmed,你知道我在通过Docker运行Puppeteer代码时,为什么会出现“SyntaxError: Unexpected reserved word 'await'”错误吗?这是在使用TypeScript时发生的。 - Tuz
谢谢,它可以工作! - Yacine Belarbi
@Tuz,你能分享一下代码吗? - Ahmed ElMetwally
为什么这个答案得到了最多的赞同,当--no-sandbox在生产环境中运行时是一个巨大的安全威胁? - undefined

22
我找到了一种方法,可以使用Chrome沙盒,感谢usethe4ce在这里的回答。
起初,我需要将Chrome与Puppeteer分开安装,我按照以下方式编辑了我的Dockerfile:
FROM node:12.18.0

WORKDIR /home/runner/app
ADD package*.json ./

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst ttf-freefont \
        --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-unstable'})
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true


# Install node modules
RUN npm i\
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    && groupadd -r runner && useradd -r -g runner -G audio,video runner \
    && mkdir -p /home/runner/Downloads \
    && chown -R runner:runner /home/runner \
    && chown -R runner:runner /home/runner/app/node_modules

USER runner

CMD ["google-chrome-unstable"]

这样做后,错误消息从 No usable sandbox 变成了:

Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted

然后我按照usethe4ce的建议进行操作。Docker默认禁止访问某些内核级别操作,Seccomp选项可以“解锁”一些chrome需要创建自己的沙盒的操作。因此,我将这个chrome.json文件添加到我的仓库中,并按以下方式编辑了我的docker-compose文件:

version: "3.8"

services:
  <service name>:
    build:
      <build options>
    init: true
    security_opt: 
      - seccomp=<path to chrome.json file>
    [...]


如果您没有使用docker-compose文件,可以使用链接答案中建议的选项--security-opt seccomp=path/to/chrome.json运行容器。
最后,使用以下命令启动浏览器:
await puppeteer.launch({
  executablePath: 'google-chrome-unstable'
});

编辑:

如果使用自定义安装的Chrome浏览器,可能会导致Puppeteer无法完全支持其版本。唯一可以保证与特定Puppeteer版本兼容的是捆绑的Chrome浏览器。

因此,建议使用上述的security_opt选项,忽略自定义安装部分。


您好,我能否使用此方法在终端上运行测试用例而无需打开浏览器? - Jananath Banuka
是的,如果这是一个puppetteer功能,它应该可以工作,因为此方法仅为启动的浏览器启用沙盒。 无论如何,我建议使用捆绑版本的Chrome,它肯定与您正在使用的puppetteer版本兼容。请确保在Dockerfile中手动安装您正在使用的捆绑式Chromium版本的所有必要依赖项。 - Riccardo Manzan
你能给我完整可用的 Dockerfile 吗?另外,我正在使用 npx codeceptjs run 来运行测试用例。 - Jananath Banuka
依赖关系可能因Chromium版本而异,因此在Puppeteer版本之间也可能有所不同,无论如何我手头没有我的Dockerfile。 我记得我通过试错找到了我版本所需的所有必要依赖项,请参考此问题:https://github.com/puppeteer/puppeteer/issues/3443。我可能错了,我记得使用我的puppetteer版本时,并不需要该问题中提供的所有依赖项,有些是缺少的。如果您需要一些关于codeceptjs的帮助,只需提一个新问题,因为这是一个完全不相关的问题。 - Riccardo Manzan
1
非常感谢您使用 chrome.json rlz 中的 security_opt - Aga
1
在 Node v16.x 上,使用带有 json 文件的 --security-opt 对我很有效,谢天谢地... zzzzzzzzzzzzzzzzzzzzzzzzzz - TJBlackman

5
我终于找到了如何在本地机器上使用沙箱运行它的方法。我只需要阅读并应用官方 Github 存储库上的文档(链接)即可。我遗漏的部分是要使用 --cap-add=SYS_ADMIN 选项运行镜像:
docker run --cap-add=SYS_ADMIN <YOUR_IMAGE_NAME>

然而,这看起来像是一个安全漏洞,因为它似乎给了你的容器一些访问主机的权限。如果您正在阅读本文,因为您绝对想使用Chrome沙盒,那么这不一定是您想要做的事情。
我的最终用例是在Cloud Run上运行我的容器,在我看来,他们绝不会允许这样的标志。如果我最终成功在Cloud Run上使用沙盒,我会编辑我的答案...
编辑:算了,在Cloud Run上没有任何标志就可以正常工作!所以,是的,我会在我的开发机器上继续使用--cap-add=SYS_ADMIN标志,这对我来说很好。
这是我的完整Dockerfile,目前对我来说可以正常工作:
FROM node:14-slim

WORKDIR /app

RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

COPY . .

RUN yarn \
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
    && mkdir -p /home/pptruser/Downloads \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

# Run everything after as non-privileged user.
USER pptruser

CMD node src/index.js

我的src/index.js文件:
const puppeteer = require('puppeteer')

const main = async () => {
  console.log('Starting browser')
  const browser = await puppeteer.launch()
  console.log('Opening a new page')
  const page = await browser.newPage()
  console.log('Navigating to google')
  await page.goto('https://www.google.fr', {
    waitUntil: 'networkidle2'
  })
  console.log('closing browser')
  await browser.close()
}

main()

0

尽管使用--privileged标志是一种不好的安全实践,但对我来说确实起到了作用。

(无法移动到新的命名空间:支持PID命名空间,支持网络命名空间,但失败:errno =操作不允许)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接