NestJS 生产环境最佳实践

学习目标

  • 了解 NestJS 应用在生产环境中的特殊需求
  • 掌握环境变量的合理配置和管理方法
  • 学习生产环境的日志配置和管理策略
  • 掌握生产环境的性能优化技巧
  • 了解监控和可观测性的实现方法
  • 学习生产环境的部署和维护最佳实践

生产环境概述

生产环境是应用最终面向用户的运行环境,它需要具备高可用性、稳定性、安全性和性能。与开发环境不同,生产环境的配置和管理需要更加谨慎和严格,以确保应用能够稳定运行并提供良好的用户体验。

生产环境的特点

  • 高可用性:应用需要持续稳定运行,最小化 downtime
  • 性能要求:需要处理真实的用户流量,性能至关重要
  • 安全性:需要保护用户数据和系统安全
  • 可观测性:需要能够监控系统状态和诊断问题
  • 可扩展性:需要能够根据流量变化进行扩展
  • 合规性:可能需要符合特定行业的合规要求

生产环境与开发环境的差异

方面 开发环境 生产环境
环境变量 本地配置,可能包含敏感信息 安全管理,使用环境变量或 secrets 管理服务
日志级别 详细,包含调试信息 精简,关注错误和关键信息
性能优化 较少关注,优先开发速度 高度优化,关注响应时间和资源使用
监控 基本,可能仅本地日志 全面,包括应用性能、系统状态等
部署 本地运行,手动部署 自动化部署,CI/CD 集成
容错性 较低,可能直接崩溃 较高,包含错误处理和恢复机制

环境变量配置

环境变量是管理应用配置的重要方式,特别是在生产环境中,它们可以帮助我们安全地管理敏感信息,同时提供不同环境的配置灵活性。

1. 使用 @nestjs/config 模块

NestJS 提供了 @nestjs/config 模块,用于管理环境变量和配置。

安装依赖

npm install @nestjs/config

基础配置

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.production', '.env'],
      ignoreEnvFile: process.env.NODE_ENV === 'production', // 在生产环境中使用系统环境变量
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_URL: Joi.string().required(),
        JWT_SECRET: Joi.string().required(),
        LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
      }),
    }),
  ],
})
export class AppModule {}

安装 Joi 进行配置验证

npm install joi

2. 环境变量管理策略

本地开发环境

  • 使用 .env 文件存储本地配置
  • .env 文件添加到 .gitignore,避免提交到版本控制系统
  • 使用 .env.example 文件作为模板,说明需要哪些环境变量

测试环境

  • 使用与生产环境类似的配置,但使用测试数据
  • 可以使用 CI/CD 系统的环境变量管理功能

生产环境

  • 使用云服务提供商的 secrets 管理服务(如 AWS Secrets Manager、GCP Secret Manager、Azure Key Vault)
  • 或使用容器编排系统的 secrets 管理(如 Kubernetes Secrets)
  • 避免在代码或配置文件中硬编码敏感信息
  • 定期轮换敏感信息(如数据库密码、API 密钥)

3. 环境变量的使用

在服务中使用

// user.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class UserService {
  constructor(private configService: ConfigService) {
    const jwtSecret = this.configService.get('JWT_SECRET');
    const databaseUrl = this.configService.get('DATABASE_URL');
  }
}

在配置文件中使用

// database.config.ts
import { ConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const getDatabaseConfig = (configService: ConfigService): TypeOrmModuleOptions => {
  return {
    type: 'postgres',
    url: configService.get('DATABASE_URL'),
    entities: [__dirname + '/../**/*.entity{.ts,.js}'],
    synchronize: configService.get('NODE_ENV') !== 'production',
    ssl: configService.get('NODE_ENV') === 'production',
  };
};

在 main.ts 中使用

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  
  const port = configService.get('PORT') || 3000;
  const host = configService.get('HOST') || '0.0.0.0';
  
  await app.listen(port, host);
  console.log(`Application running on: http://${host}:${port}`);
}
bootstrap();

日志配置

日志是生产环境中诊断问题和监控系统状态的重要工具。合理的日志配置可以帮助我们快速定位和解决问题,同时避免日志过多导致的性能问题。

1. NestJS 内置日志系统

