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
相关产品推荐
相关产品推荐

