Skip to content

Latest commit

 

History

History
643 lines (515 loc) · 14.6 KB

File metadata and controls

643 lines (515 loc) · 14.6 KB

NestJS 云托管部署指南

本指南详细介绍如何将 NestJS 应用部署到 CloudBase 云托管服务。

📋 前置要求:如果您还没有创建 NestJS 项目,请先阅读 NestJS 项目创建指南

📋 目录导航


部署特性

云托管适合以下场景:

  • 企业级应用:复杂的 API 服务和管理系统
  • 高并发:需要处理大量并发请求
  • 自定义环境:需要特定的运行时环境
  • 微服务架构:容器化部署和管理

技术特点

特性 说明
计费方式 按资源使用量(CPU/内存)
启动方式 持续运行
端口配置 可自定义端口(默认 3000)
扩缩容 支持自动扩缩容配置
Node.js 环境 完全自定义 Node.js 环境

准备部署文件

1. 创建 Dockerfile

创建 Dockerfile 文件:

# 使用官方 Node.js 运行时作为基础镜像
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json
COPY package*.json ./

# 设置 npm 镜像源以提高下载速度
RUN npm config set registry https://mirrors.cloud.tencent.com/npm/

# 安装依赖
RUN npm ci --only=production && npm cache clean --force

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 暴露端口
EXPOSE 3000

# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000

# 启动命令
CMD ["node", "dist/main"]

2. 创建 .dockerignore 文件

创建 .dockerignore 文件以优化构建性能:

node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
.vscode
.idea
*.swp
*.swo
*~
.DS_Store
.tmp
.temp
dist
scf_bootstrap
docs/
test/

3. 优化 main.ts

确保 src/main.ts 支持云托管环境:

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用 CORS
  app.enableCors({
    origin: true,
    credentials: true,
  });

  // 配置端口
  const port = process.env.PORT || 3000;
  await app.listen(port, '0.0.0.0');
  
  console.log(`NestJS application is running on port ${port}`);
}
bootstrap();

4. 依赖管理

确保 package.json 包含所有必要依赖:

{
  "dependencies": {
    "@nestjs/common": "^11.0.1",
    "@nestjs/core": "^11.0.1",
    "@nestjs/platform-express": "^11.0.1",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.14.3",
    "reflect-metadata": "^0.2.2",
    "rxjs": "^7.8.1"
  },
  "scripts": {
    "build": "nest build",
    "start": "nest start",
    "start:prod": "node dist/main"
  }
}

项目结构

cloudrun-nestjs/
├── src/                    # TypeScript 源代码
│   ├── main.ts
│   ├── app.module.ts
│   ├── app.controller.ts
│   ├── app.service.ts
│   ├── health/
│   └── users/
├── dist/                   # 编译后的 JavaScript 代码(构建时生成)
├── package.json            # 项目配置和依赖
├── Dockerfile              # 🔑 容器配置文件
├── .dockerignore           # Docker 忽略文件
└── nest-cli.json          # NestJS CLI 配置

💡 说明

  • 云托管支持自定义端口,默认使用 3000 端口
  • 使用 Docker 容器内的 Node.js 环境
  • Docker 容器提供了完整的 Node.js 环境控制

部署步骤

通过控制台部署

  1. 登录 CloudBase 控制台
  2. 选择您的环境,进入「云托管」页面
  3. 点击「新建服务」
  4. 填写服务名称(如:cloudrun-nestjs-service
  5. 选择「本地代码」上传方式
  6. 上传包含 Dockerfile 的项目目录
  7. 配置服务参数:
    • 端口:3000(或您在应用中配置的端口)
    • CPU:0.25 核
    • 内存:0.5 GB
    • 实例数量:1-10(根据需求调整)
  8. 点击「创建」按钮等待部署完成

通过 CLI 部署

# 安装 CloudBase CLI
npm install -g @cloudbase/cli

# 登录
tcb login

# 初始化云托管配置
tcb run init

# 部署云托管服务
tcb run deploy --port 3000

配置文件部署

创建 cloudbaserc.json 配置文件:

{
  "envId": "your-env-id",
  "framework": {
    "name": "nestjs",
    "plugins": {
      "run": {
        "name": "@cloudbase/framework-plugin-run",
        "options": {
          "serviceName": "cloudrun-nestjs-service",
          "servicePath": "/",
          "localPath": "./",
          "dockerfile": "./Dockerfile",
          "buildDir": "./",
          "cpu": 0.25,
          "mem": 0.5,
          "minNum": 1,
          "maxNum": 10,
          "policyType": "cpu",
          "policyThreshold": 60,
          "containerPort": 3000,
          "envVariables": {
            "NODE_ENV": "production"
          }
        }
      }
    }
  }
}

然后执行部署:

tcb framework deploy

模板部署(快速开始)

  1. 登录 腾讯云托管控制台
  2. 点击「通过模板部署」,选择 NestJS 模板
  3. 输入自定义服务名称,点击部署
  4. 等待部署完成后,点击左上角箭头,返回到服务详情页
  5. 点击概述,获取默认域名并访问

访问应用

获取访问地址

云托管部署成功后,系统会自动分配访问地址。您也可以绑定自定义域名。

访问地址格式:https://your-service-url/

测试接口

  • 根路径/ - NestJS 欢迎页面
  • 健康检查/health - 查看应用状态
  • 用户列表/api/users - 获取用户列表
  • 用户详情/api/users/1 - 获取特定用户
  • 创建用户POST /api/users - 创建新用户

示例请求

# 健康检查
curl https://your-service-url/health

# 获取用户列表
curl https://your-service-url/api/users

# 分页查询
curl "https://your-service-url/api/users?page=1&limit=2"

# 创建新用户
curl -X POST https://your-service-url/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"测试用户","email":"test@example.com"}'

常见问题

Q: 云托管支持哪些端口?

A: 云托管支持自定义端口,NestJS 应用默认使用 3000 端口,也可以根据需要配置其他端口。

Q: 如何配置生产环境设置?

A: 通过环境变量控制应用配置:

// config/configuration.ts
export default () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  database: {
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
    username: process.env.DATABASE_USERNAME,
    password: process.env.DATABASE_PASSWORD,
    database: process.env.DATABASE_NAME,
  },
});

