AWS 기술 블로그
Amazon Bedrock에서 LLM 게이트웨이의 두 사각지대 메우기: 사라진 호출자와 흐려진 모델 거버넌스
Amazon Bedrock 앞에 LLM 게이트웨이(이하 게이트웨이)를 두면 편의와 통제를 얻지만, 그 대가로 Amazon Bedrock이 보는 호출자 신원과 모델별 관측 지점이 게이트웨이 뒤로 흐려지는 사각지대가 생깁니다. 이 글은 잘 알려진 레퍼런스 아키텍처를 출발점으로, 그 사각지대를 Amazon Bedrock 네이티브 기능으로 보완하는 방법 (호출자 감사 추적과 모델별 관측 및 거버넌스)을 다룹니다.

<Claude Code → LLM 게이트웨이 → Amazon Bedrock>
들어가며
개발 조직이 Claude Code를 Amazon Bedrock 위에서 도입할 때, 규모가 커질수록 반드시 마주하는 질문이 있습니다. “이 모델 호출은 누가, 어느 팀이, 얼마를 썼는가?”
이 질문에 답하기 위해서는 개별 개발자가 각자의 IAM 자격증명으로 Amazon Bedrock을 직접 호출하는 대신, 중앙에 게이트웨이를 두는 것이 정석입니다. Claude Code 공식 문서도 LiteLLM 기반 게이트웨이를 안내하고 있으며, 이 아키텍처의 훌륭한 출발점으로 AWS 기술 블로그의 “Amazon Bedrock 기반 Claude Code, 조직에서 안전하게 운영하기: LLM Gateway 구축 가이드”와 그 레퍼런스 구현인 aws-samples 블루프린트가 이미 공개되어 있습니다. 이 자료들에는 IAM Identity Center, Virtual Key 자동 발급, Amazon Bedrock, ECS Fargate, Aurora를 이용한 게이트웨이 구현 방법이 잘 정리되어 있습니다.
그런데 이런 게이트웨이를 도입하는 순간, Amazon Bedrock 관점에서 사각지대가 생깁니다. 모든 개발자의 요청이 게이트웨이의 단일 IAM 역할로 Amazon Bedrock에 도달하기 때문에, Amazon Bedrock이 보는 호출자는 언제나 “게이트웨이에 부여된 IAM 역할” 하나뿐입니다. 개별 사용자 및 팀의 신원이 게이트웨이 뒤로 사라지는 것입니다. 이 상태에서는 다음 두 가지 엔터프라이즈 필수 요건이 충족되지 않습니다.
- 호출자 감사 추적: Amazon Bedrock의 Model Invocation Logging에 남는 모든 호출 주체가 게이트웨이 역할로 뭉뚱그려져, “이 호출을 실제로 발생시킨 사용자 및 팀”을 Amazon Bedrock 네이티브 로그만으로는 특정할 수 없습니다.
- 모델별 관측 및 거버넌스: 여러 Claude 모델을 동시에 운영하면서 모델별로 사용량 및 성능을 관측하고 접근을 통제해야 하는데, 게이트웨이가 모델ID를 직접 호출하면 이 “모델”축의 관측 지점과 권한 경계가 흐려집니다.
이 글에서는 “Amazon Bedrock 네이티브 차원의 호출자 추적과 모델별 관측 및 거버넌스“를 확보하기 위해 추가로 설계하게 되는 지점들을 다룹니다. 관통하는 설계 원칙은 하나입니다. 게이트웨이는 가볍게 유지하고, Amazon Bedrock이 네이티브로 제공하는 것은 Amazon Bedrock에 맡깁니다. 이 원칙에 따라 세 부분으로 나눠서 살펴봅니다.
- 호출자 축 (누가, 어느 팀이): Bedrock Model Invocation Log에 최종 호출자 (사용자, 팀, 키)와 호출 식별자를 포함시켜, 게이트웨이 뒤로 사라진 신원을 Amazon Bedrock 로그에서 복원하는 방법
- 모델 축 (어느 모델을): Bedrock Application Inference Profile로 모델별 CloudWatch 관측성과 IAM 접근 거버넌스를 확보하는 방법, 그리고 이를 위해 반드시 넘어야 하는 게이트웨이에서의 pass-through 로깅 이슈 해결
- 통제와 운영: Amazon Bedrock만으로는 구현하기 어려워 게이트웨이가 직접 맡는 개발자 단위 예산 강제와 조직 그룹 동기화, 그리고 그 게이트웨이를 안전하고 가볍게 유지하는 네트워크 구성과 운영 노하우
| 구분 | 호출자 축
누가, 어느 팀이 호출했는가 |
모델 축
어느 모델을 얼마나, 누가 쓸 수 있는가 |
| 문제 | 게이트웨이 역할 하나로 뭉뚱그려져 사용자 및 팀을 특정할 수 없다. | 프로파일 ARN은 불투명해 모델별 사용량 및 접근 통제가 어렵다. |
| 사용 기능 | X-Amzn-Bedrock-Request-Metadata 헤더로 호출자를 실어 보낸다. |
모델별로 Application Inference Profile을 생성 및 태그 부착. |
| 기록 위치 | Model Invocation Log의 requestMetadata 필드. |
CloudWatch 프로파일 단위 메트릭.
IAM 권한을 프로파일 ARN으로 한정. |
| 얻는 것 | Amazon Bedrock 로그 단독으로 보안 및 감사 조사.
litellm_call_id로 게이트웨이 로그와 대사 가능. |
모델별 호출, 토큰, 스로틀 관측.
승인된 모델로 접근 제한. |
<Amazon Bedrock 거버넌스의 2개 축: 호출자 축과 모델 축>
1. 아키텍처 개요
전체 흐름은 레퍼런스 아키텍처의 계보를 따릅니다. 개발자는 aws sso login 한 번으로 IAM Identity Center에 인증하고, Claude Code의 apiKeyHelper가 Token Service (API Gateway + Lambda)를 호출해 Virtual Key를 자동 발급받습니다. 이후 모든 요청은 ALB → LiteLLM (ECS Fargate) → Amazon Bedrock 경로로 흐릅니다.

