Node.js微服务架构实战:Express + TypeScript + Docker + Kubernetes部署指南

Charlie683
Charlie683 2026-03-13T02:06:05+08:00
0 0 0

前言

在现代软件开发领域,微服务架构已经成为构建大型分布式系统的重要模式。Node.js凭借其异步非阻塞I/O模型和丰富的生态系统,成为了构建微服务的理想选择。本文将深入探讨如何使用Express框架、TypeScript类型安全、Docker容器化以及Kubernetes集群部署来构建一个完整的现代化微服务系统。

微服务架构概述

什么是微服务架构

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件架构模式。每个服务:

  • 运行在自己的进程中
  • 通过轻量级通信机制(通常是HTTP API)进行交互
  • 可以独立部署和扩展
  • 专注于特定的业务功能

微服务的优势与挑战

优势:

  • 独立开发和部署
  • 技术栈多样化
  • 易于扩展和维护
  • 故障隔离性好

挑战:

  • 分布式系统复杂性增加
  • 数据一致性问题
  • 网络延迟和故障处理
  • 运维成本上升

Express框架基础搭建

项目初始化与依赖安装

首先,我们创建一个新的Node.js项目并安装必要的依赖:

mkdir microservice-demo
cd microservice-demo
npm init -y

安装Express及相关依赖:

npm install express cors helmet morgan dotenv
npm install --save-dev typescript @types/node @types/express @types/cors @types/helmet @types/morgan ts-node nodemon

基础服务器配置

创建src/server.ts文件:

import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

// 加载环境变量
dotenv.config();

const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);

// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 基础路由
app.get('/', (req: Request, res: Response) => {
  res.json({
    message: 'Welcome to Microservice Demo',
    timestamp: new Date().toISOString()
  });
});

// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: err.message
  });
});

