使用CodeDeploy部署Node.js至AWS EC2遇3000端口占用部署失败
问题描述
通过AWS CodeDeploy向EC2实例部署基础Node.js应用时触发部署失败:应用设计为监听3000端口,但部署启动阶段该端口已被运行中的Node.js进程占用。手动登录实例终止占用3000端口的进程后,对应进程会自动重启,端口始终无法释放。
错误信息
[stderr]+ return 1 [stderr]+ '[' -n 'Now using node v16.15.0 (npm v8.5.5)' ']' [stderr]+ '[' 0 -ne 1 ']' [stderr]+ nvm_echo 'Now using node v16.15.0 (npm v8.5.5)' [stderr]+ command printf '%s\n' 'Now using node v16.15.0 (npm v8.5.5)' [stdout]Now using node v16.15.0 (npm v8.5.5) [stderr]+ EXIT_CODE=0 [stderr]+ set -e [stderr]+ return 0 [stderr]+ npm ci [stdout] [stdout]added 183 packages, and audited 184 packages in 1s [stdout] [stdout]16 packages are looking for funding [stdout] run `npm fund` for details [stdout] [stdout]1 critical severity vulnerability [stdout] [stdout]To address all issues (including breaking changes), run: [stdout] npm audit fix --force [stdout] [stdout]Run `npm audit` for details. [stderr]+ npm start [stdout] [stdout]> web@0.0.1 start [stdout]> node server.js [stdout] [stderr]node:events:505 [stderr] throw er; // Unhandled 'error' event [stderr] ^ [stderr] [stderr]Error: listen EADDRINUSE: address already in use :::3000 [stderr] at Server.setupListenHandle [as _listen2] (node:net:1372:16) [stderr] at listenInCluster (node:net:1420:12) [stderr] at Server.listen (node:net:1508:7) [stderr] at Object.<anonymous> (/home/ec2-user/Web/server.js:47:8) [stderr] at Module._compile (node:internal/modules/cjs/loader:1105:14) [stderr] at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10) [stderr] at Module.load (node:internal/modules/cjs/loader:981:32) [stderr] at Function.Module._load (node:internal/modules/cjs/loader:822:12) [stderr] at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) [stderr] at node:internal/main/run_main_module:17:47 [stderr]Emitted 'error' event on Server instance at: [stderr] at emitErrorNT (node:net:1399:8) [stderr] at processTicksAndRejections (node:internal/process/task_queues:83:21) { [stderr] code: 'EADDRINUSE', [stderr] errno: -98, [stderr] syscall: 'listen', [stderr] address: '::', [stderr] port: 3000 [stderr]}
报错核心为EADDRINUSE: address already in use :::3000,即启动时3000端口已被占用。手动杀进程后自动重启,说明当前占用端口的Node进程受进程守护机制管控,直接kill子进程会被守护进程重新拉起。
解决方案
第一步:定位并彻底停用旧的守护进程
先顺着进程链路找到拉起Node进程的上层守护程序,从守护层停服才能彻底释放端口:
- 执行命令查询3000端口对应的进程PID:
sudo lsof -i :3000或者sudo ss -tulpn | grep :3000 - 拿到PID后查询父进程ID:
ps -o ppid= -p <上一步拿到的PID>,顺着父进程链路向上排查,常见的守护场景和处理方式如下:- 若父进程为pm2:执行
pm2 list查看托管的应用列表,执行pm2 delete <对应应用ID/名称>即可彻底停掉进程,关闭自动重启 - 若父进程为PID=1的systemd:到
/etc/systemd/system/目录下查找和Node、当前应用名相关的service配置文件,执行sudo systemctl stop <服务名>、sudo systemctl disable <服务名>即可停止服务并关闭开机自启 - 若存在定时巡检拉起规则:执行
crontab -l查看当前用户定时任务,同时检查/etc/cron.d/、/etc/crontab等系统定时任务配置,删除自动拉起Node进程的规则即可
- 若父进程为pm2:执行
第二步:优化CodeDeploy部署流程,避免后续重复出现端口冲突
在CodeDeploy的应用部署spec文件中,补充生命周期钩子逻辑,从流程上避免端口抢占:
- 在
ApplicationStop或BeforeInstall阶段增加停服逻辑:如果是pm2托管就执行对应pm2停服/重载命令,如果是systemd托管就先执行systemctl stop命令,确认旧进程完全退出、端口释放后再进入安装、启动新应用的阶段 - 可在启动脚本前增加端口检测逻辑,启动前确认端口空闲,示例逻辑:
# 检测3000端口占用 PORT_OCCUPY_PID=$(lsof -t -i:3000) if [ -n "$PORT_OCCUPY_PID" ]; then kill -9 $PORT_OCCUPY_PID # 等待2秒确认端口释放 sleep 2 fi # 后续再执行npm start启动应用
注意:端口检测杀进程逻辑必须配合守护进程停用操作使用,否则杀掉的子进程会被守护进程立刻拉起,依然会触发端口占用报错
第三步:验证修复
- 手动在EC2实例上执行
ss -tulpn | grep 3000,确认无进程占用3000端口 - 重新触发CodeDeploy部署,查看部署日志,确认应用正常启动,无EADDRINUSE报错即可
内容的提问来源于stack exchange,提问作者Dave Michaels
相关产品推荐
相关产品推荐

