MCP 生产部署与运维实践

将 MCP 服务器从开发环境推进到生产环境需要解决稳定性、扩展性和可观测性三大挑战。本文详解容器编排、负载均衡、健康检查、监控告警和配置管理的完整方案。

1. 生产环境的挑战

将 MCP 服务器从本地开发部署到生产环境面临一系列独特的挑战:

进程模型差异:开发时通常使用 stdio 传输,每个 AI 助手会话启动一个独立的服务器进程。这种"每会话一进程"模型在生产中会导致资源爆炸,需要转向常驻进程或按需扩容模型。

多用户并发:开发环境通常是单用户,而生产环境需要同时服务成百上千的 AI 助手会话。这要求 MCP 服务器是无状态的或可共享状态的。

网络拓扑复杂度:生产环境中 MCP 服务器可能分布在多个可用区、多个 Kubernetes 集群中。客户端需要服务发现机制来定位健康的服务器实例。

安全边界强化:开发时可能为了方便开放了宽松权限,生产环境需要严格的网络隔离、身份认证和审计。

可观测性要求:需要追踪每个工具调用的延迟、成功率和资源消耗,以便及时发现问题和优化性能。

2. 部署架构选择

2.1 架构模式 A:单边车模式(Sidecar)

每个 AI 宿主实例旁运行一个 MCP 服务器进程,两者共享生命周期:

┌─────────────────────────────────────┐
│         AI Assistant Host            │
│  ┌─────────────┐  ┌──────────────┐  │
│  │ LLM Client  │  │ MCP Client   │  │
│  └─────────────┘  └──────────────┘  │
│         │               │            │
│         │               │ stdio      │
│         │          ┌────┴────┐       │
│         │          │ MCP Server│      │
│         │          └────┬────┘       │
│         │               │            │
│         ▼               ▼            │
│  ┌─────────────────────────────┐    │
│  │      External Services      │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

优点:隔离性好,故障影响范围小,适合有状态工具
缺点:资源利用率低,每个会话都占用独立内存

2.2 架构模式 B:共享服务池

多个 AI 宿主实例共享一个 MCP 服务器集群:

┌──────────┐  ┌──────────┐  ┌──────────┐
│ AI Host 1│  │ AI Host 2│  │ AI Host 3│
└────┬─────┘  └────┬─────┘  └────┬─────┘
     │             │             │
     └─────────────┼─────────────┘
                   │ HTTP + SSE
          ┌────────▼────────┐
          │  Load Balancer  │
          └────────┬────────┘
                   │
     ┌─────────────┼─────────────┐
     ▼             ▼             ▼
┌─────────┐  ┌─────────┐  ┌─────────┐
│ MCP Srv │  │ MCP Srv │  │ MCP Srv │
│  Pod 1  │  │  Pod 2  │  │  Pod 3  │
└─────────┘  └─────────┘  └─────────┘

优点:资源利用率高,易于扩展,集中管理
缺点:需要处理并发安全,网络延迟增加

2.3 架构模式 C:函数即服务(FaaS)

将 MCP 服务器部署为无函数,冷启动时初始化:

# serverless.yml 示例
service: mcp-tools
provider:
  name: aws
  runtime: nodejs20.x

functions:
  filesystem:
    handler: dist/handlers/filesystem.handler
    timeout: 30
    memorySize: 512
    events:
      - http:
          path: /mcp/filesystem
          method: post

优点:按需付费,自动扩缩容,无服务器管理负担
缺点:冷启动延迟,执行时间受限,状态管理困难

3. Kubernetes 部署

3.1 Deployment 配置

# mcp-server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-filesystem-server
  labels:
    app: mcp-filesystem
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-filesystem
  template:
    metadata:
      labels:
        app: mcp-filesystem
    spec:
      containers:
        - name: server
          image: registry.example.com/mcp/filesystem:v1.2.3
          ports:
            - containerPort: 3000
              name: http
          env:
            - name: NODE_ENV
              value: "production"
            - name: ALLOWED_PATHS
              value: "/data/workspace"
            - name: LOG_LEVEL
              value: "info"
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5
          securityContext:
            runAsNonRoot: true
            runAsUser: 1001
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
          volumeMounts:
            - name: data-volume
              mountPath: /data
              readOnly: true
            - name: tmp-volume
              mountPath: /tmp
      volumes:
        - name: data-volume
          persistentVolumeClaim:
            claimName: workspace-data
        - name: tmp-volume
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: mcp-filesystem-service
spec:
  selector:
    app: mcp-filesystem
  ports:
    - port: 80
      targetPort: 3000
  type: ClusterIP

3.2 水平自动扩缩容

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-filesystem-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-filesystem-server
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120

4. 健康检查与优雅关闭

4.1 健康检查端点

// src/health.ts
import { Request, Response } from "express";

interface HealthStatus {
  status: "healthy" | "degraded" | "unhealthy";
  checks: Record<string, CheckResult>;
  timestamp: string;
}

