안녕하세요, 형님. 오늘 오후 오픈소스 심층 해부는 agentgateway/agentgateway입니다. 오늘(10/23) GitHub Trending Rust 일간 4위(+34)에 올라 있는, Linux Foundation 산하의 “에이전트 전용” 프록시입니다. 스타 수(5,513)만 보면 같은 날 상위권 저장소들보다 작지만, 에이전트가 LLM·MCP 도구·다른 에이전트로 보내는 트래픽을 한 데이터 평면에서 인증·인가·한도·관측하겠다는 문제 정의가 분명하고, 커밋 3천 건 이상·기여자 약 280명 규모로 굴러가는 인프라 프로젝트입니다.
이 글은 입문서가 아니라 실무자용입니다. README 요약을 넘어 설정 3형태(정적·로컬 YAML·XDS)와 공유 IR, Envoy와 다르게 잡은 XDS 리소스 설계, CEL 기반 인가가 실제로 “도구 목록을 깎는” 방식, 1.6.0의 깨지는 변경까지 짚고, 박스에서 v1.6.0 공식 바이너리로 LLM 게이트웨이·MCP 인가·MCP 멀티플렉싱·핫 리로드·지연 측정을 직접 돌린 결과를 그대로 적었습니다.

한눈에 보는 agentgateway
| 항목 | 내용 (2026-10-23 17:02 KST 기준) |
|---|---|
| 저장소 | agentgateway/agentgateway · 홈 agentgateway.dev |
| 한 줄 정의 | MCP·A2A 등 AI 네이티브 프로토콜 위에 만든 오픈소스 프록시. 에이전트→LLM, 에이전트→도구, 에이전트→에이전트 통신에 보안·관측·거버넌스를 끼워 넣음 |
| 라이선스 | Apache-2.0 |
| 거버넌스 | Solo.io가 만들어 2025-08-25 Linux Foundation에 기증(“agentgateway a Series of LF Projects, LLC”, TSC 체제) |
| 언어 | 데이터 평면 Rust(에디션 2024, 툴체인 1.99.0) · Kubernetes 컨트롤러 Go · UI TypeScript |
| 스타 / 포크 | 5,513 / 977 · 열린 이슈+PR 281 · 워처 45 |
| 규모 | 커밋 약 3,107 · 기여자 약 280(익명 포함, API 페이지 수 기준) |
| 최신 안정판 | v1.6.0 (2026-10-03 01:26 KST 공개) |
| 최신 프리릴리스 | v1.7.0-alpha.1 (2026-10-22 06:31 KST) — 릴리스 노트상 1.7.0은 “다음 주” 예정 |
| 최근 커밋 | b75bb31 “build: update Node to 24.22.1” (2026-10-23 05:54 KST) |
| 배포물 | linux/darwin/windows 바이너리(agentgateway, agctl), 컨테이너 cr.agentgateway.dev/agentgateway, Helm 차트 3종(agentgateway·crds·standalone) |
| Trending | 오늘 Rust 일간 #4 (+34 stars today, 16:57 KST 스냅샷). 전체 목록에는 없음 |
왜 오늘 트렌딩에 올라왔나
스타 증가폭은 크지 않습니다. 그래도 지금 주목할 만한 이유는 세 가지입니다. 첫째, 릴리스 리듬입니다. 10/3 v1.6.0에 이어 10/22 v1.7.0-alpha.1이 나왔고, 노트에 “1.7.0은 다음 주”라고 적혀 있습니다. 둘째, 최근 커밋이 Gemini 도구 호출 thought signature 보존, Bedrock 스트림 중간 system 메시지 처리, Perplexity 프로바이더 추가처럼 “각 사 LLM API의 미묘한 차이”를 메우는 쪽에 몰려 있습니다. 셋째, 이 저장소의 examples/openshell이 오늘 오전에 다룬 NVIDIA OpenShell의 Kubernetes 인그레스로 agentgateway를 쓰는 구성을 담고 있어, 같은 날 Rust 트렌딩에 두 프로젝트가 나란히 있는 것도 우연만은 아닙니다. 다만 이 글은 OpenShell 자체가 아니라 그 앞단 프록시를 다룹니다.
| 신호 | 관찰 내용 | 해석 |
|---|---|---|
| 릴리스 | v1.6.0(10/3) → v1.7.0-alpha.1(10/22) | 안정판 주기가 짧다. 업그레이드 노트를 꼼꼼히 읽어야 하는 프로젝트 |
| 최근 PR 성격 | Gemini·Bedrock·Anthropic 변환 버그, 모델 카탈로그 자동 갱신 봇 | “OpenAI 호환”이라는 말 뒤의 프로토콜 차이를 프록시가 흡수 |
| 예제 폭 | examples/ 44개 (llm-*, mcp-*, traffic-*, openshell 등) | LLM·MCP·일반 HTTP 게이트웨이를 한 제품으로 묶겠다는 방향 |
| 생태계 | OpenShell 인그레스 예제, Kubernetes Gateway API·Inference Gateway 확장 | 서비스 메시 출신 팀(Istio ambient의 ztunnel 경험)이 에이전트 트래픽으로 확장 |
무엇을 하는가: 세 갈래 게이트웨이
README가 내세우는 기능을 실무 관점에서 다시 묶으면 “누가 누구에게 말하느냐”로 나뉩니다. 핵심은 세 갈래를 한 바이너리, 한 정책 언어(CEL), 한 관측 체계(OpenTelemetry)로 다룬다는 점입니다.
| 역할 | 받는 트래픽 | 프록시가 하는 일 | 대표 설정 키 |
|---|---|---|---|
| LLM 게이트웨이 | OpenAI 호환 /v1/chat/completions, Anthropic Messages, Responses 등 | 모델 이름 매핑·와일드카드, 프로바이더별 포맷 변환, 백엔드 키 주입, 요청·토큰 단위 레이트 리밋, 비용 계산, 장애 조치 | llm.models[], provider, transformation |
| MCP 게이트웨이 | MCP Streamable HTTP / SSE, 업스트림은 stdio·HTTP·SSE·OpenAPI | 여러 MCP 서버를 하나로 묶기(도구 이름 접두사), JWT·OAuth 인증, 도구 단위 CEL 인가, 세션 관리 | mcp.targets[], mcpAuthorization |
| A2A 게이트웨이 | Agent2Agent 프로토콜 | 능력 탐색·태스크 협업 트래픽에 동일한 인증·관측 적용 | traffic-a2a 예제 |
| 일반 HTTP/추론 라우팅 | HTTP, gRPC, Kubernetes Inference Gateway | Gateway API 구현, GPU·KV 캐시·큐 깊이 기반 추론 라우팅(K8s 확장) | binds/listeners/routes |
저장소 구조: 무게중심은 Rust 데이터 평면
얕은 클론(최근 50커밋)으로 파일을 세어 봤습니다. 줄 수는 cat | wc -l 단순 합계라 주석·테스트를 포함합니다. 그래도 어디에 무게가 실렸는지는 분명히 보입니다.
| 경로 | 줄 수(대략) | 역할 |
|---|---|---|
| crates/agentgateway | 203,488 | 프록시 본체: 리스너·라우팅·정책·MCP·A2A·텔레메트리·설정 로딩 |
| crates/llm | 36,499 | 프로바이더 어댑터(openai, anthropic, gemini, vertex, bedrock, azure, copilot, custom)와 포맷 변환·토크나이저·모델 카탈로그 |
| crates/cel-fork · celx | 23,093 · 2,667 | CEL 인터프리터 포크와 확장 함수 |
| crates/pool · core · http · hbone · xds | 5,837 · 4,494 · 2,365 · 1,313 · 1,035 | 커넥션 풀, 공통 타입, HTTP 유틸, HBONE 터널(Istio ambient 계열), XDS 클라이언트 |
| controller/ (Go) | 96,530 | Kubernetes 컨트롤러: Gateway API·CRD를 XDS로 번역 |
| ui/ (TS) | 39,733 | 내장 관리 UI(기본 15000 포트) |
| examples/ | 44개 디렉터리 | llm-basic, mcp-authorization, mcp-multiplex, traffic-ratelimiting-local, openshell … |
| schema/ | config.md·cel.md 등 | 설정·CEL 변수 스키마 자동 생성 문서 |

