
이 글에서 다룰 주제
주요 단어 · Master Key · Virtual Key · 모델 접근 · allowed_routes · 키 폐기
한 번의 모델 호출이 성공하면 모든 애플리케이션에 같은 키를 복사하기 쉽다. 하지만 서비스 장애 분석을 위한 AIOps 호출과 모델 평가 호출이 같은 키를 쓰면, 어떤 목적의 요청이 자원을 사용했는지 나누기 어렵고 한 키를 차단했을 때 영향 범위도 커진다.
Master Key는 Proxy를 관리하는 관리자 자격 증명이다. Virtual Key는 Proxy가 발급하는 앱용 자격 증명으로, 호출 가능한 모델과 예산·속도 제한을 연결할 수 있다. 키·사용량 관리를 위해 이번 실습은 PostgreSQL을 연결했다. Virtual Keys 문서

모델 접근 제한은 “어디로 호출할 수 있는가”, API 경로 제한은 “어떤 관리 기능을 호출할 수 있는가”를 다룬다. 둘은 별도다. models에 모델 이름을 넣었다는 이유만으로 관리 API도 모두 차단되었다고 판단하지 않는다.
공식 문서는 키 소유자의 역할이 관리 경로 권한에 영향을 줄 수 있다고 설명한다. 이 실습은 앱 키의 allowed_routes를 명시하고 실제 거절을 확인했다. 조직에 도입할 때는 사용자·팀·서비스 계정의 소유 관계도 함께 설계해야 한다. 키 권한 상속 설명
서비스·AIOps·평가용 키에 다음 정책을 적용했다. 아래 표의 이름은 키 별칭이다.
| 키 별칭 | 허용 모델 | RPM |
|---|---|---|
| study-service | local-text | 30 |
| study-aiops | local-text, local-text-metered | 20 |
| study-evaluation | local-text, local-text-metered, local-vision, local-vision-8b | 60 |
RPM은 분당 요청 수 제한이다. 이 값은 로컬 학습을 위한 임의 정책이며 운영 용량 측정으로 결정한 값은 아니다. 팀의 평가 배치가 서비스 요청을 밀어내지 않도록 목적별 제한을 어떻게 나눌지 공부하는 출발점이다.
local-text-metered는 비용 계산을 연습하기 위한 별칭이다. 같은 qwen3:8b를 호출하지만 가상 단가를 붙였다. 실비는 다음 편에서 구분한다.
환경변수 LITELLM_MASTER_KEY는 1편에서 만든 관리용 키다. 아래 코드는 실제 발급에 사용한 구조에서 서비스 키 부분을 추렸다. 반환되는 키는 한 번 받은 뒤 비공개 저장소에 보관한다.
import json
import os
import urllib.request
payload = {
"key_alias": "study-service",
"models": ["local-text"],
"rpm_limit": 30,
"allowed_routes": [
"/chat/completions", "/v1/chat/completions",
"/models", "/v1/models"
],
"metadata": {
"study": "litellm-20261005",
"allow_client_message_redaction_opt_out": False
}
}
request = urllib.request.Request(
"http://127.0.0.1:4000/key/generate",
data=json.dumps(payload).encode(),
headers={
"Authorization": "Bearer " + os.environ["LITELLM_MASTER_KEY"],
"Content-Type": "application/json"
}
)
with urllib.request.urlopen(request) as response:
issued = json.load(response)
fd = os.open("service-key.json", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as output:
json.dump({"key": issued["key"]}, output)
키 원문을 화면에 출력하지 않고 service-key.json에 권한 0600으로 저장한다. .env도 같은 권한으로 보관한다. 로컬 파일 보관은 학습용 선택이며 운영 배포에서는 승인된 비밀 관리 체계를 사용한다.
키 목록에서 별칭을 찾아 모델 접근 범위와 RPM을 확인한다. UI는 정책을 읽는 데 편리하지만, 목록에 설정이 보인다는 사실만으로 요청 차단까지 입증되지는 않는다.
왼쪽 Virtual Keys에서 study-service를 선택하고 Overview를 연다. 이번 화면에서는 Rate Limits의 RPM: 30, Models의 local-text를 확인했다.

키·요청 식별자는 제외하고 Rate Limits와 Models 영역만 직접 캡처했다.
확대 화면의 TPM: Unlimited도 함께 읽는다. 이번 키는 RPM만 지정했으므로 토큰 속도 제한까지 설정했다고 설명하면 안 된다. 화면을 볼 때는 키 별칭 → 허용 모델 → 제한 수치 순서로 읽는다. 서비스 키에 Vision 별칭이 들어갔거나 평가 키를 서비스에 배포했다면 호출 결과와 비용 분류가 달라진다. 캡처에는 키 원문을 포함하지 않는다.
LiteLLM 1.104.0 단일 Proxy에서 다음을 실제 실행했다.
| 실험 | 결과 | 해석 |
|---|---|---|
| 서비스 키로 local-text 호출 | HTTP 200 | 허용 경로 정상 |
| 서비스 키로 local-vision 호출 | HTTP 403 | 모델 접근 거절 |
서비스 키로 /key/list 호출 | HTTP 403 | 관리 API 접근 거절 |
테스트 키로 /v1/models 조회 | HTTP 200 | 폐기 전 유효 |
/key/delete 이후 같은 키 조회 | HTTP 401 | 폐기 후 인증 거절 |
| RPM=1 테스트 키로 연속 두 호출 | 200 → 429 | 속도 제한 확인 |
금지 모델 호출은 key_model_access_denied, 속도 제한은 throttling_error로 구분되었다. 모두 실패로 묶으면 권한 설정 문제와 트래픽 급증을 분리하기 어렵다.
키 폐기는 관리 키로 다음 형태의 API를 호출했다. key_to_revoke는 폐기할 테스트 키를 메모리에서 읽은 변수다.
request = urllib.request.Request(
"http://127.0.0.1:4000/key/delete",
data=json.dumps({"keys": [key_to_revoke]}).encode(),
headers={
"Authorization": "Bearer " + os.environ["LITELLM_MASTER_KEY"],
"Content-Type": "application/json"
}
)
with urllib.request.urlopen(request) as response:
assert response.status == 200
실습에서는 삭제 응답을 확인한 뒤 같은 키로 다시 요청해 401을 확인했다. 원본 키를 가진 클라이언트가 여전히 성공하는지까지 봐야 폐기 검증이 끝난다. 분산 복제본에서의 전파 지연은 이 단일 Proxy 실험으로 증명하지 않았다.
실습을 마치면 임시 테스트 키도 /key/delete로 폐기한다.
서비스용 키는 서비스가 필요한 모델만, AIOps용 키는 승인된 분석 경로만, 평가용 키는 비교에 필요한 경로를 허용한다. 모델명이 같아도 목적을 나누면 사용량의 소유자를 찾기 쉽다.
다만 키만으로 업무 성공 여부를 알 수는 없다. 하나의 장애 분석이 여러 모델 호출을 만들면 interaction ID와 각 호출 ID를 연결해야 한다. 이 연결은 심화편에서 여러 Gateway를 다룰 때 이어서 검증한다.
이 단계에서 직접 답할 질문은 간단하다. 평가 키가 유출되거나 평가 배치가 폭주했을 때 어떤 모델과 관리 API까지 도달할 수 있는가? 허용 목록뿐 아니라 실제 금지 요청의 결과로 답할 수 있어야 한다.
기준: 2026-10-05, LiteLLM 1.104.0. 표는 로컬 API 실측이며 외부 Provider 호출은 없다.
이전 · [LiteLLM 1] Docker와 Ollama로 내 LLM Gateway 만들기 · 다음 · [LiteLLM V1] 이미지 입력 모델로 대시보드 읽기 — 4B·8B와 출력 예산 비교