Skip to content
第 190 / 250 章Node⏱ 12 分钟阅读

第 190 章:部署与运维

学习目标

  • 学会 Docker 容器化部署
  • 掌握 PM2 进程管理
  • 部署到云平台
  • 学会监控与日志

一、Docker 化

1.1 多阶段 Dockerfile

dockerfile
# 第一阶段:构建
FROM node:20-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json pnpm-lock.yaml ./

# 安装 pnpm
RUN npm install -g pnpm

# 安装依赖
RUN pnpm install --frozen-lockfile

# 复制源码
COPY . .

# 构建
RUN pnpm run build

# 第二阶段:运行
FROM node:20-alpine

ENV NODE_ENV=production

WORKDIR /app

COPY package*.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --prod --frozen-lockfile

COPY --from=builder /app/dist ./dist

EXPOSE 3000

CMD ["node", "dist/main.js"]

1.2 .dockerignore

node_modules
dist
.git
.env
*.log
coverage
.vscode
.idea

1.3 构建和运行

bash
docker build -t myapp:1.0.0 .
docker run -d \
  --name myapp \
  -p 3000:3000 \
  --env-file .env \
  myapp:1.0.0

二、Docker Compose

yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USER=admin
      - DB_PASS=secret
      - DB_NAME=myapp
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - app-net

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - '5432:5432'
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U admin']
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-net

  redis:
    image: redis:7-alpine
    ports:
      - '6379:6379'
    volumes:
      - redisdata:/data
    networks:
      - app-net

  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./certs:/etc/nginx/certs
    depends_on:
      - app
    networks:
      - app-net

volumes:
  pgdata:
  redisdata:

networks:
  app-net:
    driver: bridge

启动:

bash
docker-compose up -d
docker-compose logs -f app

三、Nginx 反向代理

nginx
# nginx.conf
upstream nestjs {
  server app:3000;
}

server {
  listen 80;
  server_name api.example.com;

  client_max_body_size 10M;

  location / {
    proxy_pass http://nestjs;
    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;
  }

  location /static/ {
    alias /var/www/static/;
    expires 7d;
  }
}

HTTPS 配置:

nginx
server {
  listen 443 ssl http2;
  server_name api.example.com;

  ssl_certificate /etc/nginx/certs/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/privkey.pem;

  location / {
    proxy_pass http://nestjs;
    # ... 同上
  }
}

四、PM2(传统服务器)

4.1 安装

bash
npm install -g pm2

4.2 ecosystem.config.js

javascript
module.exports = {
  apps: [{
    name: 'myapp',
    script: 'dist/main.js',
    instances: 'max',          // 进程数 = CPU 核心数
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'development',
      PORT: 3000,
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    max_memory_restart: '500M',
    error_file: 'logs/err.log',
    out_file: 'logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    watch: false,
  }],
};

4.3 常用命令

bash
pm2 start ecosystem.config.js --env production
pm2 list
pm2 logs
pm2 restart myapp
pm2 reload myapp          # 零停机重启
pm2 stop myapp
pm2 delete myapp
pm2 monit                  # 监控面板
pm2 startup                # 开机自启
pm2 save

五、健康检查

5.1 探针接口

typescript
@Controller('health')
export class HealthController {
  constructor(
    private dataSource: DataSource,
    @InjectRedis() private redis: Redis,
  ) {}

  @Get()
  async check() {
    const checks = await Promise.all([
      this.checkDb(),
      this.checkRedis(),
    ]);

    const allOk = checks.every(c => c.status === 'ok');

    return {
      status: allOk ? 'ok' : 'degraded',
      uptime: process.uptime(),
      timestamp: Date.now(),
      checks,
    };
  }

  private async checkDb() {
    try {
      await this.dataSource.query('SELECT 1');
      return { name: 'database', status: 'ok' };
    } catch (e) {
      return { name: 'database', status: 'fail', error: e.message };
    }
  }

  private async checkRedis() {
    try {
      await this.redis.ping();
      return { name: 'redis', status: 'ok' };
    } catch (e) {
      return { name: 'redis', status: 'fail', error: e.message };
    }
  }
}

5.2 探针配置

typescript
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn', 'log'],
    // 优雅关闭
    abortOnError: false,
  });

  app.enableShutdownHooks();   // 监听 SIGTERM

  await app.listen(3000);
}

5.3 Docker 健康检查