interface CheckResult {
  status: "pass" | "fail" | "warn";
  responseTime: number;
  message?: string;
}

export async function healthCheck(req: Request, res: Response): Promise<void> {
  const start = Date.now();
  const checks: Record<string, CheckResult> = {};

  // 检查文件系统访问
  try {
    const fsStart = Date.now();
    await checkFilesystemAccess();
    checks.filesystem = {
      status: "pass",
      responseTime: Date.now() - fsStart,
    };
  } catch (error) {
    checks.filesystem = {
      status: "fail",
      responseTime: Date.now() - start,
      message: error instanceof Error ? error.message : String(error),
    };
  }

  // 检查内存使用
  const memUsage = process.memoryUsage();
  const memPercent = memUsage.heapUsed / memUsage.heapTotal;
  checks.memory = {
    status: memPercent > 0.9 ? "warn" : "pass",
    responseTime: 0,
    message: `Heap: ${(memUsage.heapUsed / 1024 / 1024).toFixed(1)}MB`,
  };

  const overallStatus = Object.values(checks).every((c) => c.status === "pass")
    ? "healthy"
    : Object.values(checks).some((c) => c.status === "fail")
    ? "unhealthy"
    : "degraded";

  const status: HealthStatus = {
    status: overallStatus,
    checks,
    timestamp: new Date().toISOString(),
  };

  res.status(overallStatus === "healthy" ? 200 : overallStatus === "degraded" ? 200 : 503).json(status);
}

export async function readinessCheck(req: Request, res: Response): Promise<void> {
  // 就绪检查:服务器是否准备好接收流量
  // 例如:是否已完成初始化、是否已连接到依赖服务
  res.status(200).json({ ready: true });
}

4.2 优雅关闭

// src/shutdown.ts
import { Server } from "http";

class GracefulShutdown {
  private isShuttingDown = false;
  private activeConnections: Set<any> = new Set();

  constructor(private httpServer: Server, private timeoutMs: number = 30000) {}

  register(): void {
    process.on("SIGTERM", () => this.shutdown("SIGTERM"));
    process.on("SIGINT", () => this.shutdown("SIGINT"));

    // 追踪活跃连接
    this.httpServer.on("connection", (conn) => {
      this.activeConnections.add(conn);
      conn.on("close", () => {
        this.activeConnections.delete(conn);
      });
    });
  }

  private async shutdown(signal: string): Promise<void> {
    console.log(`收到 ${signal},开始优雅关闭...`);
    this.isShuttingDown = true;

    // 停止接收新连接
    this.httpServer.close(() => {
      console.log("HTTP 服务器已停止接收新连接");
    });

    // 设置超时强制退出
    const forceExit = setTimeout(() => {
      console.error("关闭超时,强制退出");
      process.exit(1);
    }, this.timeoutMs);

    // 等待活跃连接处理完
    const waitForConnections = async () => {
      while (this.activeConnections.size > 0) {
        console.log(`等待 ${this.activeConnections.size} 个连接完成...`);
        await new Promise((r) => setTimeout(r, 1000));
      }
    };

    await waitForConnections();
    clearTimeout(forceExit);
    console.log("所有连接已关闭,正常退出");
    process.exit(0);
  }
}

5. 监控与可观测性

5.1 Prometheus 指标

// src/metrics.ts
import prometheus from "prom-client";

// 注册表
const register = new prometheus.Registry();

// 工具调用计数器
const toolCallsTotal = new prometheus.Counter({
  name: "mcp_tool_calls_total",
  help: "Total number of tool calls",
  labelNames: ["tool_name", "status"],
  registers: [register],
});

// 工具调用延迟直方图
const toolCallDuration = new prometheus.Histogram({
  name: "mcp_tool_call_duration_seconds",
  help: "Tool call duration in seconds",
  labelNames: ["tool_name"],
  buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
  registers: [register],
});

// 活跃连接数
const activeConnections = new prometheus.Gauge({
  name: "mcp_active_connections",
  help: "Number of active MCP connections",
  registers: [register],
});

// 资源使用
const resourceReadsTotal = new prometheus.Counter({
  name: "mcp_resource_reads_total",
  help: "Total number of resource reads",
  labelNames: ["uri"],
  registers: [register],
});

export function recordToolCall(toolName: string, durationSec: number, success: boolean): void {
  const status = success ? "success" : "failure";
  toolCallsTotal.inc({ tool_name: toolName, status });
  toolCallDuration.observe({ tool_name: toolName }, durationSec);
}

export function getMetrics(): Promise<string> {
  return register.metrics();
}

5.2 结构化日志

// src/logger.ts
import winston from "winston";

export const logger = winston.createLogger({
  level: process.env.LOG_LEVEL ?? "info",
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: "mcp-server" },
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: "logs/error.log", level: "error" }),
    new winston.transports.File({ filename: "logs/combined.log" }),
  ],
});

