Jenkins 中 npm start & 无法启动应用
结论
npm start & 只是让命令在当前 Shell 中后台运行,并没有把应用交给独立的服务管理器。Pipeline 的 sh 步骤结束后,Jenkins 会清理它启动的子进程。如果后台进程仍占用标准输出、标准错误等文件描述符,Jenkins 还可能一直等待。
更稳妥的做法是使用 systemd、PM2 或 Docker 管理应用,Pipeline 只负责构建和触发部署。如果只是临时验证,可以使用 nohup,完整重定向输入输出,并修改 Jenkins 用于识别子进程的 Cookie。
临时解决方案
可以把启动阶段改成:
stage('Run') {
steps {
withEnv(['JENKINS_NODE_COOKIE=dontKillMe']) {
sh '''
nohup npm start > app.log 2>&1 < /dev/null &
echo $! > app.pid
'''
}
}
}
其中每一部分都有作用:
nohup让进程忽略终端断开时产生的挂断信号。> app.log 2>&1把标准输出和标准错误写入文件。< /dev/null断开标准输入。&让 Shell 不必等待应用退出。- 修改
JENKINS_NODE_COOKIE,可以避免 Jenkins 在步骤或构建结束时把该进程识别为需要清理的子进程。 $!用于保存后台进程的 PID,方便后续停止应用。
还可以加入简单的启动检查,防止应用启动失败,而 Pipeline 仍然显示成功:
stage('Run') {
steps {
withEnv(['JENKINS_NODE_COOKIE=dontKillMe']) {
sh '''
nohup npm start > app.log 2>&1 < /dev/null &
echo $! > app.pid
sleep 5
if ! kill -0 "$(cat app.pid)" 2>/dev/null; then
echo "Application failed to start"
cat app.log
exit 1
fi
'''
}
}
}
这种检查只能确认 PID 对应的进程仍然存在。更可靠的办法是请求应用的健康检查接口,例如:
stage('Run') {
steps {
withEnv(['JENKINS_NODE_COOKIE=dontKillMe']) {
sh '''
nohup npm start > app.log 2>&1 < /dev/null &
echo $! > app.pid
for i in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:3000/ > /dev/null; then
exit 0
fi
sleep 1
done
echo "Application did not become ready"
cat app.log
exit 1
'''
}
}
}
端口和检测地址要根据实际应用调整。
更可靠的部署方式
长期运行的应用不适合直接托管在 Jenkins 的构建 Shell 中。使用 PM2 时,可以这样部署:
stage('Deploy') {
steps {
sh '''
npm ci
pm2 start npm --name lapay-payment-frontend -- start
pm2 save
'''
}
}
后续部署可以执行:
stage('Deploy') {
steps {
sh '''
npm ci
pm2 restart lapay-payment-frontend --update-env ||
pm2 start npm --name lapay-payment-frontend -- start
'''
}
}
这要求 Jenkins 执行用户已经安装并配置好 PM2。
如果服务器使用 systemd,可以提前创建服务单元,再让 Jenkins 执行:
stage('Deploy') {
steps {
sh 'sudo systemctl restart lapay-payment-frontend'
}
}
systemd 可以统一管理自动重启、开机启动、日志、权限和进程状态,应用的生命周期也不再依赖某一次 Jenkins 构建。
日志中的 Node.js 版本也需要处理
日志显示当前环境为:
node: v12.22.9
npm: 8.5.1
多个依赖明确要求使用 Node.js 14 或更高版本。例如,react-scripts@5.0.1 的要求是:
node >=14.0.0
npm install 目前只输出了 EBADENGINE 警告,但这不表示应用一定能在 Node.js 12 下正常运行。需要升级 Jenkins Agent 上的 Node.js,并尽量选择仍在维护的 LTS 版本。最终使用哪个版本,还要根据项目的 package.json、锁文件和依赖要求来确定。
如果 Jenkins 配置了 NodeJS Plugin,可以在 Pipeline 中显式指定工具:
pipeline {
agent any
tools {
nodejs 'node-lts'
}
stages {
stage('Install') {
steps {
sh 'node --version'
sh 'npm --version'
sh 'npm ci'
}
}
stage('Deploy') {
steps {
sh 'pm2 restart lapay-payment-frontend --update-env || pm2 start npm --name lapay-payment-frontend -- start'
}
}
}
}
这里的 node-lts 必须与 Jenkins 全局工具配置中的名称一致。
注意事项
React 项目中的 npm start 通常启动的是开发服务器,不适合直接用于正式生产环境。生产部署一般应先执行:
npm ci
npm run build
构建完成后,再由 Nginx、Apache、对象存储或其他静态资源服务器托管构建产物。
此外,直接保存 $! 得到的可能是 npm 包装进程的 PID,而不是最终运行的 Node.js 子进程。因此,通过 PID 文件停止服务并不总是可靠。生产环境更适合使用 systemd、PM2 或容器编排工具来管理进程。
备注:内容仅供参考。
