Cloud Run部署的Node.js服务关闭时未触发SIGTERM处理程序
问题
我通过Cloud Run部署了基于Express的Node.js Web服务,按Cloud Run文档设置了SIGINT和SIGTERM处理程序以实现数据库连接优雅关闭。本地运行时,Ctrl-C和kill命令可正常触发关闭流程并生成对应日志,但部署到Cloud Run后,处理程序从未被调用,日志资源管理器中无预期日志。
最初使用gcloud run deploy --source从源码部署,经提醒后改用ENTRYPOINT的Exec形式构建Docker镜像部署,仍未解决问题。此前还尝试过Procfile、--command和--args参数,均无效。请问为何部署后SIGTERM处理程序未被调用?
相关代码与配置
Node.js信号处理代码
['SIGINT', 'SIGTERM'].forEach(signal => { process.on(signal, async () => { logger.info(`Received ${signal}`); await shutdown(); // 执行清理操作 }); });
Dockerfile
FROM node:22-slim ... ENTRYPOINT ["node", "-r", "dotenv/config", "bin/src/index.js"]
部署命令
gcloud run deploy [name] \ --image [image name] \ ...
可能的原因与解决方案
1. 终止宽限期不足导致进程被强制杀死
Cloud Run发送SIGTERM后会等待10秒默认宽限期,若进程未在这段时间内退出,会直接发送SIGKILL强制终止。如果你的shutdown()异步操作耗时超过10秒,日志还没输出就会被中断。
- 解决:检查
shutdown()逻辑是否能在10秒内完成,或通过gcloud run deploy --timeout延长终止宽限期(最大900秒)。
2. 异步处理未显式退出进程
Node.js默认不会等待异步信号处理函数执行完成就退出,可能导致await shutdown()还没执行完,进程就已经终止,日志无法写入。
- 解决:在异步操作完成后显式调用
process.exit():
['SIGINT', 'SIGTERM'].forEach(signal => { process.on(signal, async () => { logger.info(`Received ${signal}`); await shutdown(); logger.info("清理完成,退出进程"); process.exit(0); }); });
3. 日志输出缓冲未及时写入
Node.js的日志库或console可能会缓冲输出,进程终止时缓冲内容还没来得及同步到Cloud Logging就被中断。
- 解决:
- 配置日志库为同步输出(如Winston设置
sync: true); - 在清理完成后添加短暂延迟再退出,给日志足够写入时间:
await shutdown(); logger.info("清理完成"); setTimeout(() => process.exit(0), 100);
- 配置日志库为同步输出(如Winston设置
4. Node进程不是容器PID 1
即使使用Exec形式ENTRYPOINT,若镜像中存在间接启动脚本,可能导致Node进程不是容器的PID 1进程。Linux下PID 1默认忽略SIGTERM,信号无法传递到Node进程。
- 解决:
- 确保ENTRYPOINT直接启动Node进程,不通过中转脚本;
- 若必须用启动脚本,在脚本中用
exec替换当前进程:
再将Dockerfile的ENTRYPOINT设为#!/bin/sh exec node -r dotenv/config bin/src/index.js["./start.sh"]
5. 实例未触发终止逻辑
Cloud Run仅在实例缩容、版本更新或手动删除时发送SIGTERM,若实例一直处于活跃状态,不会触发信号。
- 解决:
- 部署新版本或手动删除当前实例,触发终止流程;
- 停止所有请求,等待Cloud Run自动缩容实例。
内容的提问来源于stack exchange,提问作者Myk Willis