Q: 如何配置环境变量?

A: 可以通过以下方式配置:

  • 控制台服务配置页面
  • cloudbaserc.json 配置文件
  • Dockerfile 中的 ENV 指令

Q: NestJS 应用如何处理静态文件?

A: 在云托管环境中,可以配置 NestJS 处理静态文件:

import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  
  // 配置静态文件
  app.useStaticAssets(join(__dirname, '..', 'public'));
  app.setBaseViewsDir(join(__dirname, '..', 'views'));
  app.setViewEngine('hbs');
  
  // ...
}

Q: 如何查看云托管日志?

A: 在云托管服务详情页面可以查看:

  • 实例日志
  • 构建日志
  • 访问日志
  • 错误日志

最佳实践

1. 多阶段构建优化

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

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

COPY . .
RUN npm run build

# 运行阶段
FROM node:18-alpine

WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

EXPOSE 3000
CMD ["node", "dist/main"]

2. 环境变量管理

// config/configuration.ts
import { registerAs } from '@nestjs/config';

export default registerAs('app', () => ({
  port: parseInt(process.env.PORT, 10) || 3000,
  nodeEnv: process.env.NODE_ENV || 'development',
  database: {
    host: process.env.DATABASE_HOST,
    port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
    username: process.env.DATABASE_USERNAME,
    password: process.env.DATABASE_PASSWORD,
    database: process.env.DATABASE_NAME,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN || '1d',
  },
}));

3. 健康检查增强

// health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Controller('health')
export class HealthController {
  constructor(private configService: ConfigService) {}

  @Get()
  check() {
    return {
      status: 'healthy',
      timestamp: new Date().toISOString(),
      framework: 'NestJS',
      deployment: '云托管',
      version: '11.0.0',
      environment: this.configService.get('NODE_ENV'),
      port: this.configService.get('PORT'),
    };
  }
}

4. 日志配置

// main.ts
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: ['error', 'warn', 'log'],
  });
  
  const logger = new Logger('Bootstrap');
  
  // ...
  
  await app.listen(port, '0.0.0.0');
  logger.log(`Application is running on: http://0.0.0.0:${port}`);
}

5. CORS 配置

// main.ts
app.enableCors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || true,
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
});

6. 全局异常过滤器

// filters/all-exceptions.filter.ts
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private readonly logger = new Logger(AllExceptionsFilter.name);

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message =
      exception instanceof HttpException
        ? exception.getResponse()
        : 'Internal server error';

    this.logger.error(
      `${request.method} ${request.url}`,
      exception instanceof Error ? exception.stack : exception,
    );

    response.status(status).json({
      success: false,
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

7. 部署前检查清单

  • Dockerfile 文件存在且配置正确
  • .dockerignore 文件配置合理
  • 端口配置灵活(支持环境变量)
  • 容器启动命令正确
  • 排除 scf_bootstrap 文件(仅用于云函数)
  • 本地 Docker 构建测试通过
  • NestJS 应用配置正确
  • 环境变量配置完整

高级配置

1. 负载均衡配置

{
  "run": {
    "name": "@cloudbase/framework-plugin-run",
    "options": {
      "serviceName": "cloudrun-nestjs-service",
      "cpu": 1,
      "mem": 2,
      "minNum": 2,
      "maxNum": 20,
      "policyType": "cpu",
      "policyThreshold": 70,
      "containerPort": 3000,
      "customLogs": "stdout",
      "initialDelaySeconds": 2
    }
  }
}

2. 数据库集成

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

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('DATABASE_HOST'),
        port: configService.get('DATABASE_PORT'),
        username: configService.get('DATABASE_USERNAME'),
        password: configService.get('DATABASE_PASSWORD'),
        database: configService.get('DATABASE_NAME'),
        entities: [__dirname + '/../**/*.entity{.ts,.js}'],
        synchronize: configService.get('NODE_ENV') === 'development',
      }),
      inject: [ConfigService],
    }),
  ],
})
export class DatabaseModule {}

3. Redis 缓存配置

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

@Module({
  imports: [
    CacheModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        store: redisStore,
        host: configService.get('REDIS_HOST'),
        port: configService.get('REDIS_PORT'),
        ttl: 600,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class CacheConfigModule {}

4. 监控和告警

// interceptors/performance.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

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

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const method = request.method;
    const url = request.url;
    const now = Date.now();

    return next.handle().pipe(
      tap(() => {
        const delay = Date.now() - now;
        
        // 记录慢请求
        if (delay > 1000) {
          this.logger.warn(`Slow request: ${method} ${url} - ${delay}ms`);
        }
        
        this.logger.log(`${method} ${url} - ${delay}ms`);
      }),
    );
  }
}

相关文档