<Claude Code on Amazon Bedrock 엔터프라이즈 게이트웨이 아키텍처>
아래는 요청 흐름을 간략화한 것입니다.

Amazon Bedrock 관점에서 이 아키텍처가 특별히 신경 쓰는 지점은 마지막 두 계층입니다. LiteLLM이 Amazon Bedrock을 어떻게 호출하는가 (Application Inference Profile 경유)와, 그 호출에 어떤 컨텍스트를 실어 보내는가 (호출자 식별을 위한 메타데이터)가 이 글의 2, 3절 주제입니다.
Claude Code는 Anthropic 네이티브 메시지 형식으로 요청을 보내고, LiteLLM의 Amazon Bedrock pass-through는 이 요청을 형식 변환 없이 그대로 Amazon Bedrock InvokeModel로 전달합니다. 덕분에 prompt caching, extended thinking 등 Claude의 최신 기능이 변환 계층에 묶이지 않고 그대로 동작하며, 게이트웨이는 인증, 예산, 감사라는 제어 역할에 집중할 수 있습니다. 다만 “그대로 전달”이라는 pass-through의 성격이 2, 3절에서 각각 하나씩의 추가 설계를 요구합니다.
2. Amazon Bedrock 감사 추적: 게이트웨이 뒤로 사라진 호출자를 복원하기
2.1 문제: Model Invocation Log에는 게이트웨이만 보인다
Amazon Bedrock의 Model Invocation Logging은 모든 모델 호출의 입출력과 메타데이터를 CloudWatch Logs 또는 S3에 기록하는, 감사와 사용량 분석의 1차 소스입니다. 그런데 게이트웨이를 경유하는 구조에서는 이 로그에 남는 호출 주체가 전부 게이트웨이의 ECS 태스크 역할 하나로 동일합니다. Amazon Bedrock은 요청을 보낸 것이 게이트웨이라는 사실만 알 뿐, 그 뒤에서 실제로 요청한 개발자나 팀을 알지 못합니다.


