You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker容器中Puppeteer运行Chrome两小时后出现Aw snap崩溃问题

问题描述

我有一个部署在Docker容器中的应用,该应用通过Puppeteer调用Google Chrome访问指定URL,并借助FFMPEG实现RTMP直播推流。容器运行约2小时后,Chrome会出现“Aw snap”错误并崩溃。

我不是代码编写者,初步推测是缓存或内存耗尽导致崩溃,但不知道具体修复方案。以下是主应用代码:

import puppeteer from 'puppeteer-extra';
import { launch, getStream } from "puppeteer-stream";
import { URL } from 'node:url';
// add stealth plugin and use defaults (all evasion techniques)
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
import {startStream, stopStream} from './app-scripts/rtmp-streaming.mjs';
import {startSocat} from './app-scripts/socat-inspector.mjs';
import {generic_webpage} from './app-scripts/generic.mjs';
import { readFileSync } from 'node:fs';

const SCREEN_RESOLUTION = process.env.SCREEN_RESOLUTION;// '1280x720' //'1920x1080';
const [SCREEN_W,SCREEN_H] = SCREEN_RESOLUTION.split('x');
const RTMP_URL = process.env.RTMP_URL || '';
const [rtmpRawUrl, rtmpSource] = RTMP_URL?.split('|');
const streamingConfiguration = {
  RTMP_URL:rtmpRawUrl,
  RTMP_SOURCE:rtmpSource || 'C',
  SCREEN_RESOLUTION,
  DISPLAY: process.env.DISPLAY,
  VIDEO_FRAMERATE:30,
  VIDEO_BITRATE:10000,
  VIDEO_GOP:60,
  AUDIO_BITRATE:'160k',
  AUDIO_SAMPLERATE:48000,
  AUDIO_CHANNELS:2
};

const preloadFile = readFileSync('./preload.js', 'utf8');
    

const appReady = async (readyMessage, browser)=>{
  const page = (await browser.pages())[0];
  console.log('got page, ready to stream');
  startStream(streamingConfiguration, page);
  console.log(readyMessage);  
}
const microphoneNameFromKey= (key)=>{
  if(!key) return 'VirtualCableB_Microphone';
  if(key.length===1) return `VirtualCable${key}_Microphone`;
  return key;
}
const speakerNameFromKey= (key)=>{
  if(!key) return 'VirtualCableA_Speaker';
  if(key.length===1) return `VirtualCable${key}_Speaker`;
  return key;
}
const cameraNameFromKey= (key)=>{
  if(!key) return 'VirtualCableA_Camera';
  if(key.length===1) return `VirtualCable${key}_Camera`;
  return key;
}
await (async () => {
  if(!process.env.BROWSER_URL){
    console.log('Exitting, must specify source URL')
    return;
  }
  puppeteer.use(StealthPlugin());
  const appUrls = [];
  if(process.env.BROWSER_URL){
    appUrls.push(process.env.BROWSER_URL);
  }
  if(process.env.BROWSER2_URL){
    appUrls.push(process.env.BROWSER2_URL);
  }
  if(process.env.BROWSER3_URL){
    appUrls.push(process.env.BROWSER3_URL);
  }
  const apps = [];
  appUrls.forEach((appUrl)=>{
    const [rawurl,appHandlerName,microphone,speaker,camera]=appUrl.split('|');
    console.log(`adding browser1 using index ${apps.length}`);
    startSocat(23222+apps.length,21222+apps.length)
    const url = new URL(rawurl);
    
    apps.push({
      inspectorPort:21222+apps.length,
      url,
      auth:process.env[`SITE_AUTH_${url.host.replace(/[^a-zA-Z]/g,'_')}`] || ':',
      SCREEN_W,
      SCREEN_H,
      SCREEN_X:SCREEN_W*apps.length,
      appHandlerName: appHandlerName || url.host.replace(/\./g,'-'),
      participantName : process.env.MEETING_PARTICIPANT_NAME || 'AWS Bot',
      microphone:microphoneNameFromKey(microphone),
      speaker:speakerNameFromKey(speaker),
      camera:cameraNameFromKey(camera)
    });
  })
  
  const appPromises = [];
  
  for (let index = 0; index < apps.length; index++) {
    const app = apps[index];
    
    const browser = await launch({
      headless: false,
      defaultViewport: null,
      ignoreHTTPSErrors: true, //TODO : Make this an option
      protocolTimeout: 0,
      executablePath: '/usr/bin/google-chrome',
      ignoreDefaultArgs: ['--enable-automation'],
      args: [
        '--kiosk',
        '--no-sandbox', 
        '--disable-gpu',
        // '--disable-extensions',
        `--window-size=${app.SCREEN_W},${app.SCREEN_H}`,
        `--window-position=${app.SCREEN_X},0`,
        '--no-first-run',
        '--no-default-browser-check',
        `--remote-debugging-port=${app.inspectorPort}`,
        '--remote-debugging-address=0.0.0.0',
        '--use-fake-ui-for-media-stream',
        '--autoplay-policy=no-user-gesture-required',
        '--auto-select-desktop-capture-source=Entire screen'
      ],
      port:app.inspectorPort
    });
    const page = (await browser.pages())[0];
    page.evaluateOnNewDocument(preloadFile);
    
    try{
      console.log(`running app ${JSON.stringify(app)}`);
      let {default:dynamicSiteHandler} = await import(`./app-scripts/${app.appHandlerName}.mjs`);
      appPromises.push(dynamicSiteHandler(browser,app,async (msg)=>{appReady(msg,browser)}));     
    }
    catch(err)
    {
      console.log(`Missing app for host ${app.appHandlerName}, using generic app handler : ${err.message}`);
      appPromises.push(generic_webpage(browser,app,async (msg)=>{appReady(msg,browser)}));     
    }
  }
  await Promise.all(appPromises).then(results=>{
    console.log('All apps exited');
    stopStream();
  }).catch(err=>{
    console.error(err);
  });
  process.exit(0);
  
})();
排查与修复建议