yaml
services:
  app:
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 40s

六、配置生产环境

6.1 关闭调试

typescript
// main.ts
const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn'],   // 生产关掉 log/debug
});

// 关掉 sync
synchronize: false,

6.2 启用 HTTPS

typescript
import * as fs from 'fs';

async function bootstrap() {
  const httpsOptions = {
    key: fs.readFileSync('certs/privkey.pem'),
    cert: fs.readFileSync('certs/fullchain.pem'),
  };

  const app = await NestFactory.create(AppModule, { httpsOptions });
  await app.listen(3000);
}

6.3 启用压缩

typescript
import * as compression from 'compression';

app.use(compression());

6.4 安全头

typescript
import helmet from 'helmet';
app.use(helmet());

6.5 限流

typescript
ThrottlerModule.forRoot([{
  ttl: 60_000,
  limit: 100,
}])

七、部署平台

7.1 Railway / Render

bash
# 登录
railway login

# 部署
railway up
json
// railway.json
{
  "$schema": "https://railway.app/railway.schema.json",
  "build": { "builder": "NIXPACKS" },
  "deploy": {
    "startCommand": "node dist/main.js",
    "healthcheckPath": "/health",
    "restartPolicyType": "ON_FAILURE"
  }
}

7.2 Fly.io

toml
# fly.toml
app = "myapp"
primary_region = "sin"

[build]
  dockerfile = "Dockerfile"

[env]
  NODE_ENV = "production"

[[services]]
  internal_port = 3000
  protocol = "tcp"

  [[services.ports]]
    port = 80
    handlers = ["http"]
    force_https = true

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

  [services.concurrency]
    type = "connections"
    hard_limit = 100

[[services.http_checks]]
  interval = "10s"
  timeout = "2s"
  grace_period = "10s"
  method = "get"
  path = "/health"

7.3 阿里云 ECS

bash
# 1. SSH 到服务器
ssh root@your-server

# 2. 安装 Docker
curl -fsSL https://get.docker.com | sh

# 3. 复制文件
scp -r ./dist ./node_modules ./package.json root@server:/app

# 4. 启动
cd /app && pm2 start ecosystem.config.js --env production

八、CI/CD(GitHub Actions)

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: user/myapp:${{ github.sha }}

      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /app
            docker-compose pull
            docker-compose up -d
            pnpm migration:run

九、监控

9.1 Prometheus + Grafana

bash
pnpm add @willsoto/nestjs-prometheus prom-client
typescript
import { PrometheusModule } from '@willsoto/nestjs-prometheus';

@Module({
  imports: [
    PrometheusModule.register({
      defaultMetrics: { enabled: true },
    }),
  ],
})
export class AppModule {}

9.2 健康监控

  • UptimeRobot:HTTP 探针
  • Sentry:异常捕获
  • New Relic / Datadog:APM
bash
pnpm add @sentry/node
typescript
import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
});

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalFilters(new SentryExceptionFilter());

  await app.listen(3000);
}

十、性能调优清单

项目配置
Node.js--max-old-space-size=4096
PM2instances: 'max'
数据库连接池connectionLimit: 20
Redis持久化 + 内存淘汰策略
Nginxworker_processes auto
HTTP/2
Gzip
CDN静态资源
HTTP 缓存ETag / Cache-Control

十一、本章小结

方式特点
Docker Compose单机/小团队
K8s大规模/集群
PM2单机传统部署
Serverless流量波动大
Railway/Fly.ioPaaS 快速部署
监控用途
Prometheus指标
Grafana可视化
Sentry异常
ELK日志聚合

动手练习

  1. 写一个 Dockerfile,多阶段构建
  2. 用 Docker Compose 启动 app + db + redis
  3. 配置 Nginx 反向代理 + HTTPS
  4. 部署到 Railway / Fly.io

推荐阅读


恭喜你完成了 NestJS 全部 25 章的学习! 🎉

全书总结:你已经掌握了从 NestJS 基础到生产部署的完整技能栈,包括模块化架构、依赖注入、数据库集成、认证授权、缓存队列、容器化部署。

下一步建议:

  • 🛠️ 实战:做一个完整的 SaaS 项目
  • 📖 进阶:GraphQL / 微服务 / WebSocket
  • 🤖 AI:集成 OpenAI API 做智能应用

本站基于 VitePress 构建 · 由 Codebook 团队维护