Node.js 部署指南
更新: 8/8/2025 字数: 0 字 时长: 0 分钟
本章将介绍 Node.js 应用的部署策略、最佳实践和常用工具。
部署环境准备
服务器环境配置
bash
# Ubuntu/Debian 系统
# 更新系统包
sudo apt update && sudo apt upgrade -y
# 安装必要工具
sudo apt install -y curl wget git build-essential
# 安装 Node.js (使用 NodeSource 仓库)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# 验证安装
node --version
npm --version
# 安装 PM2 (进程管理器)
npm install -g pm2
# 安装 Nginx (反向代理)
sudo apt install -y nginx
# 启动并启用服务
sudo systemctl start nginx
sudo systemctl enable nginx
bash
# CentOS/RHEL 系统
# 更新系统
sudo yum update -y
# 安装开发工具
sudo yum groupinstall -y "Development Tools"
sudo yum install -y curl wget git
# 安装 Node.js
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install -y nodejs
# 安装 PM2
npm install -g pm2
# 安装 Nginx
sudo yum install -y nginx
sudo systemctl start nginx
sudo systemctl enable nginx
环境变量配置
bash
# /etc/environment
NODE_ENV=production
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=myuser
DB_PASSWORD=mypassword
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-jwt-secret
API_KEY=your-api-key
javascript
// config/environment.js
const config = {
development: {
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
name: process.env.DB_NAME || 'myapp_dev',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password'
},
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379'
},
logging: {
level: 'debug'
}
},
production: {
port: process.env.PORT || 3000,
database: {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
name: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: true,
pool: {
min: 2,
max: 10
}
},
redis: {
url: process.env.REDIS_URL
},
logging: {
level: 'info'
},
security: {
jwtSecret: process.env.JWT_SECRET,
apiKey: process.env.API_KEY
}
}
};
module.exports = config[process.env.NODE_ENV || 'development'];
PM2 进程管理
PM2 配置文件
javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'myapp',
script: './app.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 3000
},
// 日志配置
log_file: './logs/combined.log',
out_file: './logs/out.log',
error_file: './logs/error.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
// 自动重启配置
max_memory_restart: '1G',
restart_delay: 4000,
max_restarts: 10,
min_uptime: '10s',
// 监控配置
watch: false,
ignore_watch: ['node_modules', 'logs'],
// 其他配置
kill_timeout: 5000,
wait_ready: true,
listen_timeout: 3000
}],
// 部署配置
deploy: {
production: {
user: 'deploy',
host: ['192.168.1.100'],
ref: 'origin/main',
repo: 'git@github.com:username/myapp.git',
path: '/var/www/myapp',
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production'
},
staging: {
user: 'deploy',
host: ['192.168.1.101'],
ref: 'origin/develop',
repo: 'git@github.com:username/myapp.git',
path: '/var/www/myapp-staging',
'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env staging'
}
}
};
PM2 常用命令
bash
# 启动应用
pm2 start ecosystem.config.js --env production
# 重启应用
pm2 restart myapp
# 重新加载应用(零停机时间)
pm2 reload myapp
# 停止应用
pm2 stop myapp
# 删除应用
pm2 delete myapp
# 查看应用状态
pm2 status
pm2 list
# 查看日志
pm2 logs myapp
pm2 logs myapp --lines 100
# 监控应用
pm2 monit
# 保存 PM2 配置
pm2 save
# 设置开机自启
pm2 startup
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u $USER --hp $HOME
# 部署应用
pm2 deploy production setup
pm2 deploy production
健康检查和自动重启
javascript
// health/healthCheck.js
const http = require('http');
const { promisify } = require('util');
class HealthChecker {
constructor(options = {}) {
this.port = options.port || process.env.PORT || 3000;
this.timeout = options.timeout || 5000;
this.interval = options.interval || 30000;
this.maxFailures = options.maxFailures || 3;
this.failures = 0;
this.isHealthy = true;
this.checks = new Map();
this.startHealthCheck();
}
// 添加健康检查项
addCheck(name, checkFn) {
this.checks.set(name, checkFn);
}
// 执行所有健康检查
async runChecks() {
const results = {};
let allHealthy = true;
for (const [name, checkFn] of this.checks.entries()) {
try {
const startTime = Date.now();
const result = await Promise.race([
checkFn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), this.timeout)
)
]);
results[name] = {
status: 'healthy',
responseTime: Date.now() - startTime,
details: result
};
} catch (error) {
results[name] = {
status: 'unhealthy',
error: error.message
};
allHealthy = false;
}
}
return {
status: allHealthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
checks: results
};
}
// 启动定期健康检查
startHealthCheck() {
setInterval(async () => {
try {
const health = await this.runChecks();
if (health.status === 'healthy') {
this.failures = 0;
this.isHealthy = true;
} else {
this.failures++;
if (this.failures >= this.maxFailures) {
this.isHealthy = false;
console.error('应用健康检查失败,准备重启...');
// 通知 PM2 重启
if (process.send) {
process.send('shutdown');
} else {
process.exit(1);
}
}
}
} catch (error) {
console.error('健康检查执行失败:', error);
}
}, this.interval);
}
// 创建健康检查端点
createHealthEndpoint(app) {
app.get('/health', async (req, res) => {
try {
const health = await this.runChecks();
const statusCode = health.status === 'healthy' ? 200 : 503;
res.status(statusCode).json({
...health,
uptime: process.uptime(),
memory: process.memoryUsage(),
pid: process.pid
});
} catch (error) {
res.status(500).json({
status: 'error',
error: error.message
});
}
});
// 简单的存活检查
app.get('/ping', (req, res) => {
res.json({ status: 'ok', timestamp: Date.now() });
});
}
}
module.exports = HealthChecker;
javascript
// 使用健康检查
const express = require('express');
const HealthChecker = require('./health/healthCheck');
const db = require('./db/connection');
const redis = require('./db/redis');
const app = express();
const healthChecker = new HealthChecker();
// 添加数据库健康检查
healthChecker.addCheck('database', async () => {
const result = await db.query('SELECT 1');
return { connected: true, result: result.rows[0] };
});
// 添加 Redis 健康检查
healthChecker.addCheck('redis', async () => {
const result = await redis.ping();
return { connected: true, response: result };
});
// 添加外部 API 健康检查
healthChecker.addCheck('external-api', async () => {
const response = await fetch('https://api.example.com/health');
return { status: response.status, ok: response.ok };
});
// 创建健康检查端点
healthChecker.createHealthEndpoint(app);
// 优雅关闭
process.on('SIGTERM', () => {
console.log('收到 SIGTERM 信号,开始优雅关闭...');
server.close(() => {
console.log('HTTP 服务器已关闭');
// 关闭数据库连接
db.end(() => {
console.log('数据库连接已关闭');
process.exit(0);
});
});
});
const server = app.listen(3000, () => {
console.log('服务器启动在端口 3000');
// 通知 PM2 应用已准备就绪
if (process.send) {
process.send('ready');
}
});
Nginx 反向代理
Nginx 配置
nginx
# /etc/nginx/sites-available/myapp
upstream myapp {
least_conn;
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3003 max_fails=3 fail_timeout=30s;
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$server_name$request_uri;
}
# HTTPS 配置
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
# SSL 证书配置
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# API 请求
location /api/ {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# 超时配置
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 缓冲配置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# 限流
limit_req zone=api burst=20 nodelay;
}
# WebSocket 支持
location /socket.io/ {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 健康检查
location /health {
proxy_pass http://myapp;
access_log off;
}
# 主应用
location / {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# 错误页面
error_page 502 503 504 /50x.html;
location = /50x.html {
root /var/www/html;
}
}
# 限流配置
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
}
SSL 证书配置
bash
# 安装 Certbot
sudo apt install certbot python3-certbot-nginx
# 获取 SSL 证书
sudo certbot --nginx -d myapp.com -d www.myapp.com
# 自动续期
sudo crontab -e
# 添加以下行
0 12 * * * /usr/bin/certbot renew --quiet
# 测试续期
sudo certbot renew --dry-run
Docker 容器化部署
Dockerfile
dockerfile
# Dockerfile
# 使用官方 Node.js 镜像
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制 package 文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production && npm cache clean --force
# 复制源代码
COPY . .
# 构建应用(如果需要)
RUN npm run build
# 生产镜像
FROM node:18-alpine AS production
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 设置工作目录
WORKDIR /app
# 复制依赖和应用
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package*.json ./
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
# 切换到非 root 用户
USER nextjs
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
# 启动应用
CMD ["node", "dist/app.js"]
Docker Compose
yaml
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: myapp
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgres
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
volumes:
- ./logs:/app/logs
networks:
- app-network
healthcheck:
test: ["CMD", "node", "healthcheck.js"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
postgres:
image: postgres:15-alpine
container_name: myapp-postgres
restart: unless-stopped
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=myuser
- POSTGRES_PASSWORD=mypassword
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d myapp"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
nginx:
image: nginx:alpine
container_name: myapp-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/ssl/certs
depends_on:
- app
networks:
- app-network
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridge
健康检查脚本
javascript
// healthcheck.js
const http = require('http');
const options = {
host: 'localhost',
port: process.env.PORT || 3000,
path: '/health',
timeout: 2000
};
const request = http.request(options, (res) => {
console.log(`健康检查状态: ${res.statusCode}`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on('error', (err) => {
console.log('健康检查失败:', err.message);
process.exit(1);
});
request.on('timeout', () => {
console.log('健康检查超时');
request.destroy();
process.exit(1);
});
request.end();
Docker 部署命令
bash
# 构建镜像
docker build -t myapp:latest .
# 运行容器
docker run -d \
--name myapp \
-p 3000:3000 \
-e NODE_ENV=production \
-e DB_HOST=localhost \
--restart unless-stopped \
myapp:latest
# 使用 Docker Compose
docker-compose up -d
# 查看日志
docker-compose logs -f app
# 扩展服务
docker-compose up -d --scale app=3
# 更新服务
docker-compose pull
docker-compose up -d
# 清理
docker-compose down
docker system prune -f
CI/CD 自动化部署
GitHub Actions
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
env:
NODE_ENV: test
DB_HOST: localhost
DB_PORT: 5432
DB_NAME: test
DB_USER: postgres
DB_PASSWORD: postgres
REDIS_URL: redis://localhost:6379
- name: Run coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
myapp:latest
myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/myapp
git pull origin main
docker-compose pull
docker-compose up -d
docker system prune -f
- name: Health check
run: |
sleep 30
curl -f https://myapp.com/health || exit 1
- name: Notify deployment
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()
部署脚本
bash
#!/bin/bash
# deploy.sh
set -e
APP_NAME="myapp"
APP_DIR="/var/www/$APP_NAME"
BACKUP_DIR="/var/backups/$APP_NAME"
GIT_REPO="https://github.com/username/myapp.git"
BRANCH="main"
echo "开始部署 $APP_NAME..."
# 创建备份
echo "创建备份..."
mkdir -p $BACKUP_DIR
cp -r $APP_DIR $BACKUP_DIR/$(date +%Y%m%d_%H%M%S)
# 保留最近 5 个备份
ls -t $BACKUP_DIR | tail -n +6 | xargs -r rm -rf
# 更新代码
echo "更新代码..."
cd $APP_DIR
git fetch origin
git reset --hard origin/$BRANCH
# 安装依赖
echo "安装依赖..."
npm ci --production
# 运行数据库迁移
echo "运行数据库迁移..."
npm run migrate
# 构建应用
echo "构建应用..."
npm run build
# 重启应用
echo "重启应用..."
pm2 reload ecosystem.config.js --env production
# 等待应用启动
echo "等待应用启动..."
sleep 10
# 健康检查
echo "执行健康检查..."
if curl -f http://localhost:3000/health; then
echo "部署成功!"
else
echo "健康检查失败,回滚到上一个版本..."
# 回滚
LATEST_BACKUP=$(ls -t $BACKUP_DIR | head -n 1)
cp -r $BACKUP_DIR/$LATEST_BACKUP/* $APP_DIR/
pm2 reload ecosystem.config.js --env production
echo "回滚完成"
exit 1
fi
# 清理
echo "清理临时文件..."
npm cache clean --force
docker system prune -f
echo "部署完成!"
监控和日志
日志管理
javascript
// logger/winston.js
const winston = require('winston');
const path = require('path');
// 自定义日志格式
const logFormat = winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
winston.format.errors({ stack: true }),
winston.format.json()
);
// 创建 logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
defaultMeta: {
service: 'myapp',
version: process.env.APP_VERSION || '1.0.0'
},
transports: [
// 错误日志
new winston.transports.File({
filename: path.join(__dirname, '../logs/error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// 组合日志
new winston.transports.File({
filename: path.join(__dirname, '../logs/combined.log'),
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// 控制台输出
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
],
// 异常处理
exceptionHandlers: [
new winston.transports.File({
filename: path.join(__dirname, '../logs/exceptions.log')
})
],
// 拒绝处理
rejectionHandlers: [
new winston.transports.File({
filename: path.join(__dirname, '../logs/rejections.log')
})
]
});
// 生产环境不输出到控制台
if (process.env.NODE_ENV === 'production') {
logger.remove(winston.transports.Console);
}
module.exports = logger;
应用监控
javascript
// monitoring/metrics.js
const prometheus = require('prom-client');
const logger = require('../logger/winston');
class MetricsCollector {
constructor() {
// 创建注册表
this.register = new prometheus.Registry();
// 添加默认指标
prometheus.collectDefaultMetrics({
register: this.register,
prefix: 'myapp_'
});
// 自定义指标
this.httpRequestDuration = new prometheus.Histogram({
name: 'myapp_http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
});
this.httpRequestTotal = new prometheus.Counter({
name: 'myapp_http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
this.activeConnections = new prometheus.Gauge({
name: 'myapp_active_connections',
help: 'Number of active connections'
});
this.databaseConnections = new prometheus.Gauge({
name: 'myapp_database_connections',
help: 'Number of database connections',
labelNames: ['state']
});
this.cacheHitRate = new prometheus.Gauge({
name: 'myapp_cache_hit_rate',
help: 'Cache hit rate'
});
// 注册指标
this.register.registerMetric(this.httpRequestDuration);
this.register.registerMetric(this.httpRequestTotal);
this.register.registerMetric(this.activeConnections);
this.register.registerMetric(this.databaseConnections);
this.register.registerMetric(this.cacheHitRate);
}
// HTTP 请求中间件
httpMetricsMiddleware() {
return (req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = (Date.now() - startTime) / 1000;
const route = req.route ? req.route.path : req.path;
this.httpRequestDuration
.labels(req.method, route, res.statusCode)
.observe(duration);
this.httpRequestTotal
.labels(req.method, route, res.statusCode)
.inc();
});
next();
};
}
// 更新活跃连接数
updateActiveConnections(count) {
this.activeConnections.set(count);
}
// 更新数据库连接数
updateDatabaseConnections(active, idle) {
this.databaseConnections.labels('active').set(active);
this.databaseConnections.labels('idle').set(idle);
}
// 更新缓存命中率
updateCacheHitRate(rate) {
this.cacheHitRate.set(rate);
}
// 获取指标
async getMetrics() {
return await this.register.metrics();
}
// 创建指标端点
createMetricsEndpoint(app) {
app.get('/metrics', async (req, res) => {
try {
res.set('Content-Type', this.register.contentType);
const metrics = await this.getMetrics();
res.end(metrics);
} catch (error) {
logger.error('获取指标失败:', error);
res.status(500).end('Internal Server Error');
}
});
}
}
module.exports = new MetricsCollector();
安全配置
安全中间件
javascript
// middleware/security.js
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
// 安全配置
function setupSecurity(app) {
// Helmet 安全头
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: []
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
// 限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 限制每个 IP 100 个请求
message: {
error: 'Too many requests from this IP, please try again later.'
},
standardHeaders: true,
legacyHeaders: false
});
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 50,
message: {
error: 'Too many API requests from this IP, please try again later.'
}
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 登录限制更严格
message: {
error: 'Too many authentication attempts, please try again later.'
},
skipSuccessfulRequests: true
});
// 应用限流
app.use(limiter);
app.use('/api/', apiLimiter);
app.use('/auth/', authLimiter);
// 慢速攻击防护
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000,
delayAfter: 50,
delayMs: 500
});
app.use(speedLimiter);
// 数据清理
app.use(mongoSanitize()); // 防止 NoSQL 注入
app.use(xss()); // 防止 XSS 攻击
app.use(hpp()); // 防止 HTTP 参数污染
// CORS 配置
app.use((req, res, next) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Max-Age', '86400');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
}
module.exports = setupSecurity;
总结
Node.js 应用部署涉及多个方面:
- 环境准备:服务器配置、依赖安装、环境变量设置
- 进程管理:使用 PM2 管理应用进程,实现自动重启和负载均衡
- 反向代理:使用 Nginx 提供静态文件服务、SSL 终止和负载均衡
- 容器化:使用 Docker 实现应用的标准化部署
- 自动化:通过 CI/CD 实现自动化测试和部署
- 监控:实施日志管理和性能监控
- 安全:配置安全中间件和防护措施