NestJS 提供了内置的日志系统,支持不同级别的日志和自定义日志实现。

基础配置

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn', 'log', 'verbose'], // 开发环境
  });
  
  const configService = app.get(ConfigService);
  const logLevel = configService.get('LOG_LEVEL') || 'info';
  
  // 根据环境配置日志级别
  if (configService.get('NODE_ENV') === 'production') {
    app.useLogger(['error', 'warn', 'log']); // 生产环境
  }
  
  await app.listen(3000);
}
bootstrap();

2. 使用 winston 进行高级日志管理

Winston 是一个功能强大的 Node.js 日志库,提供了更灵活的日志配置和输出选项。

安装依赖

npm install winston nest-winston

配置 winston

// app.module.ts
import { Module } from '@nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    WinstonModule.forRootAsync({
      useFactory: (configService: ConfigService) => {
        const logLevel = configService.get('LOG_LEVEL') || 'info';
        
        return {
          transports: [
            // 控制台输出
            new winston.transports.Console({
              format: winston.format.combine(
                winston.format.timestamp(),
                winston.format.colorize(),
                winston.format.simple(),
              ),
              level: logLevel,
            }),
            // 文件输出
            new winston.transports.File({
              filename: 'logs/error.log',
              level: 'error',
              format: winston.format.combine(
                winston.format.timestamp(),
                winston.format.json(),
              ),
            }),
            new winston.transports.File({
              filename: 'logs/combined.log',
              level: logLevel,
              format: winston.format.combine(
                winston.format.timestamp(),
                winston.format.json(),
              ),
            }),
          ],
        };
      },
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

3. 日志最佳实践

日志级别使用

  • error:记录错误和异常
  • warn:记录警告信息,如过时的API使用
  • info:记录重要的业务事件,如用户注册、订单创建
  • debug:记录调试信息,仅在开发环境使用
  • verbose:记录详细的系统信息,仅在开发环境使用

结构化日志

使用结构化日志(如 JSON 格式)可以方便日志分析工具处理和查询日志:

// user.service.ts
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class UserService {
  private readonly logger = new Logger(UserService.name);

  async createUser(user: any) {
    this.logger.log('Creating user', {
      userId: user.id,
      email: user.email,
      action: 'user_create',
      timestamp: new Date().toISOString(),
    });
    
    try {
      // 创建用户逻辑
      this.logger.log('User created successfully', {
        userId: user.id,
        status: 'success',
      });
    } catch (error) {
      this.logger.error('Failed to create user', {
        userId: user.id,
        error: error.message,
        stack: error.stack,
        status: 'error',
      });
      throw error;
    }
  }
}

日志轮换

对于生产环境,日志轮换是必要的,以避免日志文件过大:

// 使用 winston-daily-rotate-file 进行日志轮换
npm install winston-daily-rotate-file

// 在 winston 配置中添加
new winston.transports.DailyRotateFile({
  filename: 'logs/application-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  maxFiles: '14d', // 保留 14 天的日志
  level: logLevel,
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json(),
  ),
}),

敏感信息过滤

确保日志中不包含敏感信息:

// 自定义日志格式化器,过滤敏感信息
const sensitiveFields = ['password', 'token', 'creditCard'];

const filterSensitiveInfo = winston.format((info) => {
  const filteredInfo = { ...info };
  
  sensitiveFields.forEach(field => {
    if (filteredInfo[field]) {
      filteredInfo[field] = '***REDACTED***';
    }
  });
  
  return filteredInfo;
});

// 在 winston 配置中使用
format: winston.format.combine(
  winston.format.timestamp(),
  filterSensitiveInfo(),
  winston.format.json(),
),

性能优化

生产环境的性能优化是确保应用能够处理真实用户流量的关键。以下是一些 NestJS 应用在生产环境中的性能优化技巧。

1. 构建优化

使用生产模式构建

# 构建生产版本
npm run build

# 运行生产版本
NODE_ENV=production node dist/main.js

启用 TypeScript 编译优化

// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": false, // 生产环境禁用 source map
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true, // 启用增量编译
    "skipLibCheck": true, // 跳过库检查
    "strictNullChecks": true,
    "noImplicitAny": true,
    "strictBindCallApply": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true
  }
}

2. 运行时优化