< requestMetadata 주입 전: 모든 호출이 게이트웨이 역할로만 기록된다.>
물론 게이트웨이(LiteLLM)는 자신의 DB에 Virtual Key 단위 사용량을 별도로 기록합니다. 하지만 그것은 게이트웨이의 장부일 뿐, Amazon Bedrock 네이티브 로그와의 연결고리가 없습니다. 보안팀이 Bedrock Model Invocation Log를 기준으로 “이 민감한 프롬프트를 호출한 사람이 누구인가”를 조사하거나, 재무팀이 Amazon Bedrock 사용량을 팀별로 대사 (reconcile)하려 할 때, 두 로그를 이어 줄 공통 키가 없기 때문에 시간 및 사용된 토큰 수 등을 이용하여 대략적으로 대사할 수밖에 없는 문제가 발생합니다.
2.2 해법: requestMetadata로 호출자와 호출 ID를 Amazon Bedrock에 주입하여 연결고리 만들기
Amazon Bedrock은 이 문제를 위한 네이티브 메커니즘을 제공합니다. 모델 호출 시 X-Amzn-Bedrock-Request-Metadata 헤더로 키-값 메타데이터를 함께 보내면, 그 값이 Model Invocation Log의 requestMetadata 필드에 그대로 기록됩니다. 즉 게이트웨이가 이미 알고 있는 호출자 정보를 이 헤더에 실어 보내면, Amazon Bedrock 네이티브 로그만으로도 호출자를 복원할 수 있습니다.
게이트웨이 관점에서 필요한 작업은 “Amazon Bedrock으로 나가는 요청에 서명하기 직전, 이 요청을 유발한 Virtual Key / 사용자 / 팀과 호출 식별자를 헤더에 부착”하는 것입니다. LiteLLM은 요청마다 이 정보를 내부적으로 이미 들고 있으므로 (인증 단계에서 Virtual Key를 사용자 및 팀에 매핑했기 때문에), 그것을 Amazon Bedrock 네이티브 메타데이터로 옮겨 주기만 하면 됩니다.
구체적으로는 pass-through 요청에 SigV4 서명을 붙이는 지점을 감싸면 됩니다. LiteLLM 최신 버전 (v1.94.0) 기준으로 그 지점은 litellm/llms/bedrock/passthrough/transformation.py의 BedrockPassthroughConfig.sign_request 메서드입니다. 컨테이너 시작 시 이 메서드를 원본 호출 전에 헤더를 덧붙이는 래퍼로 교체합니다.
# litellm/llms/bedrock/passthrough/transformation.py 코드 발췌
def _rm_sign (self, headers, litellm_params, request_data, api_base, model=None):
md = (litellm_params or {}).get('metadata') or {}
rm = {}
if litellm_params.get('litellm_call_id'):
rm['litellm_call_id'] = str(litellm_params['litellm_call_id'])
if md.get('user_api_key_alias'): rm['key_alias'] = str(md['user_api_key_alias'])
if md.get('user_api_key_user_id'): rm['user_id'] = str(md['user_api_key_user_id'])
if md.get('user_api_key_team_alias'): rm['team_alias'] = str(md['user_api_key_team_alias'])
if rm:
headers = dict(headers or {})
headers['X-Amzn-Bedrock-Request-Metadata'] = json.dumps(rm)
return _RM_ORIG(self, headers, litellm_params, request_data, api_base, model)
<패치 예시. 컨테이너 시작 시 litellm/llms/bedrock/passthrough/transformation.py 끝에 덧붙이는 래퍼>
이 한 번의 조치로 게이트웨이와 Bedrock Invocation Log가 연결됩니다.
- Amazon Bedrock 네이티브 로그 단독으로 호출자 추적이 가능해집니다. Model Invocation Log의 requestMetadata에 user_id, team_alias, key_alias가 남으므로, 보안 및 감사 조사를 Amazon Bedrock 로그만으로 수행할 수 있습니다.
- 게이트웨이 로그와 Amazon Bedrock 로그를 요청 단위로 조인할 수 있습니다. 양쪽에 동일한 litellm_call_id가 기록되므로, “게이트웨이가 집계한 비용”과 “Amazon Bedrock이 기록한 실제 호출”을 1:1로 대사할 수 있는 연결고리가 만들어집니다.

< requestMetadata 주입 경로: 게이트웨이가 호출자를 Amazon Bedrock 로그에 실어 보냅니다>


