MCP 심층 분석, 파트 13: Azure에서 실제 규모로 MCP 호스팅하기 — Container Apps
요약
Model Context Protocol(MCP) 서버를 Azure Container Apps를 사용하여 프로덕션 환경에 배포하고 확장하는 방법을 다룹니다. 서버리스 컨테이너, KEDA 기반의 동시성 오토스케일링, 그리고 안정적인 인프라 구성을 위한 아키텍처를 설명합니다.
핵심 포인트
- Azure Container Apps를 활용한 서버리스 MCP 호스팅
- KEDA를 이용한 동시성(concurrency) 기반 오토스케일링 구현
- 콜드 스타트 방지를 위한 최소 복제본(minReplicas) 설정 권장
- 대규모 요청 처리를 위한 게이트웨이 및 인프라 토폴로지 구성
로컬 MCP 서버는 dotnet run 명령어로 실행됩니다. 하지만 프로덕션(Production) 환경의 서버는 상태가 없는(stateless) HTTP 서비스이면서 동시에 장기 실행 스트림(long-lived streams)을 유지해야 하고, 초당 3,200개의 요청이 몰려도 견뎌야 하며, 연결을 끊지 않고 배포(deploy)할 수 있어야 하고, 연결 문자열(connection string)을 절대 유출해서는 안 됩니다. 이것은 인프라의 문제이며, Azure에서는 이에 대한 구체적이고 — 대체로 즐거운 — 해답이 있습니다. 다만, 딱 한 번 당신을 물어뜯을 주의사항(gotcha)이 하나 있습니다.
이 글은 Model Context Protocol (MCP)에 대한 15부작 심층 분석 중 13번째 파트입니다. 파트 12에서는 .NET으로 서버를 구축했습니다. 이제 이를 실행할 차례입니다. 세 개의 서버 — mattrx-analytics, mattrx-reports, mattrx-admin — 는 게이트웨이 뒤에 있는 Azure Container Apps에서 구동됩니다.
요약 (TL;DR)
| 고려 사항 | 단순한 호스팅 (이전) | Azure 대규모 환경 (이후) |
|---|---|---|
| 컴퓨팅 (Compute) | 고정 VM / 항상 켜져 있는 App Service | Container Apps (서버리스, 리비전) |
| ... |
Azure 토폴로지 (Topology)
Agents / clients
|
[ Azure Front Door / APIM ] <- org MCP gateway
...
1. 컴퓨팅 (Compute): Azure Container Apps
각 MCP 서버는 Azure Container App입니다. 이는 공유 환경 내에서 내장된 인그레스(ingress), 리비전(revisions), 그리고 KEDA 오토스케일링(autoscaling)을 갖춘 서버리스 컨테이너입니다.
resource analytics 'Microsoft.App/containerApps@2024-03-01' = {
name: 'mattrx-analytics'
identity: { type: 'SystemAssigned' } // 관리 ID (section 6)
...
세 개의 서버, 세 개의 앱, 하나의 환경: mattrx-analytics는 읽기 부하(read load)에 따라 확장되고, mattrx-reports는 거의 확장되지 않으며(단순히 큐에 삽입함), mattrx-admin은 아주 작은 규모로 유지됩니다. 하나의 공유 플랜으로는 이 세 가지를 결코 균형 있게 맞출 수 없습니다.
2. KEDA를 이용한 오토스케일링 (Autoscaling)
**KEDA HTTP 스케일 규칙 (scale rule)**은 동시성(concurrency)에 따라 복제본(replicas)을 추가하며, 최소 유지 수준(warm floor)과 최대 한도(hard ceiling)를 설정할 수 있습니다. 또한 비운영(non-prod) 환경을 위해 스케일 투 제로(scale-to-zero) 기능도 제공합니다.
scale: {
minReplicas: 2 // 워밍업 상태 유지; AOT (파트 12) 덕분에 콜드 스타트(cold starts)조차 빠름
maxReplicas: 30
...
MCP 부하는 급증(bursty)하는 특성이 있습니다. 캠페인이 정시에 종료되거나 에이전트들이 확산(fan out)되는 식입니다. 따라서 CPU가 아닌 **동시성 (concurrency)**을 기준으로 스케일링하십시오. 급증 시 첫 번째 요청이 콜드 스타트(cold start)가 되지 않도록 최소한의 워밍업 상태를 유지하고, 제어되지 않는 에이전트가 예상치 못한 비용 청구로 이어지지 않도록 maxReplicas를 제한하십시오. 개발 환경에서는 유휴 상태일 때 비용이 발생하지 않도록 minReplicas: 0으로 설정하십시오. 분석 서버는 밤 동안 2개의 레플리카(replica)를 유지하다가, 초당 요청 수(rps)가 약 3,200에 달하는 피크 타임에는 약 30개까지 확장되며, 전체 기간 동안 read-tool의 p95 성능을 120 ms로 유지합니다.
3. 전면에 배치하는 게이트웨이
조직 게이트웨이 — Azure Front Door 또는 API Management (APIM) — 는 단일한 공용 및 관리형 에지(edge)로서 환경의 전면에 위치합니다.
Agents ---> [ Azure Front Door / APIM ] (조직 MCP 게이트웨이)
- TLS 종단 (termination)
- Entra 토큰 사전 검증 (pre-validation)
...
게이트웨이가 모든 것을 전면에서 처리하기 때문에, 서버들은 **내부 인그레스 (internal ingress)**를 유지하며 동일한 상태를 유지합니다. 즉, 어떤 서버도 에지(edge) 기능을 재구현하지 않습니다. 에지에서의 속도 제한(Rate-limits)은 제어되지 않는 에이전트가 오토스케일링(autoscaling)을 비용 급증으로 변질시키는 것을 방지하는 역할을 합니다.
4. SSE 주의사항 — 로드 밸런서 뒤에서의 스트리밍
스트리밍 도구가 로컬 호스트(localhost)에서는 완벽하게 작동하고 Azure로 배포되었음에도, 운영 환경에서 "무작위로" 작동을 멈추는 경우가 있습니다. 스트리밍 도중 연결이 끊기거나 스트림이 상태를 잃어버리는 현상입니다.
ingress: {
external: false
targetPort: 8080
...
이것이 Azure MCP 사용 시 겪게 되는 첫 번째 놀라운 문제입니다. SSE(Server-Sent Events)는 연결을 열어둔 채 이벤트를 간헐적으로 전송하는데, Container Apps의 인그레스(ingress)와 Front Door는 이를 유휴(idle) 상태로 간주하여 회수(reap)하거나, 스트리밍 도중 세션을 보유하지 않은 다른 레플리카로 로드 밸런싱을 해버립니다. 이는 로컬에서는 절대 발생하지 않고 오직 Azure에서만 발생하기 때문에 운영 환경에서 기습적으로 문제를 일으킵니다. 해결책은 세 가지 설정입니다: 인그레스 유휴 제한 시간(idle timeout)을 늘리고, 상태 유지 스트림(stateful streams)을 위해 세션 어피니티(session affinity)를 켜고, 서버에서 킵얼라이브 핑(keepalive-ping)을 보내는 것입니다. 이 중 하나라도 놓치면 스트리밍이 간헐적으로 끊어집니다.
5. 리비전을 통한 무중단 배포
Container Apps의 리비전 (revisions) 기능을 통해 블루-그린(blue-green) 및 카나리(canary) 배포가 가능합니다. 새로운 리비전은 트래픽을 받기 전에 반드시 준비 상태 프로브(readiness probe)를 통과해야 하며, 트래픽 비중을 점진적으로 전환할 수 있습니다.
새로운 리비전(revision) 배포; 카나리(canary) 10% 적용, 모니터링 후 100%로 전환 — 호출 중단 없음.
az containerapp update -n mattrx-analytics --image mattrxacr.azurecr.io/mcp-analytics:2.5.0
...
// 준비 상태(Readiness)가 트래픽을 제어합니다: 상태가 건강하지 않은(unhealthy) 리비전은 절대 요청을 받지 않습니다 (/readyz).
probes: [ { type: 'Readiness', httpGet: { path: '/readyz', port: 8080 } } ]
새로운 리비전이 부팅되고 /readyz를 통과해야 하며, 그 후 10%의 카나리(canary) 배포를 진행하고 100%로 전환하기 전에 도구별 메트릭(metrics)을 모니터링합니다. 기존 리비전은 트래픽이 모두 빠져나갈 때까지 진행 중인 호출(in-flight calls)과 스트림(streams)을 계속 서비스합니다 — 재시작도, 작업 중단도 없습니다.
6. Secretless 보안 — 관리 ID (managed identity), Key Vault, 프라이빗 (private)
**관리 ID (managed identity)**는 Entra, Azure SQL, Service Bus에 인증하므로 연결 문자열(connection strings)이 필요 없습니다. 남은 모든 비밀 정보(secrets)는 참조를 통해 Key Vault에서 가져오며, 인그레스(ingress)는 내부(internal)로 설정됩니다.
identity: { type: 'SystemAssigned' } // -> Entra, Azure SQL, Service Bus, 비밀 정보 없음
secrets: [ { name: 'sb-conn', keyVaultUrl: kv.properties.vaultUri, identity: 'system' } ]
// Azure SQL은 프라이빗 엔드포인트 (PRIVATE ENDPOINT)를 통해 접속; 인그레스 external:false -> 게이트웨이만 접속 가능.
서버는 관리 ID (managed identity)로 인증하므로 유출될 연결 문자열이 없습니다. 반드시 존재해야 하는 비밀 정보는 참조를 통해 Key Vault에서 가져오며, Azure SQL은 프라이빗 엔드포인트 (private endpoint) 뒤에 위치하고, 내부 인그레스 (internal ingress)를 통해 외부에서는 게이트웨이만 접속할 수 있습니다. 자격 증명 회전(credential rotation)은 사용자가 아닌 Azure의 역할이 됩니다.
지속해야 할 모델
Azure 상의 MCP 서버는 장기 실행 스트림 (long-lived streams)을 보유하는 상태가 없는 (stateless) HTTP 서비스입니다 — 이 두 가지 역할을 모두 수행하도록 호스팅하십시오. Container Apps는 서버별 탄력적 확장(elastic scale), 무중단 배포를 위한 리비전(revisions), 그리고 내부 인그레스 (internal ingress)를 제공합니다. Front Door / APIM 게이트웨이는 통제된 단일 공용 엣지 (public edge)를 제공하며, 관리 ID (managed identity)는 비밀 정보가 없는 (secretless) 보안을 제공합니다. 그리고 여러분을 놀라게 할 단 한 가지는 로드 밸런서(load balancer) 뒤의 SSE입니다 — 어피니티 (affinity), Keepalive, 타임아웃 (timeouts)을 설정하면 문제는 해결됩니다.
Azure에서 MCP를 호스팅하기 위한 세 가지 습관:
- 동시성 (concurrency) 기준으로 스케일링하고, 운영 환경은 항상 활성화 상태를 유지하세요. KEDA HTTP 규칙을 사용하여 최소 실행 상태(warm floor)를 유지하고, 스케일 투 제로 (scale-to-zero)는 개발 환경에서만 사용하세요.
- 전면에 하나의 게이트웨이를 두고, 서버는 내부망에 두세요. 단일 공용, 속도 제한 (rate-limited), 비밀번호가 필요 없는 (secretless) 에지 (edge)를 구축하여, 그 외의 어떤 것도 서버에 도달하지 못하게 하세요.
- 스트리밍 (streaming)은 로컬호스트 (localhost)가 아닌 Azure에서 검증하세요. SSE 설정은 실제 로드 밸런서 (load balancer) 뒤에서만 실패하므로, 반드시 그곳에서 테스트해야 합니다.
원문은 prepstack.co.in에 게시되었습니다. 파트 14에서는 .NET과 Azure를 넘어, MCP를 OpenAI 및 기타 에이전트 프레임워크 (agent frameworks)에 연결하는 방법을 다룹니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기