使用 Fastify 作为 HTTP 服务器

Fastify 是一个高性能的 HTTP 服务器,比默认的 Express 更快:

npm install @nestjs/platform-fastify
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
}
bootstrap();

启用 Gzip 压缩

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import compression from 'compression';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用 Gzip 压缩
  app.use(compression());
  
  await app.listen(3000);
}
bootstrap();

配置合理的超时

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 配置请求超时
  app.use((req, res, next) => {
    req.setTimeout(30000); // 30秒超时
    next();
  });
  
  await app.listen(3000);
}
bootstrap();

3. 数据库优化

使用数据库连接池

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        url: configService.get('DATABASE_URL'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: false, // 生产环境禁用自动同步
        logging: false, // 生产环境禁用日志
        poolSize: 10, // 连接池大小
        ssl: configService.get('NODE_ENV') === 'production',
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

优化查询

  • 使用索引:为频繁查询的字段创建索引
  • 避免 N+1 查询:使用关系预加载
  • 使用分页:避免一次查询过多数据
  • 优化复杂查询:分解复杂查询,使用查询构建器

4. 缓存策略

使用 Redis 作为缓存

npm install @nestjs/cache-manager cache-manager redis-store
// app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    CacheModule.registerAsync({
      useFactory: (configService: ConfigService) => ({
        store: redisStore,
        url: configService.get('REDIS_URL'),
        ttl: 60 * 60, // 缓存 1 小时
      }),
      inject: [ConfigService],
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

使用缓存装饰器

// user.service.ts
import { Injectable, CacheKey, CacheTTL } from '@nestjs/common';
import { CacheManager } from '@nestjs/cache-manager';

@Injectable()
export class UserService {
  constructor(private cacheManager: CacheManager) {}

  @CacheKey('user')
  @CacheTTL(60 * 60) // 缓存 1 小时
  async getUser(id: number) {
    // 从数据库获取用户
    return this.userRepository.findOne(id);
  }

  async updateUser(id: number, data: any) {
    // 更新用户
    const updatedUser = await this.userRepository.update(id, data);
    
    // 清除缓存
    await this.cacheManager.del(`user_${id}`);
    
    return updatedUser;
  }
}

监控和可观测性

监控和可观测性是生产环境中不可或缺的部分,它们可以帮助我们及时发现和解决问题,同时提供系统运行状态的可见性。

1. 健康检查

NestJS 提供了 @nestjs/terminus 模块,用于实现健康检查。

安装依赖

npm install @nestjs/terminus @nestjs/axios

配置健康检查

// health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private http: HttpHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
      // 可以添加数据库、Redis 等健康检查
    ]);
  }
}

2. 指标监控

使用 Prometheus 和 Grafana 进行指标监控。

安装依赖

npm install prom-client

配置指标监控

// metrics.controller.ts
import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';
import * as promClient from 'prom-client';

@Controller('metrics')
export class MetricsController {
  private register = promClient.register;

  constructor() {
    // 定义指标
    new promClient.Counter({
      name: 'http_requests_total',
      help: 'Total HTTP requests',
      labelNames: ['method', 'route', 'status'],
    });

    new promClient.Gauge({
      name: 'http_request_duration_seconds',
      help: 'HTTP request duration in seconds',
      labelNames: ['method', 'route'],
    });

    // 收集默认指标
    promClient.collectDefaultMetrics();
  }

  @Get()
  async getMetrics(@Res() res: Response) {
    res.set('Content-Type', this.register.contentType);
    res.end(await this.register.metrics());
  }
}

中间件收集指标

// metrics.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as promClient from 'prom-client';

@Injectable()
export class MetricsMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const start = Date.now();
    const route = req.route ? req.route.path : req.path;
    
    res.on('finish', () => {
      const duration = (Date.now() - start) / 1000;
      
      // 增加请求计数
      const httpRequestsTotal = new promClient.Counter({
        name: 'http_requests_total',
        help: 'Total HTTP requests',
        labelNames: ['method', 'route', 'status'],
      });
      httpRequestsTotal.inc({ method: req.method, route, status: res.statusCode });
      
      // 记录请求持续时间
      const httpRequestDurationSeconds = new promClient.Gauge({
        name: 'http_request_duration_seconds',
        help: 'HTTP request duration in seconds',
        labelNames: ['method', 'route'],
      });
      httpRequestDurationSeconds.set({ method: req.method, route }, duration);
    });
    
    next();
  }
}

