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 joi2. 环境变量管理策略
本地开发环境
- 使用
.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: LoadBalancer3. 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 openVercel 部署
// 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.json2. 配置文件
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
- 回滚机制:建立部署回滚机制
- 备份策略:定期备份数据
- 灾备计划:制定灾难恢复计划
互动问题
你认为在生产环境中最容易被忽略的配置是什么?为什么?
如何平衡生产环境的安全性和性能?
你使用过哪些监控工具来监控 NestJS 应用?它们各有什么优缺点?
在部署 NestJS 应用时,你首选的部署方案是什么?为什么?
你如何处理生产环境中的突发性能问题?有哪些应急措施?