บทนำ — ทำไมต้อง Containerize MCP Server

ในฐานะวิศวกรที่ดูแลระบบ AI Infrastructure มาหลายปี ผมเคยเจอปัญหา "Dependency Hell" เป็นร้อยครั้ง — บางครั้ง Python library ตัวนึงต้องการ version A แต่อีกตัวต้องการ version B หรือพอ upgrade ไปแล้ว service อื่นพัง ปัญหาเหล่านี้หมดไปเมื่อเรานำ Docker และ Docker Compose มาใช้ MCP (Model Context Protocol) Server คือหัวใจของการเชื่อมต่อ AI Model กับ Tools ต่างๆ ไม่ว่าจะเป็น Database, API, หรือ Filesystem เมื่อ Containerize แล้ว เราได้ทั้ง Isolation, Reproducibility และ Scale ที่ง่าย ในบทความนี้ผมจะพาทุกท่านเจาะลึกการ deploy MCP Server หลายตัวพร้อมกันด้วย Docker Compose พร้อม Performance Tuning, Concurrency Control และ Cost Optimization ที่ใช้งานจริงใน production

สถาปัตยกรรมระบบ Multi-Container MCP

สถาปัตยกรรมที่เราจะสร้างประกอบด้วย 4 main components: โครงสร้าง Docker Network จะแยก traffic ระหว่าง internal services (mcp-servers) กับ external traffic ออกจากกันชัดเจน ทำให้ security และ performance ดีขึ้น
┌─────────────────────────────────────────────────────────┐
│                    External Traffic                       │
│                    (Clients/AI Apps)                      │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              MCP Gateway (Nginx/Traefik)                  │
│              Port: 8080 (HTTP), 8443 (HTTPS)             │
└─────────────────────┬───────────────────────────────────┘
                      │
        ┌─────────────┼─────────────┬─────────────┐
        ▼             ▼             ▼             ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│   Filesystem │ │  Database  │ │    API     │ │  Custom   │
│   MCP Srv   │ │  MCP Srv   │ │ MCP Srv    │ │ MCP Srv   │
│  Port:3001  │ │  Port:3002  │ │  Port:3003  │ │  Port:3004  │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              Internal Network (mcp-internal)              │
│              Shared Volume: /data                         │
└─────────────────────────────────────────────────────────┘

Docker Compose Configuration — Production Ready

นี่คือ docker-compose.yml ที่ผมใช้งานจริงใน production มาแล้วกว่า 6 เดือน มีทั้ง health check, restart policy, resource limits และ logging configuration ครบถ้วน
version: '3.8'

services:
  mcp-gateway:
    image: nginx:alpine
    container_name: mcp-gateway
    ports:
      - "8080:8080"
      - "8443:8443"
    volumes:
      - ./gateway/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    networks:
      - mcp-public
      - mcp-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
        reservations:
          cpus: '0.25'
          memory: 128M

  mcp-filesystem:
    build:
      context: ./servers/filesystem
      dockerfile: Dockerfile
    container_name: mcp-filesystem
    environment:
      - ALLOWED_PATHS=/data,/workspace
      - MAX_FILE_SIZE=104857600
      - CONCURRENT_OPERATIONS=10
      - RATE_LIMIT_PER_MINUTE=100
    volumes:
      - shared-data:/data
      - ./workspace:/workspace:ro
    networks:
      - mcp-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

  mcp-database:
    build:
      context: ./servers/database
      dockerfile: Dockerfile
    container_name: mcp-database
    environment:
      - DB_HOST=${DB_HOST:-postgres}
      - DB_PORT=${DB_PORT:-5432}
      - DB_NAME=${DB_NAME:-mcp_production}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - CONNECTION_POOL_SIZE=20
      - QUERY_TIMEOUT=30000
      - MAX_CONCURRENT_QUERIES=50
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - mcp-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G

  mcp-api:
    build:
      context: ./servers/api
      dockerfile: Dockerfile
    container_name: mcp-api
    environment:
      - API_TIMEOUT=30000
      - MAX_RETRIES=3
      - BATCH_SIZE=100
      - CONCURRENT_REQUESTS=25
      - HOLYSHEEP_API_KEY=${HOLYSHEEP_API_KEY}
      - HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
    networks:
      - mcp-internal
      - mcp-public
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3003/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    container_name: mcp-postgres
    environment:
      - POSTGRES_DB=mcp_production
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./init-scripts:/docker-entrypoint-initdb.d:ro
    networks:
      - mcp-internal
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G

networks:
  mcp-public:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
  mcp-internal:
    driver: bridge
    internal: true

volumes:
  postgres-data:
    driver: local
  shared-data:
    driver: local

Nginx Gateway Configuration