3. 分布式追踪

使用 OpenTelemetry 进行分布式追踪。

安装依赖

npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-jaeger

配置分布式追踪

// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const traceExporter = new JaegerExporter({
  endpoint: 'http://localhost:14268/api/traces',
});

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'nestjs-application',
  }),
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

在应用中使用

// main.ts
// 在文件顶部导入
import './tracing';

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

4. 错误监控

使用 Sentry 进行错误监控。

安装依赖

npm install @sentry/node @sentry/tracing

配置 Sentry

// main.ts
import * as Sentry from '@sentry/node';
import { ProfilingIntegration } from '@sentry/profiling-node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    new ProfilingIntegration(),
  ],
  tracesSampleRate: 1.0,
  profilesSampleRate: 1.0,
});

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 使用 Sentry 作为全局异常过滤器
  app.useGlobalFilters(new SentryExceptionFilter());
  
  await app.listen(3000);
}
bootstrap();

创建 Sentry 异常过滤器

// sentry-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import * as Sentry from '@sentry/node';

@Catch()
export class SentryExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    // 捕获异常并发送到 Sentry
    Sentry.captureException(exception);

    // 处理 HTTP 异常
    if (exception instanceof HttpException) {
      const status = exception.getStatus();
      response.status(status).json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.getResponse(),
      });
    } else {
      // 处理非 HTTP 异常
      response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
        statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: 'Internal server error',
      });
    }
  }
}

部署策略

选择合适的部署策略对于生产环境的稳定性和可靠性至关重要。以下是一些常见的 NestJS 应用部署策略。

1. Docker 容器化

Docker 容器化是现代应用部署的主流方式,它提供了环境一致性和隔离性。

创建 Dockerfile

# 多阶段构建
FROM node:16-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:16-alpine AS production

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

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

ENV NODE_ENV=production

EXPOSE 3000

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

创建 docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://postgres:postgres@db:5432/app
      - JWT_SECRET=your-secret-key
    depends_on:
      - db
  
  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=app
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

2. 容器编排

对于生产环境,使用 Kubernetes 等容器编排工具可以提供更好的扩展性和管理能力。

创建 Kubernetes 部署文件

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nestjs-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nestjs-app
  template:
    metadata:
      labels:
        app: nestjs-app
    spec:
      containers:
      - name: nestjs-app
        image: your-registry/nestjs-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: jwt-secret
        resources:
          limits:
            cpu: "1"
            memory: "512Mi"
          requests:
            cpu: "500m"
            memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: nestjs-app
spec:
  selector:
    app: nestjs-app
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer

3. PaaS 平台部署

使用 Heroku、Vercel、AWS App Runner 等 PaaS 平台可以简化部署流程。

Heroku 部署

# 安装 Heroku CLI
npm install -g heroku

# 登录 Heroku
heroku login

# 创建新应用
heroku create nestjs-app

# 配置环境变量
heroku config:set NODE_ENV=production
heroku config:set DATABASE_URL=postgres://user:password@host:port/db
heroku config:set JWT_SECRET=your-secret-key

# 部署应用
git push heroku main

# 打开应用
heroku open

Vercel 部署

// vercel.json
{
  "version": 2,
  "builds": [
    {
      "src": "dist/main.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "dist/main.js"
    }
  ],
  "env": {
    "NODE_ENV": "production",
    "DATABASE_URL": "${DATABASE_URL}",
    "JWT_SECRET": "${JWT_SECRET}"
  }
}

4. 无服务器部署

对于某些场景,无服务器部署可能是一个不错的选择。

AWS Lambda 部署

// main.ts
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import * as express from 'express';
import { AppModule } from './app.module';
import { Server } from 'http';

let cachedServer: Server;

async function bootstrap() {
  const app = express();
  const adapter = new ExpressAdapter(app);
  const nestApp = await NestFactory.create(AppModule, adapter);
  await nestApp.init();
  return app;
}

export async function handler(event, context) {
  if (!cachedServer) {
    const app = await bootstrap();
    cachedServer = app;
  }
  return cachedServer(event, context);
}

