8 분 소요

AI Agent와 연계해서 MCP Server들과 single-endpoint를 지원하는 mcpgateway 배포 아키텍처 구성 diagram 입니다.

Multi-VM MCP Gateway deployment Architecture

MCPGateway docker-compose file

# =========================================================================
# Gateway VM (예시 IP: ${GATEWAY_1_IP} / ${GATEWAY_2_IP} / ${GATEWAY_3_IP})
#
# 이 파일은 Gateway VM 3대에 "완전히 동일하게" 배포합니다.
# 각 VM의 자기 자신을 구분하는 값은 GATEWAY_INSTANCE_NAME 하나뿐입니다.
#
# 실행 방법 (각 Gateway VM에서):
#   cd mcpgateway-multivm
#   cp cluster.env.example .env   # 값 채우기 (3대 모두 동일한 .env)
#   cd gateway-vm
#   GATEWAY_INSTANCE_NAME=gateway-1 docker compose --env-file ../.env up -d
#   # 2번째 VM에서는 GATEWAY_INSTANCE_NAME=gateway-2 로, 3번째는 gateway-3으로
# =========================================================================
services:
  gateway:
    image: ghcr.io/ibm/mcp-context-forge:latest
    container_name: "${GATEWAY_INSTANCE_NAME:-gateway}"
    hostname: "${GATEWAY_INSTANCE_NAME:-gateway}"
    restart: unless-stopped
    # LB VM(다른 VM)에서 접근해야 하므로 호스트 포트를 반드시 열어야 함
    # (같은 호스트 안이 아니므로 docker network expose만으로는 접근 불가)
    ports:
      - "4444:4444"
    environment:
      # --- 기본 UI / API ---
      MCPGATEWAY_UI_ENABLED: "true"
      MCPGATEWAY_ADMIN_API_ENABLED: "true"
      HOST: "0.0.0.0"
      PORT: "4444"

      # --- 인증 ---
      AUTH_REQUIRED: "true"
      REQUIRE_TOKEN_EXPIRATION: "true"
      SECURE_COOKIES: "false"
      PLATFORM_ADMIN_EMAIL: "${PLATFORM_ADMIN_EMAIL}"
      PLATFORM_ADMIN_PW: "${PLATFORM_ADMIN_PW}"
      PLATFORM_ADMIN_FULL_NAME: "${PLATFORM_ADMIN_FULL_NAME}"
      JWT_SECRET_KEY: "${JWT_SECRET_KEY}"
      AUTH_ENCRYPTION_SECRET: "${AUTH_ENCRYPTION_SECRET}"

      # --- Database Layer: DB VM의 "사설 IP"로 접속 (docker network 아님) ---
      DATABASE_URL: "postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PW}@${DB_VM_IP}:5432/${POSTGRES_DB}"
      DB_POOL_SIZE: "20"
      DB_MAX_OVERFLOW: "10"
      DB_POOL_TIMEOUT: "30"
      DB_POOL_RECYCLE: "3600"

      # --- Cache Layer: Cache VM의 "사설 IP"로 접속 ---
      CACHE_TYPE: "redis"
      REDIS_URL: "redis://:${REDIS_PW}@${CACHE_VM_IP}:6379/0"
      CACHE_PREFIX: "mcpgw:"

      # --- Federation: VM이 다르더라도 "같은 DB/Redis를 공유하는 하나의 클러스터"
      # 이므로 federation은 끕니다. (완전히 별도의 원격 게이트웨이를 연동할
      # 때만 켜세요)
      FEDERATION_ENABLED: "false"
      FEDERATION_DISCOVERY: "false"
      FEDERATION_PEERS: "[]"

      # --- 외부 접속 도메인 (CSRF/CORS) ---
      APP_DOMAIN: "${APP_DOMAIN}"
      CSRF_TRUSTED_ORIGINS: '["${APP_DOMAIN}"]'
      ALLOWED_ORIGINS: '["${APP_DOMAIN}"]'
    healthcheck:
      test: ["CMD", "python3", "-c", "import urllib.request,sys; sys.exit(0) if urllib.request.urlopen('http://localhost:4444/health', timeout=3).status==200 else sys.exit(1)"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s

# -----------------------------------------------------------------------
# 배포 전 체크
# -----------------------------------------------------------------------
# 1) 이 VM에서 DB VM(${DB_VM_IP}:5432), Cache VM(${CACHE_VM_IP}:6379)로
#    telnet/nc 등으로 접속 가능한지 먼저 확인하세요 (보안그룹/방화벽 문제로
#    가장 많이 막힙니다).
#      nc -zv ${DB_VM_IP} 5432
#      nc -zv ${CACHE_VM_IP} 6379
# 2) GATEWAY_INSTANCE_NAME을 VM마다 다르게 지정하는 것을 잊지 마세요.
#    (안 주면 3대 모두 hostname이 "gateway"로 겹쳐서 로그 구분이 어려워짐 —
#     기능 자체는 동작하지만 운영/모니터링 시 불편합니다.)

NGINX docker-compose file

# =========================================================================
# LB VM (예시 IP: ${LB_VM_IP})
#
# 실행 방법:
#   cd mcpgateway-multivm
#   cp cluster.env.example .env   # 값 채우기 (GATEWAY_1/2/3_IP 필수)
#   cd lb-vm
#   docker compose --env-file ../.env up -d
#
# 참고: 이 compose는 nginx 공식 이미지의 "템플릿 자동 렌더링" 기능을 씁니다.
# /etc/nginx/templates/*.template 안의 ${VAR}가 컨테이너 시작 시 실제
# 환경변수 값으로 치환되어 /etc/nginx/conf.d/에 생성됩니다.
# 즉 nginx.conf를 직접 고칠 필요 없이 .env의 GATEWAY_*_IP만 바꾸면 됩니다.
# =========================================================================
services:
  nginx:
    image: nginx:1.27-alpine
    container_name: mcp-lb
    restart: unless-stopped
    ports:
      - "4444:4444"   # 외부(Internet)에 노출되는 유일한 포트
    environment:
      GATEWAY_1_IP: "${GATEWAY_1_IP}"
      GATEWAY_2_IP: "${GATEWAY_2_IP}"
      GATEWAY_3_IP: "${GATEWAY_3_IP}"
    volumes:
      - ./nginx/templates:/etc/nginx/templates:ro
    healthcheck:
      test: ["CMD", "wget", "-q", "-O-", "http://localhost:4444/lb-health"]
      interval: 10s
      timeout: 5s
      retries: 5

# -----------------------------------------------------------------------
# 이중화(HA)가 필요하다면
# -----------------------------------------------------------------------
# 이 compose는 nginx 단일 인스턴스입니다. LB VM이 죽으면 전체 서비스가
# 끊깁니다. 진짜 HA가 필요하면:
#   1) 클라우드 매니지드 로드밸런서 사용 (AWS ALB/NLB, GCP LB 등) — 가장
#      간단하고 권장. 이 경우 lb-vm 자체가 필요 없어지고, 매니지드 LB의
#      타겟 그룹에 Gateway VM 3대의 4444 포트를 등록하면 됩니다.
#   2) 직접 구축 시: nginx를 2대 VM에 동일하게 띄우고, keepalived로
#      VRRP 가상 IP(VIP)를 두 VM 사이에서 페일오버 시키는 구성이 일반적입니다.

# =========================================================================
# nginx가 컨테이너 시작 시 이 템플릿의 ${VAR}를 실제 환경변수 값으로
# 치환해서 /etc/nginx/conf.d/default.conf 로 만듭니다.
# (nginx:1.19+ 공식 이미지의 docker-entrypoint.d envsubst 기능 사용)
# =========================================================================
upstream mcp_gateway_cluster {
    # Gateway는 다른 VM에 있으므로 컨테이너명이 아니라 "사설 IP:포트"로 지정
    server ${GATEWAY_1_IP}:4444 max_fails=3 fail_timeout=10s;
    server ${GATEWAY_2_IP}:4444 max_fails=3 fail_timeout=10s;
    server ${GATEWAY_3_IP}:4444 max_fails=3 fail_timeout=10s;
    keepalive 64;
}

log_format mcp_lb '$remote_addr - $upstream_addr [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   'rt=$request_time uct="$upstream_connect_time" '
                   'urt="$upstream_response_time"';

server {
    listen 4444;
    server_name _;

    access_log /dev/stdout mcp_lb;
    error_log  /dev/stderr warn;

    client_max_body_size 20m;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;

    location = /lb-health {
        access_log off;
        return 200 "ok\n";
    }

    location / {
        proxy_pass http://mcp_gateway_cluster;
        proxy_http_version 1.1;

        # WebSocket / SSE (MCP 스트리밍 전송) 지원
        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_set_header X-Forwarded-Proto $scheme;

        proxy_buffering off;
    }
}

Redis Cache VM - docker-compose

# =========================================================================
# Cache VM (예시 IP: ${CACHE_VM_IP})
#
# 실행 방법:
#   cd mcpgateway-multivm
#   cp cluster.env.example .env   # 값 채우기
#   cd cache-vm
#   docker compose --env-file ../.env up -d
#
# 방화벽/보안그룹: 6379 포트는 Gateway VM 3대의 사설 IP에서만 접근 허용.
# Internet에는 절대 노출하지 마세요.
# =========================================================================
services:
  redis:
    image: redis:7-alpine
    container_name: mcp-redis
    restart: unless-stopped
    command: >
      redis-server
      --appendonly yes
      --requirepass ${REDIS_PW:-CHANGE_ME_REDIS_PW}
      --bind 0.0.0.0
      --protected-mode yes
    ports:
      # 호스트(VM)의 6379를 열어 다른 VM(Gateway)에서 접근 가능하게 함.
      # 반드시 보안그룹/방화벽으로 출발지를 Gateway VM 대역으로 제한할 것.
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PW:-CHANGE_ME_REDIS_PW}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  redis_data:

# -----------------------------------------------------------------------
# 참고: 진짜 HA가 필요하다면
# -----------------------------------------------------------------------
# 이 compose는 단일 Redis 인스턴스입니다. 이 VM이 죽으면 세션/캐시가
# 끊깁니다(Gateway 자체는 살아있어도 세션 공유가 안 됨). 진짜 HA가
# 필요하면:
#   1) 관리형 Redis (AWS ElastiCache, GCP Memorystore 등) — 권장
#   2) Redis Sentinel 구성: 별도 VM 3대에 각각 redis-server + sentinel을
#      배치하고 REDIS_URL을 "sentinel://" 형태나 클라이언트 측 Sentinel
#      디스커버리로 전환 (mcp-context-forge가 Sentinel URL을 직접 지원하는지는
#      실제 버전 문서에서 확인 필요 — 확인 안 된 부분이니 배포 전 검증하세요)

DB VM - docker-compose

# =========================================================================
# DB VM (예시 IP: ${DB_VM_IP})
#
# 실행 방법:
#   cd mcpgateway-multivm
#   cp cluster.env.example .env   # 값 채우기
#   cd db-vm
#   docker compose --env-file ../.env up -d
#
# 방화벽/보안그룹: 5432 포트는 Gateway VM 3대의 사설 IP에서만 접근 허용.
# Internet에는 절대 노출하지 마세요.
# =========================================================================
services:
  postgres:
    image: postgres:17
    container_name: mcp-postgres
    restart: unless-stopped
    # 최대 커넥션 = Gateway 인스턴스 수 x (DB_POOL_SIZE + DB_MAX_OVERFLOW) 보다
    # 여유 있게. 예: 3대 x (20+10)=90 이므로 150으로 설정 (인스턴스 늘리면 같이 조정)
    command: ["postgres", "-c", "max_connections=150", "-c", "listen_addresses=*"]
    environment:
      POSTGRES_USER: "${POSTGRES_USER}"
      POSTGRES_PW: "${POSTGRES_PW}"
      POSTGRES_DB: "${POSTGRES_DB}"
    ports:
      # 호스트(VM)의 5432를 열어 다른 VM(Gateway)에서 접근 가능하게 함.
      # 반드시 보안그룹/방화벽으로 출발지를 Gateway VM 대역으로 제한할 것.
      - "5432:5432"
    volumes:
      - pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pg_data:

# -----------------------------------------------------------------------
# 참고: 진짜 HA가 필요하다면 (VM 장애 시에도 DB 무중단)
# -----------------------------------------------------------------------
# 이 compose는 단일 PostgreSQL 인스턴스입니다. 이 VM이 죽으면 전체 클러스터가
# 멈춥니다. 진짜 HA가 필요하면 다음 중 하나를 선택하세요:
#   1) 관리형 DB (AWS RDS Multi-AZ, GCP Cloud SQL HA 등) — 가장 간단하고 권장
#   2) Patroni + PostgreSQL 스트리밍 복제로 자체 구축 (별도 VM 2~3대 필요,
#      운영 복잡도 높음)
# 자체 구축 시 최소한 별도 VM에 물리 복제(streaming replication) 스탠바이를
# 하나 더 두고, pgpool-II나 HAProxy로 자동 페일오버를 구성해야 합니다.

MCP Gateway 멀티 VM 배포 토폴로지

단일 VM(docker-compose 한 벌) 구성과 가장 큰 차이는:

  • 컨테이너 간 통신이 docker 내부 네트워크(서비스명) 대신 VM의 사설(private) IP + 방화벽/보안그룹을 통해 이뤄집니다.
  • 각 역할(LB / Gateway / DB / Cache / MCP Pool / Monitoring)을 서로 다른 VM에 분리 배치해 장애 도메인을 나눕니다.
  • VM 한 대가 죽어도 나머지가 서비스를 유지할 수 있어야 진짜 HA입니다. (단일 VM 구성에서는 VM이 죽으면 전부 죽는 것과 대조적)