Gateway config ที่รองรับ WebSocket สำหรับ real-time communication และ rate limiting
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';
    
    access_log /var/log/nginx/access.log main buffer=16k flush=2s;
    error_log /var/log/nginx/error.log warn;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    keepalive_requests 1000;
    types_hash_max_size 2048;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript 
               application/xml application/xml+rss text/javascript application/x-javascript;

    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
    limit_req_zone $binary_remote_addr zone=burst_limit:10m rate=10r/s burst=50;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    upstream filesystem_srv {
        least_conn;
        server mcp-filesystem:3001 max_fails=3 fail_timeout=30s;
        keepalive 32;
    }

    upstream database_srv {
        least_conn;
        server mcp-database:3002 max_fails=3 fail_timeout=30s;
        keepalive 32;
    }

    upstream api_srv {
        least_conn;
        server mcp-api:3003 max_fails=3 fail_timeout=30s;
        keepalive 32;
    }

    server {
        listen 8080;
        server_name _;

        # Health check endpoint
        location /health {
            access_log off;
            return 200 "healthy\n";
            add_header Content-Type text/plain;
        }

        # Filesystem MCP endpoints
        location /mcp/filesystem {
            limit_req zone=api_limit burst=20 nodelay;
            limit_conn conn_limit 10;
            
            proxy_pass http://filesystem_srv;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            proxy_connect_timeout 30s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
            
            proxy_buffering off;
            proxy_request_buffering off;
        }

        # Database MCP endpoints
        location /mcp/database {
            limit_req zone=burst_limit burst=50 nodelay;
            limit_conn conn_limit 20;
            
            proxy_pass http://database_srv;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            proxy_connect_timeout 60s;
            proxy_send_timeout 300s;
            proxy_read_timeout 300s;
        }

        # API MCP endpoints
        location /mcp/api {
            limit_req zone=api_limit burst=30 nodelay;
            
            proxy_pass http://api_srv;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            proxy_connect_timeout 30s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        # WebSocket support
        location /ws {
            limit_req zone=api_limit burst=10 nodelay;
            
            proxy_pass http://filesystem_srv;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            
            proxy_read_timeout 86400;
            proxy_send_timeout 86400;
        }

        # Default - 404
        location / {
            return 404 '{"error": "Endpoint not found", "code": 404}';
            add_header Content-Type application/json;
        }
    }
}

Environment Variables และ Security

สร้างไฟล์ .env สำหรับ production อย่างปลอดภัย
# Database
DB_HOST=postgres
DB_PORT=5432
DB_NAME=mcp_production
DB_USER=mcp_admin
DB_PASSWORD=CHANGE_THIS_SECURE_PASSWORD_2024

HolySheep AI API (85%+ cheaper than OpenAI)

Register at: https://www.holysheep.ai/register

HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1

Server Configurations

NODE_ENV=production LOG_LEVEL=info ENABLE_METRICS=true METRICS_PORT=9090

Resource Limits

MAX_HEAP_SIZE=512m WORKER_THREADS=4

Performance Benchmark — Real Production Data

จากการ benchmark บน server 8 vCPU, 16GB RAM ผมได้ผลลัพธ์ดังนี้ เมื่อเปรียบเทียบกับ native deployment แบบไม่ containerize พบว่า overhead ของ Docker อยู่ที่ประมาณ 8-12% เท่านั้น ซึ่งคุ้มค่ากับ maintainability ที่ได้เพิ่มขึ้นมาก

Concurrency Control และ Resource Management

ใน production environment การจัดการ concurrency ที่ดีเป็นสิ่งสำคัญ ผมใช้หลายเทคนิคร่วมกัน
# Semaphore-based concurrency control in Node.js MCP Server
class ConcurrencyController {
  constructor(maxConcurrent = 50) {
    this.semaphore = new Semaphore(maxConcurrent);
    this.activeRequests = 0;
    this.queue = [];
    this.metrics = {
      total: 0,
      succeeded: 0,
      failed: 0,
      rejected: 0,
      avgWaitTime: 0
    };
  }

  async execute(task, timeout = 30000) {
    this.metrics.total++;
    const waitStart = Date.now();
    
    try {
      const result = await this.semaphore.acquire(timeout);
      this.activeRequests++;
      const waitTime = Date.now() - waitStart;
      
      this.metrics.avgWaitTime = 
        (this.metrics.avgWaitTime * (this.metrics.total - 1) + waitTime) 
        / this.metrics.total;

      try {
        const output = await Promise.race([
          task(),
          new Promise((_, reject) => 
            setTimeout(() => reject(new Error('Task timeout')), timeout)
          )
        ]);
        this.metrics.succeeded++;
        return output;
      } finally {
        this.activeRequests--;
        this.semaphore.release(result);
      }
    } catch (error) {
      this.metrics.rejected++;
      throw error;
    }
  }

  getStats() {
    return {
      ...this.metrics,
      activeRequests: this.activeRequests,
      queueLength: this.queue.length,
      utilization: this.activeRequests / this.semaphore.max
    };
  }
}

// Usage in MCP handler
const controller = new ConcurrencyController(25);

mcpServer.tool('heavy_computation', async (params) => {
  return controller.execute(async () => {
    // Your heavy computation here
    const result = await computeExpensiveOperation(params);
    return result;
  }, 60000);
});

Cost Optimization — HolySheep AI

หนึ่งใน biggest cost centers ของ AI infrastructure คือ API costs ผมย้ายจาก OpenAI มาใช้ HolySheep AI และประหยัดได้มากกว่า 85% โดย HolySheep มี rate ที่น่าสนใจมาก: