Rust로 AI 게이트웨이 확장하기: “정책만으론 부족할 때” 커스텀 변환으로 파이프라인을 장악하는 법

도입부: 인증/레이트리밋/라우팅 같은 기본 정책만으로는 LLM 트래픽의 “현실적인 요구(도메인 로직, 바디 변환, DB 기반 처리)”를 다 담기 어렵습니다. 이 글에서는 agentgateway/kgateway에서 **Rust 커스텀 변환(custom transformation)**으로 AI 게이트웨이를 확장하는 관점과, 그때 따라오는 운영/보안 트레이드오프를 정리합니다.


왜 “AI 게이트웨이 확장”이 필요해졌을까?

AI 게이트웨이는 보통 다음을 기본 제공 기능(정책)으로 내세웁니다.

  • 인증(Authentication), 레이트리밋(Rate limiting), 라우팅(Routing)
  • 프롬프트 가드(Prompt guard), PII 마스킹, 모델 라우팅, 토큰/비용 정책
  • 스트리밍 응답 처리, 관측(로그/메트릭/트레이싱)

그런데 실무에서는 금방 “정책만으로는 못 푸는 요구”가 튀어나옵니다. 예를 들면:

  • DB 조회 기반 헤더 추가: tenant_id로 과금 플랜을 조회해 X-Plan-Tier 헤더를 붙이기
  • 특수한 바디 변환: 특정 모델 벤더 포맷(OpenAI/Anthropic 등) 사이를 변환하거나, tool-calling 결과를 사내 규격 JSON으로 변환하기
  • 도메인 고유 로직: 내부 정책(민감도 등급, 금칙어 사전, 고객별 guardrail)을 요청/응답에 반영하기

이때 선택지는 두 가지입니다.

  1. 게이트웨이 밖(업스트림 서비스)에서 해결한다
  2. 게이트웨이 안(확장/필터/변환 단계)에서 해결한다

이번 주제는 2번입니다. agentgateway/kgateway는 Rust로 커스텀 변환을 작성해 요청/응답 파이프라인에 끼워 넣는 방식을 소개합니다.


전통적 API 게이트웨이(KrakenD 등)와 AI 게이트웨이의 결이 다른 지점

저는 “AI 게이트웨이 vs 기존 API 게이트웨이”의 차이는 기능 목록보다 제어 단위가 어디냐에 있다고 봅니다.

  • 전통적 API 게이트웨이(KrakenD 등)

    • HTTP API aggregation/변형/캐시/인증/레이트리밋 등 “일반 트래픽 최적화”가 중심입니다.
    • 바디 변환은 가능하더라도, 보통 LLM 특화 제어가 1급(First-class) 기능인 경우는 드뭅니다.
  • AI 게이트웨이(agentgateway/kgateway 계열)

    • “프롬프트/응답”을 다루는 LLM 특화 제어(모델 라우팅, 토큰/비용, 프롬프트 가드, 스트리밍, tool-calling 관측)가 중심입니다.
    • 그리고 중요한 차이: 확장을 어디까지 깊게 허용하느냐가 설계 철학을 가릅니다. Rust/WASM 같은 형태로 요청/응답 파이프라인 깊숙이 로직을 넣을 수 있으면 유연성은 커지지만, 운영/보안 책임도 같이 커집니다.

정리하면, “HTTP 레벨 최적화가 주된가” vs “LLM 레벨 정책/관측/가드가 주된가”가 선택의 출발점이 됩니다.


Rust 커스텀 변환(custom transformation)이 주는 실전 가치

agentgateway/kgateway의 핵심 메시지는 명확합니다.

기본 정책으로 안 되는 건 커스텀 변환으로 해결하자.
그리고 그 변환은 Rust로 작성해 성능/안정성을 가져가자.

Rust를 선택하는 이유는 보통 다음 기대치가 큽니다.

  • 성능: 게이트웨이는 핫패스(hot path)라서 변환 비용이 곧 지연/비용으로 이어집니다.
  • 안정성: 메모리 안전성은 큰 장점이지만(버그 표면 감소), “보안 격리”와는 별개의 문제라는 점은 뒤에서 다룹니다.
  • 배포 가능한 아티팩트: 플러그인/모듈 형태로 관리하며 정책처럼 조합할 수 있습니다.

어떤 변환을 넣게 될까? (요청/응답 예시)

아래는 “이런 종류의 변환을 게이트웨이에 넣는다”를 보여주는 의사코드 수준 예시입니다.

1) DB 조회 기반 헤더 추가(요청 변환)

// PSEUDO CODE: request transformation
fn transform_request(mut req: Request) -> Result<Request> {
    let tenant = req.headers().get("X-Tenant-Id")?;
    let plan = db_lookup_plan(tenant)?;            // external lookup
    req.headers_mut().insert("X-Plan-Tier", plan); // enrich header
    Ok(req)
}
  • 장점: 업스트림 서비스가 “이미 정제된 헤더/컨텍스트”를 받으니 단순해집니다.
  • 주의: 게이트웨이가 DB에 붙는 순간, 타임아웃/캐시/서킷브레이커가 없으면 게이트웨이가 병목이 되기 쉽습니다.

2) 벤더별 포맷 변환(바디 변환)