// 404处理
app.use((req: Request, res: Response) => {
  res.status(404).json({
    error: 'Not Found',
    message: 'Route not found'
  });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

export default app;

TypeScript配置文件

创建tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "types": ["node"],
    "typeRoots": ["./node_modules/@types"],
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

TypeScript类型安全实践

接口定义与类型声明

src/types/index.ts中定义数据接口:

export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface UpdateUserRequest {
  name?: string;
  email?: string;
}

export interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
  message?: string;
}

export interface PaginationOptions {
  page: number;
  limit: number;
  sortBy?: string;
  sortOrder?: 'asc' | 'desc';
}

服务层实现

创建src/services/userService.ts

import { User, CreateUserRequest, UpdateUserRequest } from '../types';
import { ApiResponse } from '../types';

class UserService {
  private users: User[] = [];
  private nextId: number = 1;

  async createUser(userData: CreateUserRequest): Promise<ApiResponse<User>> {
    try {
      const newUser: User = {
        id: this.generateId(),
        name: userData.name,
        email: userData.email,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      this.users.push(newUser);
      
      return {
        success: true,
        data: newUser
      };
    } catch (error) {
      return {
        success: false,
        error: 'Failed to create user',
        message: error.message
      };
    }
  }

  async getUserById(id: string): Promise<ApiResponse<User>> {
    try {
      const user = this.users.find(u => u.id === id);
      
      if (!user) {
        return {
          success: false,
          error: 'User not found'
        };
      }

      return {
        success: true,
        data: user
      };
    } catch (error) {
      return {
        success: false,
        error: 'Failed to get user',
        message: error.message
      };
    }
  }

  async updateUser(id: string, userData: UpdateUserRequest): Promise<ApiResponse<User>> {
    try {
      const userIndex = this.users.findIndex(u => u.id === id);
      
      if (userIndex === -1) {
        return {
          success: false,
          error: 'User not found'
        };
      }

      const updatedUser = {
        ...this.users[userIndex],
        ...userData,
        updatedAt: new Date()
      };

      this.users[userIndex] = updatedUser;

      return {
        success: true,
        data: updatedUser
      };
    } catch (error) {
      return {
        success: false,
        error: 'Failed to update user',
        message: error.message
      };
    }
  }

  async deleteUser(id: string): Promise<ApiResponse<boolean>> {
    try {
      const userIndex = this.users.findIndex(u => u.id === id);
      
      if (userIndex === -1) {
        return {
          success: false,
          error: 'User not found'
        };
      }

      this.users.splice(userIndex, 1);

      return {
        success: true,
        data: true
      };
    } catch (error) {
      return {
        success: false,
        error: 'Failed to delete user',
        message: error.message
      };
    }
  }

  private generateId(): string {
    return `user_${this.nextId++}`;
  }
}

export default new UserService();

API路由设计与实现

路由模块化

创建src/routes/users.ts

import express, { Router } from 'express';
import userService from '../services/userService';
import { CreateUserRequest, UpdateUserRequest } from '../types';

const router: Router = express.Router();

// 创建用户
router.post('/', async (req, res) => {
  try {
    const userData: CreateUserRequest = req.body;
    
    const result = await userService.createUser(userData);
    
    if (result.success) {
      res.status(201).json(result);
    } else {
      res.status(400).json(result);
    }
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

// 获取用户列表
router.get('/', async (req, res) => {
  try {
    // 这里可以添加分页、排序等逻辑
    const users = await userService.getAllUsers();
    
    res.json({
      success: true,
      data: users,
      count: users.length
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Failed to fetch users'
    });
  }
});

// 获取单个用户
router.get('/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    const result = await userService.getUserById(id);
    
    if (result.success) {
      res.json(result);
    } else {
      res.status(404).json(result);
    }
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

// 更新用户
router.put('/:id', async (req, res) => {
  try {
    const { id } = req.params;
    const userData: UpdateUserRequest = req.body;
    
    const result = await userService.updateUser(id, userData);
    
    if (result.success) {
      res.json(result);
    } else {
      res.status(404).json(result);
    }
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

// 删除用户
router.delete('/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    const result = await userService.deleteUser(id);
    
    if (result.success) {
      res.json(result);
    } else {
      res.status(404).json(result);
    }
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

export default router;

主路由配置

更新src/server.ts中的路由配置:

import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

// 导入路由
import userRoutes from './routes/users';

// 加载环境变量
dotenv.config();

const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);

// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 路由配置
app.use('/api/users', userRoutes);

// 基础路由
app.get('/', (req: Request, res: Response) => {
  res.json({
    message: 'Welcome to Microservice Demo',
    timestamp: new Date().toISOString()
  });
});

// 健康检查端点
app.get('/health', (req: Request, res: Response) => {
  res.json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'user-service'
  });
});

// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: err.message
  });
});

// 404处理
app.use((req: Request, res: Response) => {
  res.status(404).json({
    error: 'Not Found',
    message: 'Route not found'
  });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

export default app;

Docker容器化部署

Dockerfile配置

创建Dockerfile

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

# 设置工作目录
WORKDIR /app

# 复制package文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建TypeScript项目
RUN npm run build

# 暴露端口
EXPOSE 3000

# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# 启动应用
CMD ["npm", "start"]

Docker Compose配置

创建docker-compose.yml

version: '3.8'

services:
  user-service:
    build: .
    container_name: user-service
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    restart: unless-stopped
    networks:
      - microservice-network

  # 数据库服务示例
  database:
    image: postgres:15-alpine
    container_name: user-db
    environment:
      POSTGRES_DB: userservice
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - microservice-network

volumes:
  postgres_data:

networks:
  microservice-network:
    driver: bridge

.dockerignore文件

创建.dockerignore

node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output

Kubernetes部署实践

Kubernetes部署配置

创建k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service-deployment
  labels:
    app: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: PORT
          value: "3000"
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

Ingress配置

创建k8s/ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: user-service-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:
  - host: api.userservice.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 80

ConfigMap配置

创建k8s/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: user-service-config
data:
  NODE_ENV: "production"
  LOG_LEVEL: "info"
  API_TIMEOUT: "5000"

Service Account和RBAC

创建k8s/rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: user-service-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: user-service-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: user-service-binding
subjects:
- kind: ServiceAccount
  name: user-service-sa
roleRef:
  kind: Role
  name: user-service-role
  apiGroup: rbac.authorization.k8s.io

监控与日志管理

日志配置

创建src/config/logger.ts

import winston from 'winston';
import { format, transports } from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: format.combine(
    format.timestamp(),
    format.errors({ stack: true }),
    format.json()
  ),
  defaultMeta: { service: 'user-service' },
  transports: [
    new transports.Console({
      format: format.combine(
        format.colorize(),
        format.simple()
      )
    }),
    new transports.File({ 
      filename: 'logs/error.log', 
      level: 'error',
      maxsize: '50m',
      maxFiles: 5
    }),
    new transports.File({ 
      filename: 'logs/combined.log' 
    })
  ]
});

export default logger;

健康检查和监控

更新src/server.ts添加健康检查:

import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

// 导入路由和日志
import userRoutes from './routes/users';
import logger from './config/logger';

// 加载环境变量
dotenv.config();

const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);

// 中间件配置
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 健康检查端点
app.get('/health', (req: Request, res: Response) => {
  logger.info('Health check endpoint accessed');
  res.json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'user-service',
    version: process.env.npm_package_version || '1.0.0'
  });
});

// API端点
app.use('/api/users', userRoutes);

// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  logger.error(`Error: ${err.message}`, { stack: err.stack });
  res.status(500).json({
    error: 'Something went wrong!',
    message: err.message
  });
});

// 404处理
app.use((req: Request, res: Response) => {
  logger.warn(`Route not found: ${req.originalUrl}`);
  res.status(404).json({
    error: 'Not Found',
    message: 'Route not found'
  });
});

app.listen(PORT, () => {
  logger.info(`Server is running on port ${PORT}`);
});