VM 구성 (최소 권장, 예시 사설 IP)

VM 역할 대수 예시 사설 IP 주요 포트 사양(권장 최소)
LB (nginx) 1~2 (이중화 권장) 10.0.1.10 (, .11) 4444(외부) 2 vCPU / 2GB
Gateway 3 10.0.2.11 / .12 / .13 4444 2 vCPU / 2GB (인스턴스당)
PostgreSQL (DB) 1 (+선택적 replica) 10.0.3.10 (, .11 replica) 5432 2~4 vCPU / 4~8GB
Redis (Cache) 1 (+선택적 Sentinel 2대) 10.0.4.10 (, .11, .12) 6379(, 26379) 2 vCPU / 2GB
MCP Server Pool 1 이상 (도구 수에 따라 확장) 10.0.5.10 8001, 8002… 2 vCPU / 2GB
Monitoring (Prometheus+Grafana) 1 10.0.6.10 9090, 3000 2 vCPU / 4GB

실제 배포 시 위 IP는 여러분의 VPC/서브넷 대역에 맞게 바꾸세요. 클라우드 로드밸런서(AWS ALB, GCP LB 등)를 쓸 수 있다면 LB VM 자체를 없애고 매니지드 LB로 대체하는 것을 권장합니다 (이중화를 직접 구현할 필요가 없어짐).

네트워크/방화벽 매트릭스 (누가 누구에게 접근해야 하는가)

출발지 (Source) 목적지 (Destination) 포트 용도
Internet LB VM 4444 (또는 443) 외부 사용자/AI 클라이언트 접속
LB VM Gateway VM ×3 4444 요청 프록시 (Round Robin)
Gateway VM ×3 DB VM 5432 Server Registry (PostgreSQL)
Gateway VM ×3 Cache VM 6379 Session/Cache (Redis)
Gateway VM ×3 MCP Pool VM 8001, 8002… 등록된 MCP 도구 서버 호출
Monitoring VM Gateway VM ×3 4444 (/metrics) Prometheus 스크레이핑
Monitoring VM DB VM / Cache VM (선택) exporter 포트 DB/Redis 자체 모니터링 시
관리자 IP만 모든 VM 22 (SSH) 운영/배포
관리자 IP만 Monitoring VM 3000 (Grafana), 9090 (Prometheus) 대시보드 열람 (외부 공개 금지 권장)

원칙: DB/Redis/MCP Pool/Monitoring 포트는 Internet에 노출하지 말고, 반드시 위 표에 명시된 출발지(같은 VPC 내부의 특정 VM)에서만 접근 가능하도록 보안그룹/방화벽을 설정하세요. 외부에 노출해야 하는 것은 오직 LB VM의 4444 (또는 443) 뿐입니다.

