重启Supervisor任务时Nuxt 3服务器未终止致端口占用求助
我为Nuxt应用搭建了生产环境,通过Supervisor运行Nuxt服务器。端口未被占用时启动正常,但执行supervisorctl reload或supervisorctl restart production-frontend时,旧的Nuxt服务器进程经常无法退出,导致重启时因端口占用启动失败,不想用kill -9手动清理残留进程。
当前Supervisor配置文件/etc/supervisor/conf.d/production-frontend.conf内容:
[program:production-frontend] command=/usr/bin/npm start directory=/var/www/production/frontend environment=NODE_ENV="production",PORT="3011" autostart=true autorestart=true startretries=10 startsecs=30 user=www-data stderr_logfile=/var/log/supervisor/production-frontend_error.log stdout_logfile=/var/log/supervisor/production-frontend_out.log
项目目录/var/www/production/frontend包含npm run build生成的.output文件夹和package.json,npm run start实际执行node .output/server/index.mjs启动Nuxt服务器。
启动失败时错误日志:
[nitro] [dev] [uncaughtException] Error: listen EADDRINUSE: address already in use :::3011
执行netstat -pln | grep 3011能看到残留的Nuxt进程。
解决方案
1. 让Supervisor同步终止进程组
问题核心是npm start作为父进程,可能未将Supervisor的终止信号转发给Node子进程。在Supervisor配置中添加以下参数:
stopasgroup=true killasgroup=true
stopasgroup=true:终止主进程时,连带所有子进程一起停止killasgroup=true:若进程无响应,向整个进程组发送强制终止信号,避免残留
2. 直接启动Node进程,跳过npm中间层
npm作为中间进程会干扰信号传递,直接执行Node命令更可靠:
修改配置里的command字段:
command=/usr/bin/node .output/server/index.mjs
保留原环境变量配置,让Supervisor直接管理Node进程,信号传递无损耗。
3. 配置Nuxt/Nitro优雅关闭逻辑
Nuxt基于Nitro构建,默认优雅关闭机制可能不足。在nuxt.config.ts中添加配置:
export default defineNuxtConfig({ nitro: { handlers: { 'SIGTERM': () => { console.log('收到SIGTERM信号,开始优雅关闭'); process.exit(0); } }, shutdownTimeout: 5000 // 设置5秒优雅关闭超时时间 } });
修改后重新执行npm run build,让配置生效。
4. 明确Supervisor的终止规则
在配置中补充以下参数,规范终止流程:
stopsignal=TERM stopwaitsecs=10
stopsignal=TERM:默认发送TERM信号,优先尝试优雅关闭stopwaitsecs=10:等待10秒后,若进程未退出则强制终止
最终优化后的完整配置
整合所有优化点后的配置文件:
[program:production-frontend] command=/usr/bin/node .output/server/index.mjs directory=/var/www/production/frontend environment=NODE_ENV="production",PORT="3011" autostart=true autorestart=true startretries=10 startsecs=30 user=www-data stderr_logfile=/var/log/supervisor/production-frontend_error.log stdout_logfile=/var/log/supervisor/production-frontend_out.log stopasgroup=true killasgroup=true stopsignal=TERM stopwaitsecs=10
内容的提问来源于stack exchange,提问作者Sumurai8

