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

夜晚的诗人
夜晚的诗人 2026-01-25T22:06:17+08:00
0 0 1

引言

在现代软件开发中,微服务架构已成为构建可扩展、可维护应用的重要模式。Node.js凭借其非阻塞I/O和事件驱动特性,在微服务领域表现出色。本文将从零开始,手把手教学如何使用Express框架搭建Node.js微服务,并通过Docker容器化技术,最终在Kubernetes集群中部署和管理这些微服务。

什么是微服务架构

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

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

这种架构模式带来了诸多优势:技术栈灵活性、团队自治性、可扩展性和容错性。

技术栈概述

本文将使用以下核心技术栈:

  • Express.js:轻量级Node.js Web应用框架
  • Docker:容器化平台,用于服务打包和部署
  • Kubernetes:容器编排平台,用于自动化部署、扩展和管理容器化应用

第一步:搭建Express微服务基础架构

1.1 初始化项目结构

首先创建项目目录并初始化Node.js项目:

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

安装必要的依赖包:

npm install express cors helmet morgan dotenv
npm install --save-dev nodemon jest supertest

1.2 创建基础Express应用

创建src/app.js文件:

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

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

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

// 根路径测试
app.get('/', (req, res) => {
  res.json({ 
    message: 'Welcome to User Service API',
    version: '1.0.0'
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

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

module.exports = app;

1.3 创建服务入口文件

创建src/server.js

const app = require('./app');
const { PORT } = process.env;

const server = app.listen(PORT, () => {
  console.log(`🚀 User service running on port ${PORT}`);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received, shutting down gracefully');
  server.close(() => {
    console.log('Process terminated');
  });
});

process.on('SIGINT', () => {
  console.log('SIGINT received, shutting down gracefully');
  server.close(() => {
    console.log('Process terminated');
  });
});

module.exports = server;

1.4 配置环境变量

创建.env文件:

NODE_ENV=development
PORT=3000
SERVICE_NAME=user-service
LOG_LEVEL=info

第二步:实现核心业务功能

2.1 创建用户服务模型

创建src/models/User.js

class User {
  constructor(id, name, email, age) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.age = age;
    this.createdAt = new Date();
  }

  static validate(user) {
    const errors = [];
    
    if (!user.name || user.name.trim().length === 0) {
      errors.push('Name is required');
    }
    
    if (!user.email || !this.isValidEmail(user.email)) {
      errors.push('Valid email is required');
    }
    
    if (user.age && (user.age < 0 || user.age > 150)) {
      errors.push('Age must be between 0 and 150');
    }
    
    return errors;
  }

  static isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

module.exports = User;

2.2 创建用户服务控制器

创建src/controllers/userController.js

const User = require('../models/User');

// 模拟数据库存储(实际项目中应使用真实数据库)
let users = [
  { id: 1, name: 'John Doe', email: 'john@example.com', age: 30 },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25 }
];

let nextId = 3;

const userController = {
  // 获取所有用户
  getAllUsers: (req, res) => {
    try {
      res.status(200).json({
        success: true,
        data: users,
        count: users.length
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch users'
      });
    }
  },

  // 根据ID获取用户
  getUserById: (req, res) => {
    try {
      const id = parseInt(req.params.id);
      const user = users.find(u => u.id === id);
      
      if (!user) {
        return res.status(404).json({
          success: false,
          error: 'User not found'
        });
      }
      
      res.status(200).json({
        success: true,
        data: user
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch user'
      });
    }
  },

  // 创建用户
  createUser: (req, res) => {
    try {
      const { name, email, age } = req.body;
      
      // 验证输入
      const validationErrors = User.validate({ name, email, age });
      if (validationErrors.length > 0) {
        return res.status(400).json({
          success: false,
          error: 'Validation failed',
          details: validationErrors
        });
      }
      
      // 创建新用户
      const newUser = new User(nextId++, name, email, age);
      users.push(newUser);
      
      res.status(201).json({
        success: true,
        data: newUser
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to create user'
      });
    }
  },

  // 更新用户
  updateUser: (req, res) => {
    try {
      const id = parseInt(req.params.id);
      const userIndex = users.findIndex(u => u.id === id);
      
      if (userIndex === -1) {
        return res.status(404).json({
          success: false,
          error: 'User not found'
        });
      }
      
      const { name, email, age } = req.body;
      
      // 验证输入
      const validationErrors = User.validate({ name, email, age });
      if (validationErrors.length > 0) {
        return res.status(400).json({
          success: false,
          error: 'Validation failed',
          details: validationErrors
        });
      }
      
      // 更新用户信息
      users[userIndex] = {
        ...users[userIndex],
        name,
        email,
        age,
        updatedAt: new Date()
      };
      
      res.status(200).json({
        success: true,
        data: users[userIndex]
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to update user'
      });
    }
  },

  // 删除用户
  deleteUser: (req, res) => {
    try {
      const id = parseInt(req.params.id);
      const userIndex = users.findIndex(u => u.id === id);
      
      if (userIndex === -1) {
        return res.status(404).json({
          success: false,
          error: 'User not found'
        });
      }
      
      const deletedUser = users.splice(userIndex, 1)[0];
      
      res.status(200).json({
        success: true,
        message: 'User deleted successfully',
        data: deletedUser
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to delete user'
      });
    }
  }
};

module.exports = userController;

2.3 配置路由

创建src/routes/userRoutes.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');

// GET /users - 获取所有用户
router.get('/', userController.getAllUsers);

// GET /users/:id - 根据ID获取用户
router.get('/:id', userController.getUserById);

// POST /users - 创建新用户
router.post('/', userController.createUser);

// PUT /users/:id - 更新用户
router.put('/:id', userController.updateUser);

// DELETE /users/:id - 删除用户
router.delete('/:id', userController.deleteUser);

module.exports = router;

2.4 集成路由到主应用

修改src/app.js,添加路由集成:

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

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

// 引入路由
const userRoutes = require('./routes/userRoutes');

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

// API路由
app.use('/users', userRoutes);

// 根路径测试
app.get('/', (req, res) => {
  res.json({ 
    message: 'Welcome to User Service API',
    version: '1.0.0'
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

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

module.exports = app;

第三步:Docker容器化

3.1 创建Dockerfile

创建Dockerfile

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

# 设置工作目录
WORKDIR /app

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

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

# 复制应用源码
COPY . .

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

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

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

3.2 创建.dockerignore文件

创建.dockerignore

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

3.3 构建和测试Docker镜像

构建镜像:

docker build -t user-service:latest .

运行容器:

docker run -p 3000:3000 --name user-service-container user-service:latest

第四步:测试和监控

4.1 编写单元测试

创建src/__tests__/userController.test.js

const request = require('supertest');
const app = require('../app');

describe('User Controller', () => {
  describe('GET /users', () => {
    it('should return all users', async () => {
      const response = await request(app)
        .get('/users')
        .expect(200)
        .expect('Content-Type', /json/);
      
      expect(response.body.success).toBe(true);
      expect(Array.isArray(response.body.data)).toBe(true);
    });
  });

  describe('POST /users', () => {
    it('should create a new user with valid data', async () => {
      const newUser = {
        name: 'Test User',
        email: 'test@example.com',
        age: 25
      };

      const response = await request(app)
        .post('/users')
        .send(newUser)
        .expect(201)
        .expect('Content-Type', /json/);
      
      expect(response.body.success).toBe(true);
      expect(response.body.data.name).toBe(newUser.name);
      expect(response.body.data.email).toBe(newUser.email);
    });

    it('should return validation error for invalid email', async () => {
      const invalidUser = {
        name: 'Test User',
        email: 'invalid-email',
        age: 25
      };

      const response = await request(app)
        .post('/users')
        .send(invalidUser)
        .expect(400);
      
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Validation failed');
    });
  });

  describe('GET /health', () => {
    it('should return health check status', async () => {
      const response = await request(app)
        .get('/health')
        .expect(200)
        .expect('Content-Type', /json/);
      
      expect(response.body.status).toBe('OK');
    });
  });
});

4.2 配置测试脚本

修改package.json

{
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "test": "jest --verbose",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}

第五步:Kubernetes部署

5.1 创建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

5.2 创建服务配置文件

创建k8s/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: user-service
  labels:
    app: user-service
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP

5.3 创建Ingress配置(可选)

创建k8s/ingress.yaml

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

5.4 部署到Kubernetes集群

使用kubectl部署:

# 创建命名空间
kubectl create namespace microservices

# 应用配置
kubectl apply -f k8s/deployment.yaml -n microservices

# 检查部署状态
kubectl get deployments -n microservices
kubectl get pods -n microservices
kubectl get services -n microservices

# 查看日志
kubectl logs -l app=user-service -n microservices

# 访问服务(端口转发)
kubectl port-forward svc/user-service 8080:80 -n microservices

第六步:高级配置和最佳实践

6.1 配置文件管理

创建src/config/index.js

const config = {
  port: process.env.PORT || 3000,
  environment: process.env.NODE_ENV || 'development',
  service: {
    name: process.env.SERVICE_NAME || 'user-service',
    version: process.env.VERSION || '1.0.0'
  },
  database: {
    host: process.env.DB_HOST || 'localhost',
    port: process.env.DB_PORT || 5432,
    name: process.env.DB_NAME || 'users'
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info',
    format: process.env.LOG_FORMAT || 'json'
  }
};

module.exports = config;

6.2 添加监控和日志

创建src/middleware/logger.js

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'user-service' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

6.3 实现限流和安全措施

创建src/middleware/rateLimiter.js

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 限制每个IP 100个请求
  message: {
    error: 'Too many requests from this IP, please try again later.'
  },
  standardHeaders: true,
  legacyHeaders: false,
});

module.exports = limiter;

src/app.js中集成:

const rateLimiter = require('./middleware/rateLimiter');

// 在其他中间件之后添加限流器
app.use(rateLimiter);

6.4 Docker Compose支持(本地开发)

创建docker-compose.yml

version: '3.8'

services:
  user-service:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - PORT=3000
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      - redis
    networks:
      - microservice-network

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    networks:
      - microservice-network

networks:
  microservice-network:
    driver: bridge

第七步:持续集成/持续部署(CI/CD)

7.1 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 coverage
      run: npm run test:coverage

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v2
    
    - name: Login to DockerHub
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: your-username/user-service:latest

第八步:性能优化和监控

8.1 应用性能监控

安装性能监控依赖:

npm install express-prom-bundle prom-client

创建src/middleware/monitoring.js

const promBundle = require('express-prom-bundle');
const client = require('prom-client');

// 创建自定义指标
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.5, 1, 2, 5, 10]
});

// 注册指标
client.register.registerMetric(httpRequestDuration);

const metricsMiddleware = promBundle({
  includeMethod: true,
  includeRoute: true,
  includeStatusCode: true,
  normalizePath: (req, res) => {
    return req.path.replace(/\/\d+/g, '/:id');
  }
});

module.exports = { metricsMiddleware, httpRequestDuration };

8.2 健康检查端点增强

修改src/app.js中的健康检查:

app.get('/health', (req, res) => {
  // 检查数据库连接
  const dbStatus = checkDatabaseConnection();
  
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'user-service',
    version: process.env.npm_package_version,
    uptime: process.uptime(),
    memory: {
      rss: process.memoryUsage().rss,
      heapTotal: process.memoryUsage().heapTotal,
      heapUsed: process.memoryUsage().heapUsed
    },
    database: dbStatus
  });
});

function checkDatabaseConnection() {
  // 实现数据库连接检查逻辑
  return { status: 'healthy' };
}

总结

本文详细介绍了从零开始构建Node.js微服务的完整流程,涵盖了:

  1. 基础架构搭建:使用Express框架创建可扩展的服务
  2. 容器化部署:通过Docker将应用打包成标准容器
  3. Kubernetes部署:在云原生环境中实现服务编排和管理
  4. 测试和监控:确保服务质量和稳定性
  5. 最佳实践:包括安全、性能优化和CI/CD集成

这个完整的微服务架构方案具有以下特点:

  • 可扩展性:基于Kubernetes的弹性伸缩能力
  • 可靠性:内置健康检查和自动恢复机制
  • 安全性:多层次的安全防护措施
  • 可观测性:完善的监控和日志系统
  • 可维护性:标准化的开发和部署流程

通过遵循本文提供的实践指南,开发者可以快速构建出生产就绪的Node.js微服务应用,并将其成功部署到现代化的云原生环境中。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000