docker部署nodejs服务需要注意的问题

docker部署nodejs服务需要注意的问题
寒霜无头浏览器完成任务后关闭浏览器
详细说明
你希望在任务完成后关闭 Playwright 启动的浏览器。你当前的 a(type) 函数里,浏览器是在函数开头启动的,但在上传图片后没有关闭。正确做法是在上传完成(无论成功或失败)后调用 browser.close()。
- 在 a(type) 函数中,上传图片的 Promise 完成后(resolve 或 reject 前),都调用 await browser.close()。
- 可以用 try…finally 保证无论上传成功还是失败都能关闭浏览器。
修改如下:
async function a(type) {
const start = Date.now();
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// ... 省略部分代码 ...
try {
// ... 省略页面操作和截图 ...
return await new Promise((resolve, reject) => {
cos.putObject(
{
// ... 省略参数 ...
},
(err, data) => {
if (err) {
console.log("上传失败", err);
reject({ code: "E0002", msg: "图片上传失败" });
} else {
console.log("上传成功", data.Location);
if (data.statusCode == "200") {
const url = `https://${data.Location}`;
logger.info({ 图片上传完成: url });
resolve({ code: "0000", msg: "success", data: { url } });
}
}
}
);
});
} finally {
await browser.close();
}
}
这样可以确保无论上传成功还是失败,浏览器都能被正确关闭,避免资源泄漏。
问题
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:36 ? 00:00:01 node index.js
root 149 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 150 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 152 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 153 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 256 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 257 1 0 07:38 ? 00:00:00 [headless_shell] <defunct>
root 296 1 0 07:43 ? 00:00:00 [headless_shell] <defunct>
root 297 1 0 07:43 ? 00:00:00 [headless_shell] <defunct>
root 299 1 0 07:43 ? 00:00:00 [headless_shell] <defunct>
root 300 1 0 07:43 ? 00:00:00 [headless_shell] <defunct>
root 434 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 435 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 437 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 438 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 547 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 575 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 576 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 578 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 579 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 663 1 0 07:46 ? 00:00:00 [headless_shell] <defunct>
root 707 0 0 07:48 pts/0 00:00:00 bash
root 715 707 0 07:48 pts/0 00:00:00 ps -ef
在 Docker 部署后,发现有大量 [headless_shell] <defunct> 进程(僵尸进程),这说明 Playwright 启动的 Chromium 浏览器虽然调用了 browser.close(),但其子进程没有被正确回收,导致僵尸进程。
原因分析
- 这通常是 Node.js 进程作为 PID 1 运行在容器内时(即没有被其他进程如
tini或dumb-init包裹),不会自动回收子进程,导致僵尸进程堆积。 - 这不是代码问题,而是 Docker 容器内 Node 进程管理的问题。
解决方法
修改 dockerfile 文件
# 安装 tini
RUN apt-get update && apt-get install -y tini
# 用 tini 启动你的应用
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "index.js"]
如果不改影响性能吗
短期内影响不大,长期会有影响。
僵尸进程本身不会占用太多 CPU 和内存
僵尸进程只是占用很少的系统资源(主要是进程表项),不会消耗 CPU,也不会占用大量内存。但僵尸进程数量多了会有隐患
- 每个僵尸进程会占用一个进程号(PID),Linux 系统的 PID 是有限的(通常最多 32768 或更高)。
- 如果僵尸进程持续累积,最终可能导致系统无法分配新的 PID,届时无法再创建新进程(包括新的浏览器、shell、甚至系统服务),这会导致服务异常甚至崩溃。
- 进程表项过多,也会让系统管理进程的效率下降,极端情况下影响系统性能。
容器重启后会清空僵尸进程
但如果你的服务是高并发、长时间运行的,僵尸进程会不断累积,风险会逐渐变大。
结论
- 短时间/低并发下影响不大。
- 长期/高并发下,僵尸进程会越来越多,最终影响系统稳定性和性能。
- 最佳实践:一开始就用 tini 或 dumb-init,彻底解决僵尸进程问题。
dockerfile
# 使用 Playwright 的官方基础镜像
FROM mcr.microsoft.com/playwright:v1.49.0-noble
RUN npm config set registry https://registry.npmmirror.com
# 安装 pnpm
RUN npm install -g pnpm
# 安装 tini
RUN apt-get update && apt-get install -y tini
# 设置工作目录
WORKDIR /app
# 复制 package.json 和 pnpm-lock.yaml 文件
COPY package.json pnpm-lock.yaml ./
# 安装项目依赖
RUN pnpm install
# 复制项目文件
COPY . .
# 用 tini 启动 node
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["node", "index.js"]