Multi-VM MCP Gateway deployment Architecture
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) 뿐입니다.
배포 순서
의존성 순서대로 올려야 헬스체크가 정상적으로 통과합니다.
- DB VM: PostgreSQL 기동 → 정상 확인
- Cache VM: Redis 기동 → 정상 확인
- MCP Pool VM: 도구 서버 기동
- Gateway VM ×3: 각각 기동 (DB/Redis에 접속 가능해야 함)
- LB VM: nginx 기동 (Gateway 3대의 사설 IP를 upstream으로 지정)
- 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
댓글남기기