< requestMetadata 주입 후에는, Amazon Bedrock과 LiteLLM 양쪽에 동일한 litellm_call_id가 저장되므로 대사가 가능해집니다>
그렇다면 LiteLLM의 SpendLog는 계속 쌓아야 할까요?
호출자가 Bedrock Invocation Log에 명시된다면, LiteLLM의 SpendLog (요청별 상세 로그)를 정말 계속 쌓아야 하는지 다시 생각해볼 만합니다. 요청별 토큰 수, 사용 금액을 LiteLLM에서 조회해야 한다면 SpendLog 기록이 필요합니다. 그러나 감사 목적의 프롬프트 저장이 목적이라면, 그 역할은 Bedrock Invocation Log가 이미 모두 커버합니다. 입출력 본문과 호출자 메타데이터가 모두 거기에 남기 때문입니다. 이 경우 SpendLog 기록을 비활성화해도 감사는 가능합니다. 그리고 SpendLog 기록만 비활성화해도 LiteLLM의 요청당 DB 쓰기가 사라져 처리 속도와 사용 리소스 (특히 Aurora 부하)가 현저히 줄어듭니다. 무엇을 어디에 남기는 것이 좋을지는 6절에서 이어서 다룹니다.
3. Amazon Bedrock 모델 관측성과 거버넌스: Application Inference Profile
2절에서 “누가, 어느 팀이” 호출했는지를 Amazon Bedrock 로그에 복원했습니다. 이 절은 그와는 별개의 축인 “어느 모델을”을 다룹니다. 엔터프라이즈에서 여러 Claude 모델 (Fable, Opus, Sonnet, Haiku)을 동시에 운영하면, 모델별로 사용량과 성능을 관측하고 접근을 통제할 필요가 생깁니다. Amazon Bedrock에서 이를 가능하게 해주는 리소스가 바로 Application Inference Profile입니다.
두 축의 역할 분담
개인 및 팀 단위의 사용량 추적은 2절 (requestMetadata + LiteLLM 집계)이 전담하고, 이 절의 Application Inference Profile은 오직 “모델” 단위만 다룹니다. 게이트웨이는 모델ID로 직접 호출할 수도 있지만, Application Inference Profile을 경유하면 1) 모델별 관측성과 2) 모델 접근 거버넌스를 확보할 수 있습니다.
3.1 모델별 운영 관측성: Application Inference Profile 단위 CloudWatch 메트릭
Application Inference Profile을 경유해 호출하면, 모델별로 CloudWatch 사용량 메트릭이 분리됩니다. Amazon Bedrock 공식 문서 (Inference Profiles)에서는 아래와 같이 명시하고 있습니다.
사용량 지표 추적 (Track usage metrics): CloudWatch 로그를 설정하고, 애플리케이션 추론 프로파일을 사용하여 모델 호출 요청을 제출하면 모델 호출에 대한 사용량 지표를 수집할 수 있습니다.
애플리케이션 추론 프로파일 (Application inference profiles): 사용자가 비용 및 모델 사용량을 추적하기 위해 직접 생성하는 추론 프로파일입니다.
이 관측성은 본질적으로 모델 단위입니다. 프로파일을 경유하면 CloudWatch 메트릭이 우리가 만든 프로파일 단위로 쌓입니다. “Opus 5가 이번 주 몇 번, 몇 토큰 호출됐고, 스로틀은 얼마나 났는가”를 프로파일별로 보면, 용량 계획 (프로비저닝된 처리량 도입 판단), 효율적인 토큰 사용을 위한 모델 사용 패턴 최적화 (무거운 작업만 Opus, 나머지는 Sonnet / Haiku로 유도), 스로틀 대응 같은 운영 판단을 데이터로 내릴 수 있습니다.
3.2 모델 접근 거버넌스: IAM 최소 권한을 프로파일로 한정
Application Inference Profile을 경유하는 설계는 접근 통제와도 맞물립니다. 게이트웨이가 구동되는 ECS 태스크 역할의 bedrock:InvokeModel 권한을 우리가 생성한 Application Inference Profile ARN과 그 기반 모델로 한정하면, 게이트웨이는 승인된 모델들만 호출할 수 있습니다. “어느 모델을 호출할 수 있는가”를 리소스 수준에서 강제할 수 있게 되는 것입니다.
3.3 구현: 모델별 Application Inference Profile을 CDK로 생성
모델당 하나씩, System-defined Inference Profile을 복사 (copyFrom)하여 생성합니다. 각 프로파일에는 표준 리소스 태그를 부착합니다.
const profileDefs = [
{ id: 'Opus48', systemProfileId: MODELS.OPUS_4_8, suffix: 'opus-4-8' },
{ id: 'Sonnet5', systemProfileId: MODELS.SONNET_5, suffix: 'sonnet-5' },
// ... Opus 4.7 / 4.6, Sonnet 4.6, Haiku 4.5
];
for (const def of profileDefs) {
new bedrock.CfnApplicationInferenceProfile(this, def.id, {
inferenceProfileName: `${PROJECT_NAME}-${def.suffix}`,
modelSource: {
copyFrom: `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${def.systemProfileId}`,
},
tags: [
{ key: 'Project', value: PROJECT_NAME },
{ key: 'Model', value: def.suffix },
],
});
}
<예시. 모델별 Application Inference Profile을 생성하는 CDK 코드>
참고: 리소스 태그 표준화
프로파일은 태그를 붙일 수 있는 리소스이므로, 부착한 태그를 통해 Amazon Bedrock 사용량이 조직의 리소스 태그 규칙과 비용 보고 체계 (Cost Explorer, 비용 및 사용 보고서)에 자연스럽게 편입됩니다. 계정 내 다른 AWS 리소스와 동일한 태그 규칙을 Amazon Bedrock 사용량에도 적용할 수 있는 것으로, 온디맨드 파운데이션 모델을 직접 호출할 때는 얻을 수 없는 이점입니다. (공용 파운데이션 모델은 태그를 걸 대상 리소스가 없습니다.)
LiteLLM은 이 프로파일 ARN을 라우팅 대상으로 사용합니다. 이는 소스 수정이 아니라 LiteLLM 표준 설정 (config.yaml의 model_list)이며, 버전에 무관하게 동일합니다. 프로파일 ARN으로 라우팅하되, 비용 계산 정확도를 위해 model_info.base_model을 함께 지정하는 것이 중요합니다.
# config.yaml을 생성하는 코드 (컨테이너 시작 시 실행)
{
'model_name': 'global.anthropic.claude-opus-4-8',
'litellm_params': {
'model': 'bedrock/' + os.environ['INFERENCE_PROFILE_ARN_OPUS_4_8'], # 프로파일 ARN
},
'model_info': {
'base_model': 'bedrock/global.anthropic.claude-opus-4-8', # 정확한 단가 매핑용
},
}
<예시. config.yaml을 생성하는 코드 (컨테이너 시작 시 실행)>
3.4 문제: pass-through로 프로파일 ARN을 호출하면 사용량 로깅이 깨진다
Application Inference Profile을 경유하도록 설계하면 모델별 관측성과 거버넌스를 얻지만, Bedrock pass-through로 Application Inference Profile ARN을 호출할 때 LiteLLM의 사용량 로깅이 동작하지 않는 문제를 만나게 됩니다. LiteLLM의 로깅 로직이 Amazon Bedrock의 리소스 형태 (Application Inference Profile ARN)를 제대로 인지하지 못해서 발생하는 이슈입니다.
증상. 모델 호출 자체는 200 OK로 정상 응답합니다. 그러나 스트리밍 응답이 끝난 뒤 백그라운드에서 도는 사용량 로깅 태스크가 다음과 같이 실패합니다.