生产环境维护

生产环境的维护是确保应用持续稳定运行的关键。以下是一些生产环境维护的最佳实践。

1. 日志管理

  • 集中化日志:使用 ELK Stack、Splunk 等工具集中管理日志
  • 日志分析:定期分析日志,识别潜在问题
  • 日志保留:根据合规要求和存储成本,制定合理的日志保留策略
  • 日志安全:确保日志中不包含敏感信息,保护日志安全

2. 备份策略

  • 数据备份:定期备份数据库和重要文件
  • 备份验证:定期验证备份的完整性和可恢复性
  • 异地备份:将备份存储在不同的地理位置,防止灾难导致数据丢失
  • 备份自动化:自动化备份流程,减少人为错误

3. 安全更新

  • 依赖更新:定期更新依赖,修复安全漏洞
  • 框架更新:及时更新 NestJS 和 Node.js 版本
  • 安全扫描:使用工具如 npm audit 定期扫描安全漏洞
  • 漏洞响应:建立安全漏洞响应流程,及时处理新发现的漏洞

4. 性能监控

  • 定期性能评估:定期评估应用性能,识别瓶颈
  • 负载测试:使用工具如 JMeter、Artillery 进行负载测试
  • 性能基准:建立性能基准,用于比较不同版本的性能
  • 自动扩缩容:根据负载自动调整资源分配

5. 灾备和恢复

  • 灾难恢复计划:制定详细的灾难恢复计划
  • 故障演练:定期进行故障演练,测试恢复流程
  • 高可用性设计:设计多区域、多可用区部署,提高系统可用性
  • 回滚机制:建立应用部署的回滚机制,以便在出现问题时快速恢复

实践案例:配置生产环境

1. 项目结构

├── src/
│   ├── main.ts
│   ├── app.module.ts
│   ├── config/
│   │   ├── configuration.ts
│   │   └── validation.schema.ts
│   ├── health/
│   │   └── health.controller.ts
│   └── metrics/
│       └── metrics.controller.ts
├── .env.example
├── .env.production.local
├── Dockerfile
├── docker-compose.yml
├── k8s/
│   ├── deployment.yaml
│   └── service.yaml
└── package.json

2. 配置文件

configuration.ts

// src/config/configuration.ts
export default () => ({
  nodeEnv: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT, 10) || 3000,
  database: {
    url: process.env.DATABASE_URL,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1h',
  },
  redis: {
    url: process.env.REDIS_URL,
  },
  sentry: {
    dsn: process.env.SENTRY_DSN,
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info',
  },
});

validation.schema.ts

// src/config/validation.schema.ts
import * as Joi from 'joi';

export const validationSchema = Joi.object({
  NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
  PORT: Joi.number().default(3000),
  DATABASE_URL: Joi.string().required(),
  JWT_SECRET: Joi.string().required(),
  REDIS_URL: Joi.string().required(),
  SENTRY_DSN: Joi.string().required(),
  LOG_LEVEL: Joi.string().valid('error', 'warn', 'info', 'debug').default('info'),
});

3. 主应用配置

// src/main.ts
import * as Sentry from '@sentry/node';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import compression from 'compression';

// 配置 Sentry
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [new Sentry.Integrations.Http({ tracing: true })],
  tracesSampleRate: 1.0,
});

async function bootstrap() {
  // 使用 Fastify 作为 HTTP 服务器
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
    {
      logger: process.env.NODE_ENV === 'production' ? ['error', 'warn', 'log'] : ['error', 'warn', 'log', 'verbose', 'debug'],
    },
  );
  
  const configService = app.get(ConfigService);
  
  // 启用压缩
  app.use(compression());
  
  // 配置 CORS
  app.enableCors({
    origin: configService.get('CORS_ORIGIN') || '*',
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    credentials: true,
  });
  
  // 配置全局前缀
  app.setGlobalPrefix('api');
  
  const port = configService.get('port');
  await app.listen(port, '0.0.0.0');
  console.log(`Application running on port ${port}`);
}

bootstrap();

4. 应用模块配置

// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '@nestjs/cache-manager';
import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios';
import * as redisStore from 'cache-manager-redis-store';