// PSEUDO CODE: request body transformation
fn map_openai_to_vendor(mut body: Json) -> Json {
    // messages[] -> input_text, tool schema mapping, etc.
    transform(body)
}
  • 장점: 클라이언트는 하나의 표준 스키마만 쓰고, 게이트웨이가 백엔드 벤더별 차이를 흡수합니다.
  • 주의: 스트리밍 응답이면 변환이 더 복잡해지고, 백프레셔(backpressure) 처리까지 고려해야 합니다.

3) 응답 마스킹/감사 로깅(응답 변환)

// PSEUDO CODE: response transformation
fn transform_response(mut resp: Response) -> Response {
    resp.body = mask_pii(resp.body);
    audit_log(resp); // be careful not to log secrets
    resp
}
  • 장점: “정책 + 변환” 조합으로 가드레일을 강제할 수 있습니다.
  • 주의: 무엇을 로그로 남기는지(프롬프트 원문, API 키 등) 자체가 보안 사고로 이어질 수 있습니다.

유연성 vs 운영 복잡도: 균형은 어디서 잡을까?

원본 자료의 열린 질문이 핵심을 찌릅니다.

게이트웨이에 비즈니스 로직을 확장으로 넣을 때, 유연성 향상과 운영 복잡도(배포·디버깅·보안) 사이의 균형을 어떻게 잡아야 할까요?

제가 운영 관점에서 정리하는 기준은 “게이트웨이에 넣어도 되는 로직”을 명확히 나누는 것입니다.

1) 게이트웨이에 넣기 좋은 로직: “정책에 가까운 결정”

  • 인증/인가에 필요한 컨텍스트 주입(단, 최소 정보만)
  • 표준화된 스키마 변환, 벤더 차이 흡수
  • PII 마스킹/프롬프트 가드처럼 가드레일
  • 테넌트별 라우팅/쿼터처럼 트래픽 제어

이들은 공통 횡단 관심사(cross-cutting concern)에 가깝고, 중앙에서 강제할수록 유리합니다.

2) 게이트웨이에 넣기 위험한 로직: “업무 도메인의 핵심 프로세스”

  • 긴 트랜잭션/복잡한 DB 조인
  • 장애 시 재시도/보상 트랜잭션이 필요한 로직
  • 여러 내부 시스템을 조합하는 오케스트레이션

이런 것까지 게이트웨이에 올리면, 게이트웨이가 “단순 프록시”가 아니라 핵심 애플리케이션 런타임이 되어버립니다. 배포/디버깅/장애 대응의 부담이 급격히 커집니다.


(중요) “Rust 네이티브 확장”과 “격리(샌드박싱)”는 다른 문제입니다

대화에서 제가 강조했던 부분을 다시 정리하겠습니다.

  • Rust는 메모리 안전성 측면에서 강점이 있지만,
  • 같은 프로세스에서 네이티브 플러그인으로 실행되는 순간 권한은 공유되기 쉽습니다.
  • 질문에서 요구한 수준이 “최악의 확장 코드가 네트워크로 데이터 유출하는 것까지 막는 수준”이라면,
    • 네이티브 플러그인은 기본적으로 불리합니다.
    • 현실적인 출발점은 WASM 같은 capability 기반 샌드박스 + 기본 egress 차단 또는 확장을 별도 프로세스로 외부화하는 방식입니다.

또 하나의 함정이 있습니다. “확장 자체의 네트워크를 막았다”로 끝나지 않습니다.

  • 확장이 게이트웨이의 업스트림 요청에 민감정보를 슬쩍 끼워 넣는 간접 유출도 가능합니다.
  • 그래서 egress 차단과 함께,
    • 확장에 전달되는 입력(헤더/바디)의 최소화
    • 필드 레벨 마스킹(예: 토큰/키 비노출)
    • 테넌트별 CPU/메모리/시간 제한과 결정적 타임아웃
      까지 같이 설계해야 합니다.

제가 추천하는 실무 가이드라인(체크리스트)

  1. 확장 책임 경계 정하기

    • “정책/표준화/가드레일”까지만 게이트웨이에서 처리하고, 도메인 코어 로직은 업스트림으로 둡니다.
  2. 실행 모델 선택하기

    • 신뢰할 수 없는(또는 팀별/테넌트별) 코드를 허용한다면: 네이티브보다 WASM/외부화가 기본값입니다.
  3. 데이터 최소화

    • 확장에 API 키/토큰/PII/프롬프트 원문이 꼭 필요하지 않다면 애초에 전달하지 않습니다.
  4. 운영 가능성(Observability & Rollback) 확보

    • 변환 모듈 버전 관리, 단계적 롤아웃, 즉시 롤백, 변환 실패 시 폴백 전략(차단/패스스루)을 준비합니다.

마무리

AI 게이트웨이는 “정책만 잘 쓰면 끝”이 아니라, 결국 우리 조직의 요구를 파이프라인에 녹이는 확장 전략이 성패를 가릅니다. Rust 커스텀 변환은 유연성과 성능을 주지만, 동시에 배포/디버깅/보안(특히 격리)까지 책임져야 하니, “무엇을 게이트웨이에 넣을지” 경계를 먼저 정하고 실행 모델(WASM/외부화/네이티브)을 선택하는 것이 안전합니다.


참고 자료

1 Like