<Application Inference Profile을 사용하면 LiteLLM에서 provider 추론 실패로 사용량 로깅이 깨지게 됩니다>
원인. 스트리밍 응답은 작은 조각 (청크) 단위로 도착하는데, 조각의 형식이 모델 provider마다 다릅니다. LiteLLM은 응답이 끝난 뒤 이 조각들을 해석해 토큰 사용량을 집계하므로, 어떤 형식으로 해석할지 정하기 위해 먼저 모델 문자열에서 provider를 추론해야 합니다. 그 추론 로직이 litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py의 get_bedrock_invoke_provider인데, 핵심 부분은 다음과 같습니다. (최신 버전 v1.94.0 기준)
# from BerriAI/litellm (MIT License)
# litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py
# ①,②,③ 주석은 필자 주
@staticmethod
def get_bedrock_invoke_provider(model: str) -> Optional[...]:
if model.startswith("invoke/"):
model = model.replace("invoke/", "", 1)
if "nova" in model.lower():
...
# ① 첫 토큰이 arn:… 이라 provider 목록에 없어서
_split_model = model.split(".")[0]
if _split_model in get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL):
return cast(..., _split_model)
# ② 'provider/모델' 패턴 검사 → Application Inference Profile ARN엔 해당 없음
provider = AmazonInvokeConfig._get_provider_from_model_path(model)
if provider is not None:
return provider
# ③ provider 이름이 문자열에 들어있는지 검사
# → 프로파일 ARN은 불투명 ID(예: e03jrya3kchm)뿐이라 매칭 실패
for provider in get_args(litellm.BEDROCK_INVOKE_PROVIDERS_LITERAL):
if provider in model:
return provider
return None # ← 프로파일 ARN은 여기로 떨어져 None 반환
<참고. litellm/llms/bedrock/chat/invoke_transformations/base_invoke_transformation.py>
System-defined Inference Profile ARN (예: arn:aws:bedrock:<region>:<account-id>:inference-profile/global.anthropic.claude-opus-5)에는 anthropic 같은 provider 이름이 들어 있어 매칭되지만, Application Inference Profile ARN (예: arn:aws:bedrock:<region>:<account-id>:application-inference-profile/e03jrya3kchm)은 provider를 알 수 없는 ID만 담고 있어 ①, ②, ③에 모두 걸리지 않아 None이 반환됩니다. 호출부는 이 None을 받아 ValueError를 던집니다. 요청은 이미 성공 (200 OK)했는데 사후 로깅만 실패하므로, 성공 콜백 (S3, 관측 도구 등)이 호출되지 않고 실패 콜백으로 빠지게 됩니다. 이는 대시보드에서 사용량이 비어 보이는 형태로 드러납니다.
이는 LiteLLM 하나의 이슈로만 볼 문제가 아니라, Application Inference Profile ARN을 라우팅 대상으로 쓰는 다른 게이트웨이에서도 만날 수 있는 문제입니다. 프로파일 경유의 이점을 얻으려면 이 로깅 이슈를 함께 해결해야 합니다.
3.5 해법: 프로파일 ARN에 provider를 명시적으로 지정하기
위 provider 추론이 프로파일 ARN에서 None으로 떨어질 때, 이 게이트웨이가 다루는 모델이 전부 Claude (anthropic)라는 사실을 이용해 provider를 명시적으로 지정합니다. 이 글에서는 컨테이너 시작 시점에 라이브러리 소스를 멱등하게 (idempotent) 확장하는 방식으로 적용했습니다. 이미지를 포크하지 않고 버전을 고정한 채 특정 동작만 덧붙일 수 있어, 업그레이드 시 제거 및 검증이 쉽기 때문입니다.
if invoke_provider is None and 'application-inference-profile' in model:
invoke_provider = 'anthropic'
<예시. 호출부인 passthrough/transformation.py에서 get_bedrock_invoke_provider 호출 직후, None 검증 전에 삽입>
이로써 Application Inference Profile을 이용하여 호출해도 스트리밍 사용량 로깅이 정상 동작하여, 3.1 ~ 3.3에서 확보한 모델별 관측성 및 거버넌스와 사용량 집계가 함께 완성됩니다.
위 방법은 게이트웨이가 Anthropic 모델만 서비스하는 구성에서 가장 간단한 해결책입니다. 다른 provider의 프로파일을 함께 운영한다면 이 지정을 그대로 쓰면 안 됩니다. 그 호출도 anthropic으로 판정되어 잘못된 형식으로 해석하게 되고, 예외로 드러나는 대신 사용량이 잘못 기록됩니다. 이 경우 아래 업스트림 수정을 기다리거나(그때까지는 패치를 적용하지 않아 로깅을 포기), Application Inference Profile ARN과 provider의 매핑을 기록해 두고 조회하는 로직을 별도로 구성해야 합니다.
LiteLLM 업스트림 진행 상황
이 동작은 LiteLLM 커뮤니티에서 리포트되어, 이슈 #28105와 수정 PR #28130으로 추적되고 있습니다. (블로그 포스팅 날짜 기준으로 아직 반영되지 않았습니다). 업스트림 수정은 provider가 None일 때 원래 모델명 (global.anthropic.claude-…)에서 provider를 다시 추론하고, 그래도 안 되면 예외 대신 경고 로그로 graceful 처리하는 접근입니다. 반영되어 릴리스에 포함되면 위 패치는 제거하고 표준 동작으로 전환할 수 있습니다. 특정 버전 내부 구현에 의존하는 소스 확장은 이렇게 업스트림 트래킹과 함께, 제거를 전제로 운용하는 것이 안전합니다.
4. Amazon Bedrock 사용 통제: 예산 강제와 자동 온보딩
2절과 3절은 추적과 관측을 Amazon Bedrock 네이티브 기능에 위임했습니다. 반대로 이 절의 두 가지는 Amazon Bedrock만으로는 구현하기 어려워 게이트웨이를 이용하는 것이 좋습니다. Amazon Bedrock에는 사전 설정된 예산을 초과 사용할 경우 개발자 단위로 실시간 차단하는 네이티브 기능이 없고 (AWS Budgets는 계정 및 태그 수준의 알림 중심입니다), 회사의 조직 체계를 게이트웨이의 팀, 예산 구조로 동기화하는 것도 게이트웨이의 몫입니다.
4.1 Virtual Key 레벨 예산으로 Amazon Bedrock 사용 한도 강제
각 Virtual Key에 예산 한도 (max_budget)와 리셋 주기 (budget_duration)를 부여하여, 사용자 단위로 Amazon Bedrock 사용 한도를 강제합니다. 예산 강제 지점을 키 레벨에 두면 pass-through 경로에서도 초과 시 즉시 차단됩니다. Token Service가 키를 발급할 때 아래와 같이 설정합니다.
# Virtual Key 발급 시 키 레벨 예산 부여
body = {
"key_alias": f"sso-{username}",
"user_id": username,
"team_id": team_id,
"max_budget": 1000.0, # 키 레벨 예산 (예시값 — 조직 정책에 맞춰 조정)
"budget_duration": "30d",
"metadata": {"sso_arn": user_arn, "account": account, "team_id": team_id},
}
# LiteLLM 마스터 키로 LiteLLM에 키 생성 요청 시 위 body를 함께 전달
resp = _post(f"{LITELLM_URL}/key/generate", body, master_key)
<예시. Virtual Key 발급 시 키 레벨 예산도 함께 강제하는 코드>
팀 단위 통제가 필요하면 팀 예산을 추가로 얹어 다중 예산 구조 (키 + 팀)를 구현할 수 있습니다. 예산은 게이트웨이가 계산한 비용의 누적이므로, 3.3의 base_model 지정이 없으면 예산 소진 속도가 실제 Amazon Bedrock 청구와 어긋납니다.
4.2 IAM Identity Center 그룹 → 팀 자동 매핑
관리자가 게이트웨이에서 팀을 만들거나 키를 발급하는 수작업 없이, 회사의 IdP와 IAM Identity Center 연결만으로도 팀, 예산, 키가 자동 매핑됩니다. Token Service가 첫 인증 시 사용자의 Identity Center 그룹을 조회해 LiteLLM 팀으로 매핑합니다.
# IAM Identity Center 그룹을 LiteLLM 팀과 자동 매핑
user_id = _find_identity_store_user(username) # Identity Store 조회
groups = _get_user_groups(user_id) # 소속 그룹 조회
team_id = groups[0] if groups else DEFAULT_TEAM # 그룹 → 팀 (없으면 default)
_ensure_user_exists(master_key, username) # 멱등: 있으면 skip
_ensure_team_exists(master_key, team_id)
_ensure_team_member(master_key, team_id, username)
virtual_key = _create_virtual_key(...) # 4.1의 키 레벨 예산 포함 발급
<예시. IAM Identity Center 그룹을 LiteLLM 팀과 자동 매핑>
사용자, 팀 생성은 모두 “있으면 skip”으로 동작하며 반복 호출에 안전하고, 발급된 키는 DynamoDB에 캐시해 이후 로그인을 빠르게 처리합니다. 퇴사 시에는 Virtual Key 삭제 (기존 키 즉시 차단) → IAM Identity Center 비활성화 (재발급 차단) → DynamoDB 캐시 삭제 (발급 캐시 정리)의 다층 방어로 접근을 통제합니다. 팀 이동은 키를 삭제한 후 재발급받으면 새 그룹 매핑이 반영됩니다. 이 매핑 덕분에 팀별 사용량 추적 (2절)과 예산 통제 (4.1)가 조직도와 자동으로 동기화됩니다.
5. 네트워크 및 보안 아키텍처
4절까지가 게이트웨이의 기능적인 내용이었다면, 이 절부터는 그 게이트웨이 자체를 지키는 내용입니다. Amazon Bedrock으로 향하는 트래픽을 안전하게 보호하기 위한 네트워크 및 보안 요소들입니다.
- VPC Interface Endpoint를 통한 Bedrock Runtime 호출: LiteLLM → Amazon Bedrock 트래픽이 인터넷을 경유하지 않고 AWS 백본망으로만 통신합니다. 보안과 성능 (지연, NAT 비용) 모두에 유리하며, Amazon S3 / DynamoDB는 무료인 Gateway Endpoint를 사용하여 비용을 절감할 수 있습니다.
- 3-tier 서브넷 격리: ALB는 퍼블릭 서브넷, LiteLLM (Amazon ECS)과 VPC Endpoint는 프라이빗 서브넷, Aurora와 인증 캐시는 격리된 서브넷 (Isolated)에 배치합니다. 인터넷 노출이 최소화되어 공격 표면이 줄어듭니다.
- 보안 그룹 체인: ALB (443) → ECS (4000, ALB만) → Aurora (5432, ECS만)로 각 계층이 바로 앞 계층에서만 접근을 허용하는 최소 권한 인바운드를 구성합니다.
- TLS 1.3 종단: ALB HTTPS 리스너에 최신 TLS 정책을 적용하고 HTTP는 HTTPS로 리다이렉트합니다. 스트리밍 응답을 고려해 ALB idle timeout을 늘려주면 좋습니다.
- IAM 최소 권한: ECS 태스크 역할의 Amazon Bedrock 호출 권한은 생성한 Application Inference Profile ARN 및 그 기반 모델로 한정하고 (3.2), CloudWatch PutMetricData는 전용 네임스페이스 조건으로 좁힙니다.
- Secrets 관리: LiteLLM Master Key와 Aurora 자격증명은 Secrets Manager에 저장하고 ECS에 시크릿으로 주입해 안전하게 관리합니다.
(옵션) Direct Connect / VPN을 통한 완전 사설 (Private) 구성
위 구성은 ALB를 퍼블릭 서브넷에 두고 접근하는 기본형입니다. 사내 개발자 PC가 온프레미스에 있다면 AWS Direct Connect 또는 Site-to-Site VPN으로 연결하고 ALB를 Internal로 전환해 공개 진입점을 아예 없앨 수도 있습니다. 이때 Token Service의 API Gateway도 PRIVATE 타입으로 만들어 execute-api 인터페이스 엔드포인트로 접근해야 토큰 발급 경로까지 사설이 됩니다. 퍼블릭 서브넷과 Internet Gateway가 사라지므로 egress는 전부 VPC Endpoint가 담당합니다.
다만 완전히 격리된 환경은 아닙니다. aws sso login의 로그인 과정에서는 AWS 공용 sign-in 엔드포인트를 사용하고, API Gateway → Lambda → DynamoDB 구간은 AWS 관리형 service-to-service 호출입니다. 이 구간까지 VPC 내부로 묶으려면 Lambda를 VPC에 연결하고 DynamoDB는 Gateway Endpoint를 사용하면 됩니다.
사설 구성: Direct Connect / VPN + Internal ALB + execute-api 엔드포인트