배포 순서

의존성 순서대로 올려야 헬스체크가 정상적으로 통과합니다.

  1. DB VM: PostgreSQL 기동 → 정상 확인
  2. Cache VM: Redis 기동 → 정상 확인
  3. MCP Pool VM: 도구 서버 기동
  4. Gateway VM ×3: 각각 기동 (DB/Redis에 접속 가능해야 함)
  5. LB VM: nginx 기동 (Gateway 3대의 사설 IP를 upstream으로 지정)
  6. Monitoring VM: Prometheus(Gateway 3대 스크레이핑) → Grafana

이 저장소의 각 VM별 배포 방법

각 VM에 이 저장소 전체를 복사한 뒤, 해당 VM의 폴더 안에서만 docker compose up -d를 실행하면 됩니다. (다른 VM 폴더는 무시됩니다)

mcpgateway-multivm/
├── cluster.env.example   # 모든 VM에 공통으로 배포되는 환경변수 템플릿
├── lb-vm/                # ── LB VM에서 실행
├── gateway-vm/            # ── Gateway VM 3대에 "각각" 동일하게 실행
├── db-vm/                 # ── DB VM에서 실행
├── cache-vm/               # ── Cache VM에서 실행
├── mcp-pool-vm/            # ── MCP Pool VM에서 실행
└── monitoring-vm/          # ── Monitoring VM에서 실행
# 예: Gateway VM 1번에서
scp -r mcpgateway-multivm/ user@10.0.2.11:~/
ssh user@10.0.2.11
cd mcpgateway-multivm
cp cluster.env.example .env      # 값 채우기 (모든 VM에 동일한 .env 배포)
cd gateway-vm
docker compose up -d

env example

# =========================================================================
# cluster.env.example
#
# 모든 VM(LB / Gateway / DB / Cache / MCP Pool / Monitoring)에 동일하게
# 배포하는 공통 환경변수 파일입니다.
#
#   cp cluster.env.example .env
#
# 각 VM의 docker-compose.yml은 이 파일을 상위 폴더의 ".env"로 자동 로드합니다
# (docker compose가 실행 디렉토리 기준 상위 ".env"를 못 읽으므로, 각 VM의
#  하위 폴더(gateway-vm 등)에서 실행할 때는 --env-file ../.env 를 사용하세요.
#  아래 각 VM docker-compose.yml 상단 주석에 정확한 실행 명령을 적어두었습니다.)
# =========================================================================

# --- 클러스터 전체에서 사용하는 사설 IP (실제 VPC 대역에 맞게 수정) ---
LB_VM_IP=10.0.1.10
GATEWAY_1_IP=10.0.2.11
GATEWAY_2_IP=10.0.2.12
GATEWAY_3_IP=10.0.2.13
DB_VM_IP=10.0.3.10
CACHE_VM_IP=10.0.4.10
MCP_POOL_VM_IP=10.0.5.10
MONITORING_VM_IP=10.0.6.10

# --- 외부에서 접속할 실제 도메인/IP (CSRF/CORS 허용 origin) ---
# 예: https://mcp.example.com  또는  http://<LB_VM의_공인_IP>:4444
APP_DOMAIN=http://CHANGE_ME:4444

# --- JWT / 암호화 (openssl rand -hex 32 로 생성 후 교체) ---
JWT_SECRET_KEY=CHANGE_ME_REPLACE_WITH_OPENSSL_RAND_HEX_32
AUTH_ENCRYPTION_SECRET=CHANGE_ME_REPLACE_WITH_OPENSSL_RAND_HEX_32

# --- PostgreSQL (DB VM) ---
POSTGRES_USER=appuser01
POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD
POSTGRES_DB=mcpdb

# --- Redis (Cache VM) ---
REDIS_PASSWORD=CHANGE_ME_STRONG_REDIS_PASSWORD

# --- Platform Admin (Gateway 최초 관리자 계정) ---
PLATFORM_ADMIN_EMAIL=testuser@gmail.com
PLATFORM_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD
PLATFORM_ADMIN_FULL_NAME=Platform Administrator

# --- Grafana (Monitoring VM) ---
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=CHANGE_ME_STRONG_PASSWORD

댓글남기기