export default app;

性能优化与最佳实践

缓存策略实现

创建src/middleware/cache.ts

import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');

export const cacheMiddleware = (duration: number = 300) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    const key = `cache:${req.originalUrl}`;
    
    try {
      const cachedData = await redis.get(key);
      
      if (cachedData) {
        return res.json(JSON.parse(cachedData));
      }
      
      // 重写res.json方法来缓存响应
      const originalJson = res.json;
      res.json = function(data) {
        redis.setex(key, duration, JSON.stringify(data));
        return originalJson.call(this, data);
      };
      
      next();
    } catch (error) {
      console.error('Cache error:', error);
      next();
    }
  };
};

请求限流

创建src/middleware/rateLimit.ts

import { Request, Response, NextFunction } from 'express';
import rateLimit from 'express-rate-limit';

export const createRateLimiter = (maxRequests: number, windowMs: number) => {
  return rateLimit({
    windowMs,
    max: maxRequests,
    message: {
      error: 'Too many requests',
      message: `Too many requests from this IP, please try again after ${windowMs / 1000} seconds`
    },
    standardHeaders: true,
    legacyHeaders: false,
  });
};

数据库连接池优化

更新src/config/database.ts

import { Pool } from 'pg';
import { createPool } from 'generic-pool';

const pool = new Pool({
  user: process.env.DB_USER || 'postgres',
  host: process.env.DB_HOST || 'localhost',
  database: process.env.DB_NAME || 'userservice',
  password: process.env.DB_PASSWORD || 'password',
  port: parseInt(process.env.DB_PORT || '5432', 10),
  max: 20, // 最大连接数
  min: 5,  // 最小连接数
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// 创建连接池
const connectionPool = createPool({
  create: () => pool.connect(),
  destroy: (client) => client.release(),
  validate: (client) => !client._invalid && !client._ended,
  max: 10,
  min: 2,
  acquireTimeoutMillis: 30000,
  idleTimeoutMillis: 30000,
  reapIntervalMillis: 1000,
});

export default connectionPool;

测试策略

单元测试配置

创建src/__tests__/userService.test.ts

import userService from '../services/userService';
import { User, CreateUserRequest } from '../types';

describe('UserService', () => {
  beforeEach(() => {
    // 清空用户数据
    (userService as any).users = [];
  });

  test('should create a new user', async () => {
    const userData: CreateUserRequest = {
      name: 'John Doe',
      email: 'john@example.com'
    };

    const result = await userService.createUser(userData);
    
    expect(result.success).toBe(true);
    expect(result.data).toBeDefined();
    expect(result.data.name).toBe(userData.name);
    expect(result.data.email).toBe(userData.email);
  });

  test('should get user by id', async () => {
    const userData: CreateUserRequest = {
      name: 'Jane Smith',
      email: 'jane@example.com'
    };

    const createResult = await userService.createUser(userData);
    const userId = createResult.data!.id;

    const getResult = await userService.getUserById(userId);
    
    expect(getResult.success).toBe(true);
    expect(getResult.data!.name).toBe(userData.name);
  });

  test('should update user', async () => {
    const userData: CreateUserRequest = {
      name: 'Original Name',
      email: 'original@example.com'
    };

    const createResult = await userService.createUser(userData);
    const userId = createResult.data!.id;

    const updateData = {
      name: 'Updated Name'
    };

    const updateResult = await userService.updateUser(userId, updateData);
    
    expect(updateResult.success).toBe(true);
    expect(updateResult.data!.name).toBe(updateData.name);
  });
});

集成测试

创建src/__tests__/api.test.ts

import request from 'supertest';
import app from '../server';

describe('API Endpoints', () => {
  describe('GET /health', () => {
    it('should return health check status', async () => {
      const response = await request(app)
        .get('/health')
        .expect(200);
      
      expect(response.body).toHaveProperty('status', 'OK');
      expect(response.body).toHaveProperty('service', 'user-service');
    });
  });

  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201);
      
      expect(response.body).toHaveProperty('success', true);
      expect(response.body.data).toHaveProperty('name', userData.name);
      expect(response.body.data).toHaveProperty('email', userData.email);
    });
  });
});

CI/CD流水线

GitHub Actions配置

创建.github/workflows/ci-cd.yml

name: 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 test
    
    - name: Run linting
      run: npm run lint
    
    - name: Build application
      run: npm run build

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    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: Build Docker image
      run: |
        docker build -t user-service:${{ github.sha }} .
        docker tag user-service:${{ github.sha }} user-service:latest
    
    - name: Push to registry
      run: |
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
        docker push user-service:${{ github.sha }}
        docker push user-service:latest
    
    - name: Deploy to Kubernetes
      run: |
        echo "${{ secrets.KUBECONFIG }}" > kubeconfig
        export KUBECONFIG=kubeconfig
        kubectl set image deployment/user-service-deployment
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000