6. 운영 팁: LiteLLM 게이트웨이 운영 노하우
Amazon Bedrock 측 설계와 별개로, LiteLLM 게이트웨이를 장기간 안정적으로 운영하며 정착시킨 실무 팁을 간략히 정리합니다. 이 항목들은 Amazon Bedrock 자체의 내용이라기보다는, LiteLLM을 프로덕션에서 운영할 때의 노하우에 가깝습니다.
- SpendLog 정리: SpendLog (요청별 상세 로그, 테이블명 LiteLLM_SpendLogs)를 계속 쌓기로 했다면 (2절 말미의 판단 참고), 규모가 커질수록 이 테이블의 크기와 삭제 부하가 문제가 됩니다. 가장 깔끔한 방법은 테이블을 시간 (예: 일 단위)으로 파티셔닝해 두고, 오래된 파티션을 DROP / DETACH로 통째로 제거하는 것입니다. 대량 DELETE와 달리 락, 데드 튜플, VACUUM 부담이 사실상 없습니다. 만약 파티셔닝이 어려운 상황이라면 보조 수단으로, 트래픽이 낮은 시간대에 오래된 행을 작은 배치로 나눠 삭제합니다. (단일 대량 DELETE는 락을 유발합니다.) 감사 추적은 이미 Amazon Bedrock 네이티브 로그 (2절)로 확보되므로, 이 상세 로그의 보존 정책은 조직 요건에 맞춰 가볍게 가져갈 수 있습니다.
- 주기적 롤링 재시작: 장시간 무중단 운영에서 LiteLLM 태스크가 서서히 성능 저하를 보이는 경우, 트래픽이 가장 적은 새벽 시간대에 무중단 롤링 재시작을 예약해 두면 안정적입니다. EventBridge Scheduler가 ecs:UpdateService를 forceNewDeployment=true로 호출하면, 새 태스크가 헬스체크를 통과한 뒤 기존 태스크가 드레인됩니다. 배포 서킷 브레이커 + 롤백을 함께 걸어 두면 잘못된 재시작이 자동 복구됩니다.
- 모니터링: CloudWatch 대시보드로 ECS CPU / 메모리, ALB 요청 수, 응답 시간을 모으고 알람을 SNS로 통지합니다. 응답 시간은 평균이 아니라 백분위수 (p95 / p99)로 봅니다. 느린 소수의 요청이 평균에 희석되어 드러나지 않는데, 사용자가 체감하는 성능은 바로 그 꼬리에서 결정되기 때문입니다.
- 오토 스케일링과 로드밸런싱: LiteLLM 워크로드는 CPU만으로는 부하를 대표하지 못하므로 메모리 기반 오토스케일링을 함께 설정합니다. 로드밸런싱은 라운드 로빈(Round Robin)이 아니라 Least Outstanding Requests (LOR)를 권장합니다. LLM 요청은 처리 시간 편차가 매우 큰데 (짧은 자동 완성 vs. 긴 스트리밍 응답), 라운드 로빈은 요청을 순서대로 균등 분배할 뿐이라 무겁고 오래 걸리는 요청이 우연히 특정 태스크에 몰리면 그 태스크만 커넥션 및 메모리가 쌓여 과부하가 됩니다. LOR은 “현재 처리 중인 미완료 요청이 가장 적은” 태스크로 새 요청을 보내므로, 이런 쏠림을 실시간으로 완화합니다.
7. 마치며
Amazon Bedrock 앞에 게이트웨이를 두면 편의와 통제를 얻지만, 그 대가로 Amazon Bedrock이 보는 호출자 신원과 모델별 관측 지점이 게이트웨이 뒤로 흐려지는 사각지대가 생깁니다. 이 글은 레퍼런스 아키텍처를 출발점으로, 그 사각지대를 Amazon Bedrock 네이티브 기능으로 보완하는 방법을 다뤘습니다.
- 호출자 축 (누가, 어느 팀이): X-Amzn-Bedrock-Request-Metadata로 호출자 (사용자, 팀, 키)와 호출 ID를 주입해, Bedrock Invocation Log만으로 호출자를 식별하고 게이트웨이 로그와 요청 단위로 대사할 수 있게 합니다.
- 모델 축 (어느 모델을): Application Inference Profile을 경유하도록 설계해 모델별 CloudWatch 관측성 (사용량 및 성능)과 IAM 접근 거버넌스를 확보합니다. 그 과정에서 pass-through 사용량 로깅이 Application Inference Profile ARN 경유 시 깨지는 이슈를 소스코드 수정으로 해결하고, 업스트림에도 리포트해 병합 후 제거할 수 있도록 했습니다.
- 통제와 운영: Virtual Key 레벨 예산과 IAM Identity Center 그룹 자동 매핑으로 Amazon Bedrock 사용을 회사 정책에 연결하고, 네트워크 격리와 게이트웨이 자동화로 장기 운영 안정성을 확보합니다.
정리하면, 게이트웨이는 요청을 가볍게 통과시키는 역할에 집중하고, 실제 호출자 추적과 모델별 관측은 Amazon Bedrock이 원래 제공하는 기능 (requestMetadata, Application Inference Profile)에 맡기는 것이 핵심입니다. 게이트웨이를 가볍게 유지할수록 Claude의 최신 기능 호환성과 운영 단순함을 둘 다 지킬 수 있습니다.
참고 자료
- AWS 기술 블로그 – Amazon Bedrock 기반 Claude Code, 조직에서 안전하게 운영하기: LLM Gateway 구축 가이드 (레퍼런스)
- Amazon Bedrock — Inference profiles (모델 사용량 메트릭·비용 추적)
- Amazon Bedrock — Model Invocation Logging
- AWS Billing — 사용자 정의 비용 배분 태그
- Claude Code — LLM Gateway / Amazon Bedrock 공식 문서
- LiteLLM — Bedrock Pass-through
- PostgreSQL — Table Partitioning (SpendLog 정리용)