아키텍처 핵심 1: 설정은 세 갈래, 런타임 표현은 하나
architecture/configuration.md가 이 프로젝트를 이해하는 열쇠입니다. 설정은 세 형태로 들어오지만 모두 같은 내부 표현(IR)으로 수렴합니다.
| 형태 | 들어오는 경로 | 바꿀 수 있는 것 | 특징 |
|---|---|---|---|
| 정적(Static) | 환경 변수 또는 YAML/JSON의 config: 블록 |
로그, 관리·통계·준비 상태 포트 등 전역값 | 프로세스 시작 시 1회. 라우팅·정책은 여기서 못 바꿈 |
| 로컬(Local) | -f config.yaml 파일 |
백엔드·라우트·정책 전부 | 파일 감시로 핫 리로드. JWKS URL 가져오기, host: https://… 한 줄로 백엔드+정책 생성 같은 편의 번역 포함 |
| XDS | 원격 제어 평면(Go 컨트롤러) | 전부 | Envoy의 xDS 전송 프로토콜만 쓰고 타입은 전용 protobuf. 파일·URL 조회 같은 편의 기능은 없음 |
Envoy와 다르게 잡은 XDS 리소스 설계
문서가 직접 Envoy와 비교합니다. Envoy에서는 리스너가 라우트 목록을 품고 있어서, 라우트 하나의 가중치를 바꿔도 delta xDS를 써도 수 MB짜리 라우트 목록 전체가 프록시마다 다시 퍼질 수 있다고 지적합니다. agentgateway는 반대로 자식이 부모를 가리키게 했습니다. HTTPRoute 규칙 하나는 agentgateway Route 하나, Pod 하나는 Workload 하나로 대응합니다. 정책도 컨트롤러가 가장 낮은 단위로 펼쳐 보내지 않고, “어디에 붙는지” 참조만 단 채 그대로 보내고 병합·우선순위는 런타임이 계산합니다.
| 설계 결정 | 얻는 것 | 치르는 비용 |
|---|---|---|
| 자식→부모 참조, 사용자 API와 1:1 대응 리소스 | 작은 변경은 작은 메시지. 사용자가 쓴 설정과 프록시 상태의 대응이 쉽게 읽힘 | 병합 로직이 데이터 평면에 들어감. 프록시 쪽 정책 해석이 더 복잡해짐 |
| Envoy 타입 대신 전용 protobuf | 에이전트 프로토콜에 맞는 모델링, 번역이 기계적 | Envoy 생태계 도구(필터·확장)를 그대로 못 씀 |
| 로컬 YAML과 XDS가 같은 IR 공유 | 단독 실행과 K8s 모드의 동작 차이를 줄임 | 로컬 모드에만 있는 편의 번역(파일·URL 조회)은 XDS에서 쓸 수 없음 |
| 확장 언어로 CEL 채택(Lua·WASM 아님) | 빠르고 샌드박스된 식 평가, 파싱은 설정 변경 시 1회 | 문서 표현대로 Lua·WASM보다 표현력이 낮음. 복잡한 로직은 ext_authz·ext_proc 같은 외부 호출로 |
아키텍처 핵심 2: CEL과 “쓰는 만큼만 내는” 컨텍스트
agentgateway는 인가 규칙, 로그·트레이스 필드, 헤더·본문 변환, 레이트 리밋 키 선택에 모두 CEL을 씁니다. 흥미로운 부분은 ContextBuilder입니다. 설정이 바뀔 때 식을 파싱하면서 어떤 변수를 참조하는지 미리 뽑아 두고, 요청 처리 중에는 그 변수만 컨텍스트에 담습니다. 예를 들어 어떤 식도 request.body를 안 쓰면 본문 복사본을 만들지 않습니다. 문서는 request["body"]처럼 쓰면 정적 추출이 놓칠 수 있다(false negative)고 솔직하게 적어 둡니다. 규칙을 쓸 때 점 표기를 쓰는 편이 안전합니다.
| CEL이 쓰이는 곳 | 예시 식 | 실무 메모 |
|---|---|---|
| MCP 도구 인가 | jwt.sub == "test-user" && mcp.tool.name == "get-sum" |
허용 목록 방식. 규칙 중 하나라도 참이면 허용 |
| 레이트 리밋 버킷 키 | jwt.sub, jwt.team, jwt.sub + "/" + llm.requestModel |
키를 평가할 수 없는 요청은 하나의 공유 버킷으로 모임(스키마 문서 명시) |
| 로그·트레이스 속성 | request.headers["user-agent"] |
필요한 필드만 남기므로 개인정보 로그 최소화에도 유리 |
| LLM 모델 변환 | llmRequest.model.stripPrefix("anthropic/") |
와일드카드 모델(anthropic/*)을 실제 모델명으로 |
1.6.0에서 깨지는 것들 (업그레이드 전 필독)
릴리스 노트가 5만 자가 넘습니다. 운영자가 놓치면 장애로 이어질 항목만 추렸습니다.
| 변경 | 내용 | 대응 |
|---|---|---|
| Anthropic Messages 변환 우선순위 | 1.5는 Chat Completions 우선, 1.6은 OpenAI Responses 우선. Responses 변환은 extended-thinking 이력을 버림 | OpenAI 프로바이더는 OpenAI API에만. 다른 호환 서버는 custom 프로바이더로 지원 포맷만 광고. 임시 우회 AGENTGATEWAY_MESSAGES_PREFER_COMPLETIONS=true(1.7에서 제거 예정) |
| 내장 모델 카탈로그 가격 | 주요 공개 모델 요청에 기본 비용이 붙음 | USD 예산·llm.cost 식이 업그레이드 직후부터 걸릴 수 있음. 예산값 재검토 |
| 경로 없는 base URL | https://api.openai.com은 이제 /를 그대로 씀(1.5는 /v1 자동 추가 등) |
모든 base URL에 /v1 등 경로를 명시 |
| 단독 모드 LLM 경로 정확 일치 | /tenant-a/v1/messages 같은 접두 경로는 변환 없이 통과 |
llm.pathPrefix 설정 |
| 스트리밍 가드레일 | 스트림 검사 중 가드 실패 시 콘텐츠 거부 | 1.5 동작을 원하면 failureMode: failOpen |
| YAML 해석 | 따옴표 없는 no가 문자열로 남음 |
불리언은 true/false로 |
| 타임아웃 기본값 | gRPC ext_authz 2초, 레이트 리밋·ext_proc 10초 | 외부 정책 서버 지연 SLO와 맞춰 확인 |
설치·실행 경로
| 방법 | 명령/아티팩트 | 언제 |
|---|---|---|
| 단일 바이너리 | GitHub Releases agentgateway-linux-amd64 + .sha256 |
로컬 개발, VM, 사이드카 실험. 오늘 스모크에 사용 |
| 컨테이너 | cr.agentgateway.dev/agentgateway:v1.6.0 |
Docker·Compose 기반 단독 배포 |
| Helm (단독) | cr.agentgateway.dev/charts/agentgateway-standalone |
K8s 위에 로컬 YAML 모드로 |
| Helm (컨트롤러) | charts/agentgateway + agentgateway-crds |
Gateway API·AgentgatewayModel CRD로 운영 |
| 소스 빌드 | cargo run -- -f examples/…/config.yaml |
기여·디버깅. rust-toolchain 1.99.0 고정 |
# 박스에서 실제로 실행한 설치 (v1.6.0, x86_64 musl)
curl -sSL -O https://github.com/agentgateway/agentgateway/releases/download/v1.6.0/agentgateway-linux-amd64
curl -sSL -O https://github.com/agentgateway/agentgateway/releases/download/v1.6.0/agentgateway-linux-amd64.sha256
sha256sum agentgateway-linux-amd64 # 496ed27e…f079d, sidecar와 일치
chmod +x agentgateway-linux-amd64
./agentgateway-linux-amd64 --version
# "version": "1.6.0", "git_revision": "ea56086…", "build_target": "x86_64-unknown-linux-musl",
# "crypto_backend": "aws-lc-rs", "crypto_fips": false
./agentgateway-linux-amd64 -f llm.yaml --validate-only # Configuration is valid!
박스 실측 1: LLM 게이트웨이 (목 백엔드)
실제 API 키 없이 검증하려고, 받은 요청의 모델명·Authorization 헤더 앞부분만 기록하는 파이썬 OpenAI 호환 목 서버를 127.0.0.1에 띄우고 그 앞에 agentgateway를 세웠습니다. 설정은 아래와 같습니다. 클라이언트는 일부러 Authorization: Bearer client-fake-token을 보냈습니다.
config:
adminAddr: 127.0.0.1:15000
readinessAddr: 127.0.0.1:19001
statsAddr: 127.0.0.1:19002
llm:
port: 4000
policies:
localRateLimit:
- type: requests
maxTokens: 5
tokensPerFill: 5
fillInterval: 60s
key: request.headers["x-user"] # 사용자 헤더별 버킷
models:
- name: smart # 별칭 → 실제 모델
provider: { custom: { formats: [ { type: completions } ] } }
params:
baseUrl: http://127.0.0.1:8099/v1
model: mock-large
apiKey: sk-backend-only-secret # 백엔드 키는 게이트웨이에만
- name: mock/* # 와일드카드 + 접두사 제거
provider: { custom: { formats: [ { type: completions } ] } }
params: { baseUrl: http://127.0.0.1:8099/v1, apiKey: sk-backend-only-secret }
transformation:
model: llmRequest.model.stripPrefix("mock/")
| 시나리오 | 관찰 결과 | 의미 |
|---|---|---|
model: smart |
HTTP 200, 백엔드가 받은 모델 mock-large |
별칭 매핑 동작 |
model: mock/qwen-mini |
HTTP 200, 백엔드가 받은 모델 qwen-mini |
와일드카드 + CEL stripPrefix 변환 |
model: gpt-unknown |
HTTP 404 model_not_found (백엔드 호출 없음) |
등록되지 않은 모델은 프록시에서 차단 |
| 클라이언트 Authorization | 백엔드 로그의 Authorization은 Bearer sk-backend-on…(설정한 백엔드 키) |
클라이언트 토큰은 업스트림으로 새지 않고, 진짜 키는 게이트웨이에만 |
| x-user: bob 연속 7회 | 1~5회 200, 6·7회 429 rate limit exceeded |
키 단위 요청 버킷 동작 |
| x-user: carol (bob 차단 직후) | 200 | 버킷은 키별로 분리 |
| x-user 헤더 없음 6회 | 5회 200, 6번째 429 | 키를 못 구한 요청은 하나의 공유 버킷(문서와 일치) — 익명 트래픽을 따로 막으려면 strict 인증과 함께 써야 함 |
| 통계 포트 /metrics | agentgateway_gen_ai_client_token_usage 히스토그램(입력 12·출력 3 토큰 × 요청 수) |
OTel GenAI 시맨틱 이름으로 토큰 사용량이 바로 나옴 |
| 접근 로그 | gen_ai.request.model=mock-large gen_ai.usage.input_tokens=12 … duration=43ms |
요청 단위 비용·토큰 추적이 로그 한 줄에 |
x-user 헤더가 그대로 전달되어 있었습니다. 레이트 리밋 키로 쓴 내부 헤더가 업스트림 LLM 사업자에게까지 갈 수 있다는 뜻입니다. 민감한 식별자를 키로 쓴다면 헤더 제거 변환을 함께 거는 편이 좋습니다.박스 실측 2: MCP 도구 단위 인가 (JWT + CEL)
저장소의 examples/mcp-authorization을 거의 그대로 썼습니다. 업스트림은 @modelcontextprotocol/server-everything 2.0.0(Streamable HTTP, 3001 포트)이고, JWT 공개키·예제 토큰은 저장소 manifests/jwt/에 들어 있는 데모용입니다. 규칙은 세 줄입니다: 누구나 echo, sub == "test-user"만 get-sum, nested.key == "value" 클레임이 있으면 get-env. MCP 클라이언트는 파이썬 표준 라이브러리로 initialize → tools/list → tools/call을 직접 보냈고, “위조 토큰”은 같은 kid·ES256으로 제가 새로 만든 키로 서명했습니다.

| 토큰 | initialize | tools/list 결과 | echo | get-sum | get-env |
|---|---|---|---|---|---|
| 없음 (기본 mode) | 200 | [echo] |
200 | 400 Unknown tool | 400 Unknown tool |
| example2 (sub=test-user) | 200 | [echo, get-sum] |
200 | 200 “The sum of 2 and 3 is 5.” | 400 Unknown tool |
| example1 (sub + nested.key) | 200 | [echo, get-env, get-sum] |
200 | 200 | 200 (서버 환경 변수 반환) |
| 위조 서명(ES256, 다른 키) | 401 InvalidSignature |
— | — | — | — |
없음 (mode: strict) |
401 no bearer token found |
— | — | — | — |
여기서 실무적으로 중요한 사실이 두 가지 나왔습니다.
- jwtAuth의 기본 모드는
optional입니다. 소스(crates/agentgateway/src/http/jwt.rs)의Modeenum에 “This is the default option. Warning: this allows requests without a JWT.”라고 적혀 있고, 실제로 토큰 없이도 세션이 열려echo를 호출할 수 있었습니다. 예제 설정을 그대로 프로덕션에 옮기면 “인증이 걸린 줄 알았는데 익명도 들어오는” 상태가 됩니다.mode: strict를 명시하자 토큰 없는 요청이 401로 막혔습니다. 세 번째 값permissive는 잘못된 토큰까지 통과시키니 디버깅 외에는 쓰지 않는 게 맞습니다. - 거부된 도구는 목록에서 사라지고, 호출하면 “Unknown tool”이 돌아옵니다. 403이 아니라 “그런 도구는 없다”는 응답이라 권한 없는 사용자에게 도구 존재 자체를 숨깁니다. 에이전트는 보이는 도구로만 계획을 세우니 프롬프트 인젝션으로 숨은 도구를 부르게 만드는 공격 면도 줄어듭니다. 덧붙여
get-env는 실제로 업스트림 서버 프로세스의 환경 변수를 통째로 돌려줬습니다(내용은 싣지 않습니다). “도구 단위 인가”가 왜 필요한지 보여 주는 예입니다.
박스 실측 3: MCP 멀티플렉싱과 핫 리로드
examples/mcp-multiplex를 응용해 stdio 업스트림(uvx --with "mcp<2" mcp-server-time)과 HTTP 업스트림(위 everything 서버)을 한 백엔드로 묶었습니다. 예제 주석에 따르면 mcp-server-time이 MCP SDK 2.x에서 McpError→MCPError 이름 변경으로 import에 실패하고, 그러면 멀티플렉스 백엔드 전체의 initialize가 실패하기 때문에 SDK를 2 미만으로 묶어 둡니다. 업스트림 하나의 실패가 묶음 전체를 넘어뜨린다는 점은 운영 시 기억할 만합니다.
| 항목 | 결과 |
|---|---|
| initialize serverInfo | {name: agentgateway, version: 1.6.0} — 클라이언트는 게이트웨이를 하나의 MCP 서버로 봄 |
| tools/list | 15개 = time 2개 + everything 13개, 이름은 타깃명_도구명(예: time_get_current_time, everything_get-sum) |
time_get_current_time {timezone: Asia/Seoul} |
200, 2026-10-23T17:01:47+09:00, 9.8 ms(클라이언트 측) |
everything_get-sum {40, 2} |
200, “The sum of 40 and 2 is 42.”, 2.8 ms |
| 접근 로그 | mcp.method.name=tools/call mcp.target=time gen_ai.tool.name=get_current_time mcp.session.id=… |
| 핫 리로드 | 실행 중인 인가 게이트웨이의 YAML에서 echo 규칙을 mcp.tool.name == "echo" && has(jwt.sub)로 바꾸자 로그에 loaded config from File가 찍히고, 3초 안에 토큰 없는 세션의 tools/list가 []로 바뀜(재시작 없음) |
| 메모리 | 게이트웨이 프로세스 5개 각각 RSS 약 35~40 MB(바이너리 크기 97.7 MB, musl 정적 빌드) |
박스 실측 4: 프록시 한 홉의 지연
파이썬 http.client로 연결을 재사용하며 같은 요청을 순차 500회(워밍업 20회 제외) 보냈습니다. 동시성·처리량 벤치가 아니라 한 홉이 더하는 지연만 보는 측정이고, 목 백엔드가 즉시 응답하므로 실제 LLM 호출(수백 ms~수 초)에 비하면 오차 범위 수준이라는 점을 먼저 밝힙니다.

| 경로 | p50 (ms) | p90 (ms) | p99 (ms) | 평균 (ms) |
|---|---|---|---|---|
| 목 서버 직접(응답 1회 write) | 0.122 | 0.145 | 0.585 | 0.135 |
| agentgateway 경유(같은 목 서버) | 0.262 | 0.418 | 0.634 | 0.294 |
| 목 서버 직접(헤더·본문 분할 write) | 44.011 | 47.817 | 48.190 | 44.437 |
BaseHTTPRequestHandler로 헤더와 본문을 따로 씀)를 게이트웨이 뒤에 두자 요청마다 약 43~48 ms가 나와 “프록시가 느리다”고 오해할 뻔했습니다. 그런데 같은 목 서버를 직접 호출해도 44 ms였습니다. 응답을 한 번에 쓰도록 바꾸자 직접 0.12 ms, 경유 0.26 ms로 내려갔습니다. 작은 응답을 여러 번 나눠 쓰는 서버와 연결 재사용 클라이언트 사이의 TCP 지연(ACK 지연과 Nagle 알고리즘의 상호작용으로 추정)이지 게이트웨이 탓이 아니었습니다. 게이트웨이를 벤치할 때는 직접 호출 기준선을 꼭 같이 재야 합니다.비교: 비슷한 자리를 노리는 도구들
아래는 각 프로젝트의 공식 설명을 기준으로 한 위치 비교이며, 성능 비교가 아닙니다. 같은 조건의 벤치는 돌리지 않았습니다.
| 도구 | 주 무대 | agentgateway와의 차이 |
|---|---|---|
| agentgateway | LLM + MCP + A2A + 일반 HTTP, 단독/K8s | 에이전트 프로토콜을 1급으로 다루는 Rust 데이터 평면, CEL 인가, LF 거버넌스 |
| LiteLLM (BerriAI/litellm) | LLM API 통합 SDK·프록시(100+ 프로바이더) | LLM 호출 통합·예산 관리에 집중. 저장소 설명상 Rust 코어 + Python SDK. MCP 도구 단위 인가·A2A는 agentgateway 쪽이 더 중심 |
| Envoy AI Gateway | Envoy Proxy 기반 LLM 트래픽 | Envoy 필터 생태계를 그대로 쓰는 대신 Envoy 리소스 모델을 따름. agentgateway는 이 모델의 fan-out을 피하려 전용 XDS 타입을 택함 |
| Kong·기존 API 게이트웨이의 AI 플러그인 | 기존 API 관리 위에 AI 기능 추가 | 이미 Kong을 쓰는 조직엔 도입 비용이 낮음. 에이전트 세션(MCP) 의미론은 플러그인 수준 |
| 토큰 압축 프록시 (Headroom, caveman) | 컨텍스트·토큰 절감 | 목적이 다름(비용·컨텍스트 절감). agentgateway는 인증·인가·한도·관측. 앞뒤로 같이 둘 수 있음 |
| NVIDIA OpenShell | 에이전트 샌드박스·이그레스 정책 | 실행 격리 쪽. agentgateway는 저장소 예제에서 OpenShell의 K8s 인그레스(Gateway API 구현)로 쓰임 — 경쟁보다 보완 |
보안·운영 체크리스트
| 항목 | 권장 | 근거 |
|---|---|---|
| JWT 모드 | 프로덕션은 mode: strict 명시 |
기본 optional — 박스에서 토큰 없이 세션·echo 호출 확인 |
| 인가 규칙 | 허용 목록으로 도구별 작성, 점 표기 사용 | CEL 변수 정적 추출이 request["body"]류를 놓칠 수 있음 |
| 레이트 리밋 키 | 키 없는 요청이 공유 버킷으로 가는 걸 전제로 설계 | 박스에서 헤더 없는 요청 5회 후 공동 429 |
| 업스트림 헤더 | 내부 식별 헤더는 제거 변환 | x-user 헤더가 백엔드까지 전달됨을 확인 |
| 관리 UI·통계 포트 | 127.0.0.1 바인딩 또는 off |
설정 스키마가 adminAddr·statsAddr에 off 지원 |
| 예제 키 | manifests/jwt의 키·토큰은 데모 전용, 절대 재사용 금지 |
저장소에 공개된 공개키·예제 토큰 |
| 멀티플렉스 | 업스트림 MCP 서버 버전 고정 | 하나의 타깃 실패가 묶음 전체 initialize 실패로 |
| 업그레이드 | 1.6 breaking changes 표의 base URL·예산·변환 항목부터 점검 | 릴리스 노트 |
| FIPS | 필요하면 FIPS 빌드 여부 확인 | 공식 v1.6.0 바이너리는 crypto_fips: false |
무엇을 돌리지 않았나 (한계)
| 항목 | 상태 | 이유 |
|---|---|---|
| 실제 LLM 프로바이더(OpenAI·Anthropic·Gemini 등) | 미실행 | API 키 미사용. 포맷 변환 정확도는 목 서버로 검증 불가 |
| Kubernetes 컨트롤러·Helm·Gateway API | 미실행 | 박스에 클러스터 없음 |
| A2A, OAuth, 가드레일, 비용 예산 | 미실행 | 외부 서비스·키 필요 |
| 동시성 처리량·장시간 부하 | 미실행 | 측정은 순차 단일 연결 지연만 |
| v1.7.0-alpha.1 | 미실행 | 안정판 v1.6.0으로만 측정 |
누가 지금 써 볼 만한가
사내에 MCP 서버가 여러 개 생기기 시작했고, “누가 어떤 도구를 불렀는지”를 남기고 사람·팀별로 막아야 하는 조직이라면 가장 먼저 검토할 만합니다. 오늘 실측처럼 단일 바이너리 + YAML 한 장으로 MCP 묶기, 도구 단위 인가, LLM 키 은닉, 키별 한도, OTel 지표까지 바로 나옵니다. 반대로 LLM 호출 통합·비용 대시보드만 필요하다면 기존 LLM 프록시로도 충분할 수 있고, 이미 Envoy·Kong에 깊이 투자했다면 그쪽 AI 확장이 운영 부담이 적을 수 있습니다. 어느 쪽이든 기본값(optional JWT, 공유 버킷, 헤더 전달)을 그대로 믿지 말고 위 체크리스트를 먼저 적용하시길 권합니다.

형님, 오늘 오후 심층 해부는 여기까지입니다. 저녁 슬롯도 겹치지 않는 주제로 준비하겠습니다.