import configuration from './config/configuration';
import { validationSchema } from './config/validation.schema';
import { HealthController } from './health/health.controller';
import { MetricsController } from './metrics/metrics.controller';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [configuration],
      validationSchema,
      envFilePath: ['.env.production', '.env'],
      ignoreEnvFile: process.env.NODE_ENV === 'production',
    }),
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        url: configService.get('database.url'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: false,
        logging: false,
        poolSize: 10,
        ssl: configService.get('nodeEnv') === 'production',
      }),
      inject: [ConfigService],
    }),
    CacheModule.registerAsync({
      useFactory: (configService: ConfigService) => ({
        store: redisStore,
        url: configService.get('redis.url'),
        ttl: 60 * 60,
      }),
      inject: [ConfigService],
      isGlobal: true,
    }),
    TerminusModule,
    HttpModule,
    UserModule,
    AuthModule,
  ],
  controllers: [HealthController, MetricsController],
  providers: [],
})
export class AppModule {}

5. 健康检查配置

// src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck, TypeOrmHealthIndicator } from '@nestjs/terminus';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private http: HttpHealthIndicator,
    private db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
    ]);
  }
}

6. 部署配置

Dockerfile

FROM node:16-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:16-alpine AS production

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

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

ENV NODE_ENV=production

EXPOSE 3000

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

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - PORT=3000
      - DATABASE_URL=postgres://postgres:postgres@db:5432/app
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=your-secret-key
      - SENTRY_DSN=https://your-sentry-dsn
      - LOG_LEVEL=info
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:13
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=app
    volumes:
      - postgres_data:/var/lib/postgresql/data
  
  redis:
    image: redis:6
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

生产环境最佳实践总结

1. 配置管理

  • 使用环境变量:避免硬编码配置,使用环境变量管理配置
  • 配置验证:使用 Joi 等工具验证配置的有效性
  • 敏感信息管理:使用 secrets 管理服务存储敏感信息
  • 不同环境配置:为不同环境(开发、测试、生产)提供不同的配置

2. 日志管理

  • 结构化日志:使用 JSON 格式的结构化日志
  • 适当的日志级别:根据环境选择适当的日志级别
  • 日志轮换:配置日志轮换,避免日志文件过大
  • 敏感信息过滤:确保日志中不包含敏感信息
  • 集中化日志:使用 ELK Stack 等工具集中管理日志

3. 性能优化

  • 生产模式构建:使用 npm run build 构建生产版本
  • 使用 Fastify:考虑使用 Fastify 替代 Express 以提高性能
  • 启用压缩:启用 Gzip 压缩减少传输大小
  • 数据库优化:使用连接池,优化查询
  • 缓存策略:使用 Redis 等缓存减少数据库负载
  • 资源限制:合理配置应用的资源限制

4. 监控和可观测性

  • 健康检查:实现健康检查端点
  • 指标监控:使用 Prometheus 等工具监控系统指标
  • 分布式追踪:使用 OpenTelemetry 进行分布式追踪
  • 错误监控:使用 Sentry 等工具监控错误
  • 日志聚合:集中管理和分析日志

5. 安全性

  • 依赖更新:定期更新依赖,修复安全漏洞
  • HTTPS:在生产环境中使用 HTTPS
  • 输入验证:严格验证所有用户输入
  • CORS 配置:合理配置 CORS 策略
  • 安全头部:设置适当的安全 HTTP 头部
  • 速率限制:实现 API 速率限制,防止暴力攻击

6. 部署和维护

  • 容器化:使用 Docker 容器化应用
  • CI/CD:配置自动化部署流程
  • 滚动部署:使用滚动部署减少 downtime
  • 回滚机制:建立部署回滚机制
  • 备份策略:定期备份数据
  • 灾备计划:制定灾难恢复计划

互动问题

  1. 你认为在生产环境中最容易被忽略的配置是什么?为什么?

  2. 如何平衡生产环境的安全性和性能?

  3. 你使用过哪些监控工具来监控 NestJS 应用?它们各有什么优缺点?

  4. 在部署 NestJS 应用时,你首选的部署方案是什么?为什么?

  5. 你如何处理生产环境中的突发性能问题?有哪些应急措施?

« 上一篇 NestJS 代码质量工具配置 下一篇 » 团队协作规范