// 工具调用日志
export function logToolCall(params: {
  tool: string;
  args: Record<string, unknown>;
  result: "success" | "failure";
  durationMs: number;
  sessionId: string;
  error?: Error;
}): void {
  logger.info("tool_call", {
    tool: params.tool,
    args: sanitizeArgs(params.args),
    result: params.result,
    duration_ms: params.durationMs,
    session_id: params.sessionId,
    error: params.error?.message,
  });
}

function sanitizeArgs(args: Record<string, unknown>): Record<string, unknown> {
  const sensitive = new Set(["password", "token", "secret", "api_key", "key"]);
  const result: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(args)) {
    result[k] = sensitive.has(k.toLowerCase()) ? "[REDACTED]" : v;
  }
  return result;
}

5.3 告警规则

# alert-rules.yaml
groups:
  - name: mcp-alerts
    rules:
      - alert: MCPHighErrorRate
        expr: rate(mcp_tool_calls_total{status="failure"}[5m]) / rate(mcp_tool_calls_total[5m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MCP 服务器错误率过高"
          description: "{{ $labels.tool_name }} 的错误率超过 10%"

      - alert: MCPHighLatency
        expr: histogram_quantile(0.95, rate(mcp_tool_call_duration_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MCP 工具调用延迟过高"
          description: "{{ $labels.tool_name }} 的 P95 延迟超过 2 秒"

      - alert: MCPPodCrashLooping
        expr: rate(kube_pod_container_status_restarts_total{container="mcp-server"}[15m]) > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "MCP Pod 正在反复重启"

6. 配置管理

6.1 环境变量分层

// src/config.ts
interface ServerConfig {
  port: number;
  logLevel: string;
  allowedPaths: string[];
  maxFileSize: number;
  rateLimit: {
    callsPerMinute: number;
    callsPerHour: number;
  };
  features: {
    tools: boolean;
    resources: boolean;
    prompts: boolean;
    subscriptions: boolean;
  };
}

function loadConfig(): ServerConfig {
  return {
    port: parseInt(process.env.PORT ?? "3000"),
    logLevel: process.env.LOG_LEVEL ?? "info",
    allowedPaths: (process.env.ALLOWED_PATHS ?? "/tmp").split(","),
    maxFileSize: parseInt(process.env.MAX_FILE_SIZE ?? "10485760"), // 10MB
    rateLimit: {
      callsPerMinute: parseInt(process.env.RATE_LIMIT_PER_MINUTE ?? "60"),
      callsPerHour: parseInt(process.env.RATE_LIMIT_PER_HOUR ?? "500"),
    },
    features: {
      tools: process.env.FEATURE_TOOLS !== "false",
      resources: process.env.FEATURE_RESOURCES !== "false",
      prompts: process.env.FEATURE_PROMPTS !== "false",
      subscriptions: process.env.FEATURE_SUBSCRIPTIONS === "true",
    },
  };
}

export const config = loadConfig();

6.2 ConfigMap 和 Secret

apiVersion: v1
kind: ConfigMap
metadata:
  name: mcp-server-config
data:
  LOG_LEVEL: "info"
  ALLOWED_PATHS: "/data/workspace,/data/shared"
  MAX_FILE_SIZE: "10485760"
  RATE_LIMIT_PER_MINUTE: "120"
  FEATURE_SUBSCRIPTIONS: "true"
---
apiVersion: v1
kind: Secret
metadata:
  name: mcp-server-secrets
type: Opaque
stringData:
  DATABASE_URL: "postgresql://user:pass@db/mcp"
  API_KEY: "sk-production-key"

7. 备份与恢复

// 定期备份工具调用日志和配置
import { CronJob } from "cron";

const backupJob = new CronJob(
  "0 2 * * *", // 每天凌晨 2 点
  async () => {
    logger.info("开始备份 MCP 数据");
    try {
      // 备份审计日志
      await backupAuditLogs();
      // 备份配置文件
      await backupConfiguration();
      // 清理过期日志
      await cleanupOldLogs(30); // 保留 30 天
    } catch (error) {
      logger.error("备份失败", { error });
    }
  },
  null,
  true,
  "UTC"
);

8. 总结

MCP 服务器的生产部署需要从开发思维全面转向运维思维:

维度开发环境生产环境
进程每会话一个常驻服务池
传输stdioHTTP + SSE
认证无OAuth / mTLS
资源无限制CPU/内存/文件限制
监控控制台日志Prometheus + Grafana
扩展手动HPA 自动扩缩容
部署npm startKubernetes

成功的生产部署需要关注三个核心指标:可用性(uptime)、延迟(latency)和错误率(error rate)。通过健康检查确保可用性,通过资源限制和自动扩缩容控制延迟,通过结构化日志和告警规则监控错误率。只有这三项指标都达标,MCP 服务器才能真正稳定地服务于 AI 应用。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 排序学习与搜索召回排序系统
  2. 数据版本控制与血缘:DVC 与 LakeFS
  3. 模型可解释性:SHAP、LIME 与注意力归因