将 Node.js 应用部署到生产环境的实用框架
了解针对 Node.js 应用制定的完整生产检查清单,内容涵盖服务器、敏感信息管理、进程管理、HTTPS、CI/CD、日志记录以及备份方案。
在自己的机器上运行 Node.js 应用其实是最简单的部分。
npm run dev
API 能正常响应,数据库也能连接,一切似乎都运行良好。
这时有人会问:
“那我们该如何将它部署到生产环境呢?”
这时才明白,仅仅构建 API 只是完成了一半工作。
部署到生产环境后会带来一系列新问题:
- 应用实际上将在哪里运行?
- 如果应用崩溃,有什么机制能让它继续运行?
- 外部请求是如何到达 Node.js 进程的?
- 环境变量应该存储在哪里?
- 如何设置 HTTPS?
- 如何发布新版本?
- 出现错误时该如何处理?
- 如果服务器本身重启会怎样?
以下是一个用于规划 Node.js 生产环境设置的实用框架。
1. 理解生产架构
常见的基本生产环境设置如下:
Internet
│
▼
┌─────────┐
│ Nginx │
│ :80/443│
└────┬────┘
│
▼
┌─────────────┐
│ Node.js │
│ Application │
└──────┬──────┘
│
┌────────┼────────┐
▼ ▼ ▼
PostgreSQL Redis External APIs
核心原则是,最终用户通常不应直接连接到类似以下这样的组件:
localhost:3000
相反,Nginx 会位于前端,接收公共请求并将其转发给你的 Node.js 应用程序。
2. 准备服务器
你可以从 AWS、GCP、Azure 或 DigitalOcean 等提供商处获取 VPS 或云虚拟机。
在开始其他操作之前,首先需要配置服务器本身。
在 Ubuntu 系统上,通常从以下步骤开始:
sudo apt update
sudo apt upgrade -y
之后,安装应用程序所需的各类工具。
对于 Node.js 本身,建议通过 nvm 这样的版本管理工具来安装,这样就能精确控制运行的版本。
可通过以下方式确认已安装的版本:
node -v
npm -v
生产环境中运行的 Node 版本应与本地测试的版本一致。
这看似是个小细节,但版本不匹配会在部署后引发难以排查的故障。
3. 不要在代码中存放敏感信息
这个错误很容易犯,但也很容易避免。
避免像这样硬编码凭证:
const DATABASE_URL =
"postgresql://user:password@database.com/mydb";
并且绝不要将敏感信息提交到 Git 历史记录中。
应将其定义为环境变量:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://...
REDIS_URL=redis://...
JWT_SECRET=...
然后在代码中通过以下方式读取它们:
process.env.DATABASE_URL
确保将 .env 文件排除在版本控制之外:
.env
.env.production
一个被泄露的数据库密码所造成的危害,远超过任何部署错误。
4. 构建应用程序
在启动应用之前,仅安装生产环境所需的依赖项,如果框架需要的话再执行构建步骤。
例如,一个 TypeScript 项目可能会这样运行:
npm ci
npm run build
然后使用以下命令启动编译后的输出:
npm start
具体命令会因所使用的技术栈而有所不同。
重要的是遵循这一原则:
生产环境中的请求应指向生产版本的应用,绝不能指向开发服务器。
换言之,切勿意外地让类似以下内容:
npm run dev
作为实际运行进程存在。
5. 如果 Node.js 崩溃会怎样?
假设你直接以这种方式启动应用:
node dist/server.js
在某个时刻,会出现问题导致进程意外终止。
此时您的 API 已处于离线状态,且没有任何方法可以使其恢复运行。
这正是进程管理器要解决的问题。
PM2 是这方面广泛使用的工具。
请全局安装它:
npm install -g pm2
然后在 PM2 的监控下启动您的应用程序:
pm2 start dist/server.js --name my-api
查看其状态:
pm2 status
查看日志:
pm2 logs my-api
或根据需要重新启动它:
pm2 restart my-api
其优势不仅在于易用性,更在于您的进程现在处于主动监控之下,而无需在终端窗口中运行并寄希望于不会被意外终止。
6. 在服务器重启后自动启动应用程序
服务器无论是否出于有意,都可能会被重启。
例如:
Server reboot
↓
Operating system starts
↓
Node.js application?
您不希望每次这种情况发生时都手动通过SSH登录。
PM2可以为您的系统生成启动脚本:
pm2 startup
然后保存当前正在运行的进程列表:
pm2 save
有了这个机制,您的应用程序在重启后可以自动恢复在线。
7. 在Node.js前端添加Nginx
假设您的Node.js服务器绑定在:
localhost:3000
但您的用户访问的是:
https://api.example.com
Nginx可以作为反向代理来填补这一空白。
简化后的配置可能如下所示:
server {
listen 80;server_name api.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这样一来,请求路径就变成了:
User
↓
https://api.example.com
↓
Nginx :80
↓
Node.js :3000
这样,Node.js应用程序监听的端口就无需直接暴露在公共互联网上。
8. 添加HTTPS
在普通环境下运行生产环境API
http://
这样还不够。你需要通过特定方式来提供它。
https://
一种广泛使用的方法是将 Let's Encrypt 与 Certbot 结合使用。
首先安装 Certbot:
sudo apt install certbot python3-certbot-nginx
然后为你的域名申请并应用证书:
sudo certbot --nginx -d api.example.com
Certbot 会帮你处理证书的配置以及 HTTPS 的设置。
完成后,你的请求流程如下:
Client
↓
HTTPS
↓
Nginx
↓
Node.js
↓
Database / Redis
9. 别忘了防火墙
服务器上的并非所有端口都需要对外部开放。
通常情况下,你希望公众能够访问的只有:
80 → HTTP
443 → HTTPS
以及通过 SSH 进行的访问:
22
而 PostgreSQL 或 Redis 所使用的端口,除非有特殊原因且已设置严格的访问控制,否则通常不应对互联网开放。
具体规则会因您的配置而异,但核心原则始终不变:
仅暴露确实需要公开的内容。
10. 手动部署可行,直到它不再可行
在初期,部署可能只是:
git pull
npm install
npm run build
pm2 restart my-api
对于小型项目来说这完全没问题。
但迟早您会发现自己不得不反复执行相同的步骤:
Developer pushes code
↓
SSH into server
↓
git pull
↓
install dependencies
↓
build
↓
restart
此时就值得考虑自动化处理了。
11. CI/CD改变了工作流程
使用 GitHub Actions 等工具后,流程可以转变为:
Developer
↓
git push
↓
GitHub
↓
CI/CD Pipeline
↓
Build + Test
↓
Deploy
↓
Production
最简化的流程定义可能如下所示:
name: Deploy
on:
push:
branches:
- mainjobs:
deploy:
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v4 - name: Install dependencies
run: npm ci - name: Build
run: npm run build - name: Test
run: npm test
具体的部署步骤将取决于您的基础设施情况。
更重要的是其背后的原则:
不要对尚未理解的部署流程进行自动化处理。
先学习如何手动完成该流程。
只有之后才能将重复性步骤转化为自动化操作。
12. 日志记录不可或缺
即使实际用户遇到错误,应用程序也可能看似运行正常。
日志就是用来发现问题的工具。
至少你需要得到以下问题的答案:
When did the error happen?
Which endpoint failed?
What status code was returned?
What was the error?
How long did the request take?
一个简单示例:
console.error({
message: error.message,
endpoint: req.originalUrl,
method: req.method,
timestamp: new Date().toISOString()
});
对于任何大规模运行的系统,结构化的日志配合集中的日志聚合功能,其效果远优于在代码中随处使用console.log()调用。
13. 监控的内容不止错误
没有异常出现并不意味着系统就处于健康状态。
你还需要关注以下方面的情况:
CPU
Memory
Disk
Request latency
Error rate
Database performance
Redis health
Traffic
例如:
Requests → 1,500/min
Average latency → 180ms
Error rate → 0.4%
CPU → 42%
Memory → 61%
这样的指标能让你更全面地了解系统的实际运行状况。
14. 备份比部署更重要
以下是大多数开发者都不愿考虑的情况:
Production database
↓
Something goes wrong
↓
Data disappears
你可以随时重新部署应用程序代码。
然而,数据库中存储的数据一旦丢失就可能无法再恢复。
正因如此,备份也绝非可选项。
对于生产环境中的重要内容,你必须制定备份计划,同样重要的是,你还得真正掌握如何从这些备份中恢复数据。
从未尝试过恢复的备份是不可信赖的。
15. 无停机部署是另一个问题
在某些情况下,为了部署而短暂让应用离线已不再是可以接受的做法。
试想以下场景:
Old version running
↓
New version deployed
↓
Traffic gradually moves
↓
Old version removed
根据您的基础设施配置不同,您可能会采用以下方法:
- 并行运行多个 Node.js 进程实例
- 使用 PM2 的内置集群模式
- 通过负载均衡器将流量分配到各个实例上
- 逐步逐个节点部署更新,而非一次性全部部署
- 在旧环境与新环境之间切换流量(蓝绿部署模式)
- 将应用打包到容器中
- 使用 Kubernetes 对所有组件进行协调管理
不过,拥有 Node.js API 并不意味着就一定需要 Kubernetes。
初期保持简单,只有在实际需求出现时再逐步扩展架构。
16. 我的生产环境检查清单
在认为某个 Node.js 应用已准备好投入生产之前,以下事项是需要检查的:
[ ] Production environment configured
[ ] Secrets stored securely
[ ] Database connection configured
[ ] Redis configured if required
[ ] Production build tested
[ ] Process manager configured
[ ] Application restart tested
[ ] Nginx configured
[ ] HTTPS configured
[ ] Firewall configured
[ ] Logs available
[ ] Error monitoring configured
[ ] Database backups configured
[ ] Backup restoration tested
[ ] Deployment process documented
[ ] CI/CD configured if needed
[ ] Health check endpoint available
17. 我所考虑的架构
Node.js 系统的基本生产环境配置通常会呈现如下结构:
┌─────────────┐
│ Internet │
└──────┬──────┘
│
▼
┌─────────────┐
│ Nginx │
│ SSL / Proxy│
└──────┬──────┘
│
┌─────────┴─────────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ Node.js │ │ Node.js │
│ Instance 1 │ │ Instance 2 │
└─────┬──────┘ └─────┬──────┘
│ │
└─────────┬─────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
PostgreSQL Redis External APIs
在那个核心框架之外,你通常还需要:
Monitoring
Logging
Backups
CI/CD
Security
最后的思考
将 Node.js 应用部署到生产环境绝非仅仅:
npm start
真正的生产环境工作意味着要为系统启动后的所有可能情况做好规划。
如果系统意外出现故障,你的应对计划是什么?
服务器重启后系统的表现如何?
当接入流量突然增加时,系统会如何响应?
数据库无法访问时会发生什么?
一旦发布了有缺陷的版本,该如何恢复?
如果凭证意外泄露,应遵循何种处理流程?
这就是两者之间的差距:
“在我的机器上可以运行。”
还有
“在正式环境中也能稳定运行。”
一开始不必搭建庞大的基础设施。
从小规模开始做起。
充分了解每新增的一个组件。
将重复性任务自动化处理。
关注真正重要的指标。
只有当系统确实需要时才增加复杂性。
部署并非开发的终点, 而是软件真正开始运行的起点。
相关阅读
- 适用于本地开发及生产环境的Node.js命令参考手册 — 一份便于查阅的命令参考资料,涵盖Node.js版本管理、包管理工具、环境配置、调试方法、PM2使用以及无中断Linux部署技巧。