1. Chrome启动参数优化

在Puppeteer的args数组中添加以下参数,限制内存和缓存占用:

  • --disk-cache-size=52428800:将磁盘缓存上限设为50MB(可根据需求调整)
  • --max-old-space-size=1024:限制V8引擎内存为1GB(根据容器内存配置调整)
  • --disable-background-networking:关闭后台网络请求,减少资源消耗
  • --disable-background-timer-throttling:禁用后台计时器节流,避免内存泄漏
  • --memory-pressure-off:关闭内存压力检测,适合长期运行的直播场景

修改后的args示例:

args: [
  '--kiosk',
  '--no-sandbox', 
  '--disable-gpu',
  `--window-size=${app.SCREEN_W},${app.SCREEN_H}`,
  `--window-position=${app.SCREEN_X},0`,
  '--no-first-run',
  '--no-default-browser-check',
  `--remote-debugging-port=${app.inspectorPort}`,
  '--remote-debugging-address=0.0.0.0',
  '--use-fake-ui-for-media-stream',
  '--autoplay-policy=no-user-gesture-required',
  '--auto-select-desktop-capture-source=Entire screen',
  // 新增参数
  '--disk-cache-size=52428800',
  '--max-old-space-size=1024',
  '--disable-background-networking',
  '--disable-background-timer-throttling',
  '--memory-pressure-off'
]

2. 内存泄漏与自动恢复

  • 监听Chrome断开连接事件,实现自动重启:
    在创建browser实例后添加:
    browser.on('disconnected', async () => {
      console.log('Chrome crashed, restarting...');
      // 此处可复用原代码的browser创建逻辑,实现实例重启
    });
    
  • 定时清理页面缓存:在页面运行过程中,每隔30分钟执行一次缓存清理:
    setInterval(async () => {
      const page = (await browser.pages())[0];
      await page.evaluate(() => {
        // 清理Service Worker缓存
        if ('caches' in window) {
          caches.keys().then(cacheNames => cacheNames.forEach(name => caches.delete(name)));
        }
        // 清理本地存储
        localStorage.clear();
        sessionStorage.clear();
        // 清理IndexedDB(如果存在)
        indexedDB.databases().then(dbs => dbs.forEach(db => indexedDB.deleteDatabase(db.name)));
      });
    }, 1800000); // 30分钟
    

3. Docker容器配置优化

  • 启动容器时添加内存限制,避免无节制占用资源:
    docker run --memory=2g --memory-swap=2g your-image-name
    
  • 使用轻量级Chrome镜像:替换为debian:bookworm-slim基础镜像安装chromium-browser,比官方Google Chrome镜像更节省资源
  • 定期清理容器内临时文件:在Dockerfile中添加定时任务,清理Chrome默认缓存目录/tmp:
    RUN echo "0 */2 * * * rm -rf /tmp/*" >> /etc/crontab
    RUN service cron start
    

4. Puppeteer模式优化

  • 尝试启用新版无头模式:将headless: false改为headless: 'new',新版无头模式资源占用更低,稳定性更好(需对应Chrome 112+版本)

内容的提问来源于stack exchange,提问作者Mash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 19:43:10