NestJS 部署和 DevOps 详解
部署概述
部署是将应用程序从开发环境转移到生产环境的过程。NestJS 应用程序可以通过多种方式部署,包括传统服务器、容器化部署、云服务等。
1. Docker 容器化
创建 Dockerfile
dockerfile# 构建阶段 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 生产阶段 FROM node:18-alpine AS runner WORKDIR /app ENV NODE_ENV production COPY package*.json ./ RUN npm ci --only=production COPY /app/dist ./dist EXPOSE 3000 CMD ["node", "dist/main.js"]
创建 .dockerignore
shellnode_modules dist .git .env *.log
构建 Docker 镜像
bashdocker build -t nestjs-app .
运行 Docker 容器
bashdocker run -p 3000:3000 nestjs-app
2. Docker Compose
docker-compose.yml
yamlversion: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_HOST=db - DATABASE_PORT=3306 - DATABASE_USER=root - DATABASE_PASSWORD=password - DATABASE_NAME=nestjs depends_on: - db restart: unless-stopped db: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=password - MYSQL_DATABASE=nestjs ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql restart: unless-stopped volumes: mysql_data:
启动服务
bashdocker-compose up -d
3. Kubernetes 部署
Deployment 配置
yamlapiVersion: 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: nestjs-app:latest ports: - containerPort: 3000 env: - name: NODE_ENV value: "production" - name: DATABASE_HOST valueFrom: secretKeyRef: name: db-secret key: host resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5
Service 配置
yamlapiVersion: v1 kind: Service metadata: name: nestjs-app-service spec: selector: app: nestjs-app ports: - protocol: TCP port: 80 targetPort: 3000 type: LoadBalancer
Ingress 配置
yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nestjs-app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: nestjs-app-service port: number: 80
4. CI/CD 管道
GitHub Actions 配置
yamlname: CI/CD Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm run test - name: Run lint run: npm run lint - name: Build run: npm run build build-and-push: 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 uses: docker/build-push-action@v4 with: context: . push: true tags: username/nestjs-app:latest,username/nestjs-app:${{ github.sha }} cache-from: type=registry,ref=username/nestjs-app:latest cache-to: type=inline deploy: needs: build-and-push runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Deploy to Kubernetes uses: azure/k8s-deploy@v4 with: manifests: | k8s/deployment.yaml k8s/service.yaml images: | username/nestjs-app:${{ github.sha }} kubeconfig: ${{ secrets.KUBE_CONFIG }}
GitLab CI 配置
yamlstages: - test - build - deploy variables: NODE_ENV: test test: stage: test image: node:18 script: - npm ci - npm run test - npm run lint cache: paths: - node_modules/ build: stage: build image: docker:latest services: - docker:dind script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main deploy: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/nestjs-app nestjs-app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA only: - main
5. 环境变量管理
使用 .env 文件
bash# .env.production NODE_ENV=production PORT=3000 DATABASE_HOST=localhost DATABASE_PORT=3306 DATABASE_USER=root DATABASE_PASSWORD=password DATABASE_NAME=nestjs JWT_SECRET=your-secret-key
使用 ConfigModule
typescriptimport { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV}`, }), ], }) export class AppModule {}
Kubernetes Secrets
yamlapiVersion: v1 kind: Secret metadata: name: db-secret type: Opaque data: host: bG9jYWxob3N0 port: MzMwNg== user: cm9vdA== password: cGFzc3dvcmQ=
6. 健康检查
健康检查端点
typescriptimport { Controller, Get } from '@nestjs/common'; import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus'; @Controller('health') export class HealthController { constructor( private health: HealthCheckService, private db: TypeOrmHealthIndicator, ) {} @Get() @HealthCheck() check() { return this.health.check([ () => this.db.pingCheck('database'), ]); } }
Kubernetes 健康检查
yamllivenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3
7. 日志管理
结构化日志
typescriptimport { Logger } from '@nestjs/common'; export class AppService { private readonly logger = new Logger(AppService.name); async getData() { this.logger.log('Fetching data', { userId: 123, action: 'fetch' }); // 业务逻辑 } }
使用 Winston
typescriptimport { WinstonModule } from 'nest-winston'; import * as winston from 'winston'; @Module({ imports: [ WinstonModule.forRoot({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), winston.format.json(), ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error', }), new winston.transports.File({ filename: 'logs/combined.log', }), ], }), ], }) export class AppModule {}
8. 监控和告警
使用 Prometheus
typescriptimport { Controller, Get } from '@nestjs/common'; import { MetricsService } from './metrics.service'; @Controller('metrics') export class MetricsController { constructor(private metricsService: MetricsService) {} @Get() getMetrics() { return this.metricsService.getMetrics(); } }
Grafana 仪表板
yamlapiVersion: v1 kind: ConfigMap metadata: name: grafana-dashboard data: dashboard.json: | { "dashboard": { "title": "NestJS Application", "panels": [ { "title": "Request Rate", "targets": [ { "expr": "rate(http_requests_total[5m])" } ] } ] } }
9. 负载均衡
Nginx 配置
nginxupstream nestjs_backend { server nestjs-app-1:3000; server nestjs-app-2:3000; server nestjs-app-3:3000; } server { listen 80; server_name api.example.com; location / { proxy_pass http://nestjs_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
AWS ALB 配置
yamlResources: LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: nestjs-alb Subnets: - !Ref SubnetA - !Ref SubnetB SecurityGroups: - !Ref SecurityGroup Type: application TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: nestjs-tg Port: 3000 Protocol: HTTP VpcId: !Ref VPC Targets: - Id: !Ref EC2Instance1 - Id: !Ref EC2Instance2
10. 灾难恢复
数据库备份
bash#!/bin/bash # backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="/backups" DATABASE="nestjs" mysqldump -u root -p$MYSQL_ROOT_PASSWORD $DATABASE | gzip > $BACKUP_DIR/db_backup_$DATE.sql.gz # 保留最近7天的备份 find $BACKUP_DIR -name "db_backup_*.sql.gz" -mtime +7 -delete
自动扩展
yamlapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nestjs-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nestjs-app minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
部署最佳实践
- 环境隔离:开发、测试、生产环境分离
- 自动化部署:使用 CI/CD 自动化部署流程
- 版本控制:所有配置文件纳入版本控制
- 监控告警:实施全面的监控和告警机制
- 备份策略:定期备份关键数据
- 安全加固:实施安全最佳实践
- 文档化:维护详细的部署文档
- 回滚计划:准备快速回滚方案
- 性能测试:部署前进行性能测试
- 渐进式发布:使用蓝绿部署或金丝雀发布
总结
NestJS 部署和 DevOps 提供了:
- 灵活的容器化方案
- 强大的编排支持
- 自动化的 CI/CD 流程
- 完善的监控体系
- 可靠的灾难恢复
掌握部署和 DevOps 是将 NestJS 应用程序成功交付到生产环境的关键。通过合理使用容器化、编排、CI/CD 和监控工具,可以构建出高可用、可扩展的生产环境,确保应用程序的稳定运行和快速迭代。