1편에서 라우팅 전략을, 2편에서 폴백과 장애 대응을 다뤘습니다. 코드는 완성됐지만 프로덕션에 올리면 달라지는 게 있습니다. 단일 프로세스로 돌리면 멀티 인스턴스 간 RPM/TPM 상태를 공유할 수 없고, API 키를 코드에 박으면 팀원에게 공유할 수 없고, 누가 얼마나 쓰는지 보이지 않으면 월말에 청구서로 알게 됩니다. 3편은 그 세 가지를 해결합니다. Redis로 멀티 인스턴스 상태 동기화, Docker/K8s Proxy 서버 배포, Virtual Key로 팀별 예산과 Rate Limit 관리, Langfuse·Prometheus 연동까지 — 프로덕션에서 LiteLLM이 실제로 어떻게 운영되는지 전부 다룹니다.
이 포스트 한 줄 요약 → Redis 없이 멀티 인스턴스 → 각 인스턴스가 독립적으로 RPM 카운트 → 실제 2배 트래픽 허용 → redis_host + redis_port + redis_password 설정 세 줄이 전부 → Proxy 이미지: docker.litellm.ai/berriai/litellm:main-stable (핀 필수) → 필수 인프라: PostgreSQL (Virtual Key·비용 추적) + Redis (로드밸런싱 상태) → master_key: 모든 관리 API의 관문 — sk-로 시작하는 강력한 랜덤 문자열 → Virtual Key: 실제 API 키 숨기고, 팀·사용자별 예산·RPM·모델 접근 제어 → 예산 다중 윈도우: 일별 $10 AND 월별 $100 동시 적용 가능 → K8s 권장: Pod 1개당 Uvicorn worker 1개, HPA로 수평 확장 → ⚠️ 2026년 3월 보안 사고 (v1.82.7~1.82.8) — 버전 핀 필수
왜 Redis가 필요한가 — 멀티 인스턴스 문제
단일 LiteLLM 프로세스는 RPM/TPM 카운터를 인메모리에 저장합니다. 인스턴스를 2개 띄우면 각자 독립적인 카운터를 갖습니다.
인스턴스 A: Azure 배포에 300 RPM 카운트
인스턴스 B: 같은 Azure 배포에 300 RPM 카운트
→ 실제로는 600 RPM — 한도의 2배가 Azure로 전송됨
→ 429 폭탄 발생
Redis를 붙이면 모든 인스턴스가 동일한 RPM/TPM 카운터를 공유합니다.
인스턴스 A → Redis: Azure 배포 RPM += 1
인스턴스 B → Redis: Azure 배포 RPM 조회 → 현재 값 기반 결정
→ 전체 인스턴스 합산이 한도 이내로 유지
config.yaml 완전 해부
LiteLLM Proxy의 모든 설정은 config.yaml 하나로 관리됩니다. 프로덕션 수준의 완전한 예시입니다.
# config.yaml
# ── 모델 등록 ───────────────────────────────────────────────
model_list:
# 기본 고성능 모델 (Anthropic 직접)
- model_name: claude-sonnet # 클라이언트가 사용하는 이름
litellm_params:
model: claude-sonnet-4-6 # LiteLLM이 실제 호출하는 모델
api_key: os.environ/ANTHROPIC_API_KEY # 환경변수 참조
rpm: 1000
tpm: 200000
model_info:
id: claude-sonnet-direct
order: 1
# 같은 모델, Bedrock 경유 (멀티 리전)
- model_name: claude-sonnet
litellm_params:
model: bedrock/anthropic.claude-sonnet-4-6-v1
aws_region_name: us-east-1
aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID
aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY
rpm: 1000
tpm: 200000
model_info:
id: claude-sonnet-bedrock-east
order: 2
- model_name: claude-sonnet
litellm_params:
model: bedrock/anthropic.claude-sonnet-4-6-v1
aws_region_name: eu-west-1
aws_access_key_id: os.environ/AWS_ACCESS_KEY_ID
aws_secret_access_key: os.environ/AWS_SECRET_ACCESS_KEY
rpm: 800
tpm: 160000
model_info:
id: claude-sonnet-bedrock-eu
order: 3
# 교차 프로바이더 폴백 전용
- model_name: claude-sonnet-fallback
litellm_params:
model: gpt-5.5
api_key: os.environ/OPENAI_API_KEY
rpm: 500
tpm: 100000
# 롱 컨텍스트 폴백 전용
- model_name: claude-sonnet-long
litellm_params:
model: gemini-3.5-flash
api_key: os.environ/GEMINI_API_KEY
max_tokens: 1048576
# ── 라우터 설정 ─────────────────────────────────────────────
router_settings:
routing_strategy: simple-shuffle
num_retries: 1
timeout: 20
allowed_fails: 3
cooldown_time: 60
enable_pre_call_checks: true
enable_weighted_failover: true
# Redis 연동 (멀티 인스턴스 필수)
redis_host: os.environ/REDIS_HOST
redis_port: 6379
redis_password: os.environ/REDIS_PASSWORD
# 폴백 체인
fallbacks:
- claude-sonnet:
- claude-sonnet-fallback
context_window_fallbacks:
- claude-sonnet:
- claude-sonnet-long
- claude-sonnet-fallback:
- claude-sonnet-long
default_fallbacks:
- claude-sonnet-fallback
# 모델 별칭 (하위 호환성)
model_group_alias:
gpt-4: claude-sonnet # gpt-4 요청 → claude-sonnet 그룹으로 라우팅
# ── LiteLLM 전역 설정 ──────────────────────────────────────
litellm_settings:
# 응답 캐싱
cache: true
cache_params:
type: redis
host: os.environ/REDIS_HOST
port: 6379
password: os.environ/REDIS_PASSWORD
ttl: 3600 # 1시간 캐시
supported_call_types:
- acompletion
- completion
# 비용 추적
success_callback:
- langfuse # Langfuse로 전체 추적 전송
- prometheus # Prometheus 메트릭 노출
# 기본 파라미터
drop_params: true # 모델이 지원하지 않는 파라미터 자동 제거
set_verbose: false # 프로덕션에서 verbose 로그 끄기
# ── 프록시 서버 설정 ───────────────────────────────────────
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY # 🚨 반드시 sk- 로 시작
# PostgreSQL (Virtual Key, 비용 추적, 팀 관리 저장)
database_url: os.environ/DATABASE_URL
# 보안
store_model_in_db: true # UI에서 모델 추가 허용
disable_spend_logs: false # 비용 로그 활성화
# 환경
environment: production
환경변수 참조 방식: os.environ/변수명 형태를 쓰면 config.yaml에 실제 시크릿이 노출되지 않습니다. os.getenv("변수명")을 호출하는 것과 동일합니다.
Docker Compose 배포 — 단일 서버
# docker-compose.yml
version: "3.9"
services:
litellm:
# 🚨 보안: 버전 핀 필수 (2026년 3월 v1.82.7~1.82.8 공급망 사고)
image: docker.litellm.ai/berriai/litellm:main-stable
ports:
- "4000:4000"
volumes:
- ./config.yaml:/app/config.yaml:ro # 읽기 전용 마운트
environment:
- LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}
- DATABASE_URL=postgresql://litellm:${POSTGRES_PASSWORD}@postgres:5432/litellm
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- GEMINI_API_KEY=${GEMINI_API_KEY}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
# Langfuse 연동
- LANGFUSE_PUBLIC_KEY=${LANGFUSE_PUBLIC_KEY}
- LANGFUSE_SECRET_KEY=${LANGFUSE_SECRET_KEY}
- LANGFUSE_HOST=${LANGFUSE_HOST}
command:
- "--config"
- "/app/config.yaml"
- "--port"
- "4000"
- "--num_workers"
- "1" # K8s는 1 권장, 단일 서버는 CPU 코어 수
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health/liveliness"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: litellm
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U litellm"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres_data:
redis_data:
# .env
LITELLM_MASTER_KEY=sk-your-strong-random-master-key-here
POSTGRES_PASSWORD=strong-postgres-password
REDIS_PASSWORD=strong-redis-password
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GEMINI_API_KEY=AI...
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://cloud.langfuse.com
DATABASE_URL=postgresql://litellm:${POSTGRES_PASSWORD}@postgres:5432/litellm
# 시작
docker compose up -d
# 상태 확인
curl http://localhost:4000/health/liveliness
# → {"status":"ok"}
Kubernetes 배포 — 멀티 인스턴스 고가용성
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: litellm
namespace: ai-gateway
spec:
replicas: 3 # 최소 2개 (고가용성), 3개 권장
selector:
matchLabels:
app: litellm
template:
metadata:
labels:
app: litellm
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "4000"
prometheus.io/path: "/metrics"
spec:
containers:
- name: litellm
# 🚨 latest 금지 — 특정 버전 핀
image: docker.litellm.ai/berriai/litellm:main-stable
ports:
- containerPort: 4000
args:
- "--config"
- "/app/config.yaml"
- "--port"
- "4000"
- "--num_workers"
- "1" # K8s: Pod당 1 worker, HPA로 수평 확장
env:
- name: LITELLM_MASTER_KEY
valueFrom:
secretKeyRef:
name: litellm-secrets
key: master-key
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: litellm-secrets
key: database-url
- name: REDIS_HOST
value: "redis-service"
- name: REDIS_PORT
value: "6379"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: litellm-secrets
key: redis-password
volumeMounts:
- name: config
mountPath: /app/config.yaml
subPath: config.yaml
livenessProbe:
httpGet:
path: /health/liveliness
port: 4000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/readiness
port: 4000
initialDelaySeconds: 10
periodSeconds: 5
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "2Gi"
cpu: "1000m"
volumes:
- name: config
configMap:
name: litellm-config
---
# HPA — 요청 수 기반 수평 확장
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: litellm-hpa
namespace: ai-gateway
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: litellm
minReplicas: 3
maxReplicas: 20
metrics:
# CPU 50% 초과 시 스케일아웃
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
# 메모리 80% 초과 시 스케일아웃
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
---
# PodDisruptionBudget — 롤링 업데이트 중 가용성 보장
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: litellm-pdb
namespace: ai-gateway
spec:
minAvailable: 2 # 항상 최소 2개 Pod 유지
selector:
matchLabels:
app: litellm
LiteLLM 공식 문서는 Pod당 Uvicorn worker 1개 + HPA 수평 확장 조합을 명시적으로 권장합니다. Worker를 늘리는 대신 Pod를 늘리는 것이 레이턴시 안정성과 HPA 임계값 튜닝에 더 효과적입니다.
Virtual Key — API 키 없이 팀과 모델 접근 제어
Virtual Key는 실제 LLM 프로바이더 API 키를 숨기고, 팀·사용자·서비스별로 독립적인 접근 제어와 예산을 적용하는 핵심 기능입니다.
# Master Key로 Virtual Key 생성
# 팀 A: Claude Sonnet만 허용, 일별 $50 한도, RPM 200
curl -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-your-master-key" \
-H "Content-Type: application/json" \
-d '{
"max_budget": 50,
"budget_duration": "1d",
"models": ["claude-sonnet"],
"tpm_limit": 100000,
"rpm_limit": 200,
"metadata": {"team": "team-a", "purpose": "production"},
"key_alias": "team-a-production"
}'
# 응답:
# {
# "key": "sk-xxxxxxxxxxxxxxxxxxxxxxxx", ← 이 키를 팀 A에 배포
# "key_alias": "team-a-production",
# "expires": null,
# "max_budget": 50.0
# }
# 팀 A는 이 키로 Proxy를 사용
# 실제 Anthropic API 키는 모름
from openai import OpenAI
client = OpenAI(
api_key="sk-xxxxxxxxxxxxxxxxxxxxxxxx", # Virtual Key
base_url="http://your-litellm-proxy:4000",
)
response = client.chat.completions.create(
model="claude-sonnet",
messages=[{"role": "user", "content": "안녕하세요"}],
)
Virtual Key가 해결하는 문제:
기존 방식:
팀원 → 공유 API 키 → 누가 얼마나 쓰는지 모름
키 유출 → 전체 계정 위험
Virtual Key 방식:
팀원 → Virtual Key (팀별 고유) → 사용량 추적 가능
키 유출 → 해당 키 즉시 비활성화 (실제 API 키 안전)
예산 초과 → 해당 키만 429, 다른 팀 영향 없음
예산 관리 — 다중 윈도우 + 모델별 한도
# 다중 예산 윈도우: 일별 $10 AND 월별 $100
curl -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-master-key" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "dev-team-key",
"models": ["claude-sonnet", "claude-sonnet-fallback"],
"budget_duration": "1d",
"max_budget": 10,
"metadata": {
"team": "dev",
"monthly_budget": 100
}
}'
# 모델별 개별 예산
# (Enterprise 기능 — 특정 모델의 비용만 별도 추적)
curl -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-master-key" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "mixed-team-key",
"model_max_budget": {
"claude-sonnet": {
"max_budget": 5,
"budget_duration": "1d"
},
"claude-sonnet-fallback": {
"max_budget": 50,
"budget_duration": "30d"
}
}
}'
예산 초과 시 동작:
예산 초과 → HTTP 429 + 에러 메시지:
{
"error": {
"message": "ExceededTokenBudget: Current spend for token: 10.02;
Max Budget for Token: 10.0",
"type": "budget_exceeded",
"code": 429
}
}
팀 단위 예산 설정:
# 팀 생성
curl -X POST http://localhost:4000/team/new \
-H "Authorization: Bearer sk-master-key" \
-H "Content-Type: application/json" \
-d '{
"team_alias": "engineering",
"max_budget": 500,
"budget_duration": "30d",
"models": ["claude-sonnet", "claude-sonnet-fallback", "claude-sonnet-long"],
"tpm_limit": 500000,
"rpm_limit": 2000
}'
# 팀에 속하는 키 생성
curl -X POST http://localhost:4000/key/generate \
-H "Authorization: Bearer sk-master-key" \
-H "Content-Type: application/json" \
-d '{
"team_id": "engineering-team-id",
"key_alias": "engineer-john",
"max_budget": 50, # 개인 한도 (팀 한도의 부분집합)
"budget_duration": "30d"
}'
비용 추적 — 누가 얼마나 쓰는지 실시간 파악
# 팀별 지출 리포트
curl -X GET "http://localhost:4000/global/spend/report\
?start_date=2026-05-01\
&end_date=2026-05-26\
&group_by=team" \
-H "Authorization: Bearer sk-master-key"
# 응답 예시:
# {
# "results": [
# {"team_id": "engineering", "spend": 247.83, "tokens": 24783000},
# {"team_id": "product", "spend": 89.12, "tokens": 8912000}
# ]
# }
# 특정 키 현재 상태 확인
curl -X GET "http://localhost:4000/key/info?key=sk-virtual-key" \
-H "Authorization: Bearer sk-master-key"
# 일별 활동 추적
curl -X GET "http://localhost:4000/user/daily/activity\
?start_date=2026-05-20\
&end_date=2026-05-26" \
-H "Authorization: Bearer sk-master-key"
요청에 커스텀 태그 추가 — 프로젝트·환경별 추적:
response = client.chat.completions.create(
model="claude-sonnet",
messages=[...],
extra_headers={
"x-litellm-tags": "project:recommendation-engine,env:production",
}
)
# → Langfuse/Prometheus에서 태그별 비용 필터링 가능
Langfuse + Prometheus 모니터링 연동
# config.yaml 모니터링 섹션
litellm_settings:
success_callback:
- langfuse
- prometheus
failure_callback:
- langfuse # 실패 요청도 추적
# Prometheus 메트릭 엔드포인트: GET /metrics
# 노출되는 주요 메트릭:
# litellm_requests_total{model, status_code}
# litellm_request_duration_seconds{model, quantile}
# litellm_tokens_total{model, type}
# litellm_spend_total{model, team}
# Prometheus scrape config
scrape_configs:
- job_name: 'litellm'
static_configs:
- targets: ['litellm-service:4000']
metrics_path: '/metrics'
scrape_interval: 15s
Grafana 대시보드 핵심 패널:
# 요청 성공률
sum(rate(litellm_requests_total{status_code="200"}[5m]))
/ sum(rate(litellm_requests_total[5m])) * 100
# 모델별 P95 레이턴시
histogram_quantile(0.95,
sum(rate(litellm_request_duration_seconds_bucket[5m])) by (le, model)
)
# 팀별 시간당 비용
sum(rate(litellm_spend_total[1h])) by (team)
# 폴백 발생률
sum(rate(litellm_requests_total{fallback="true"}[5m]))
/ sum(rate(litellm_requests_total[5m])) * 100
프로덕션 배포 체크리스트
인프라
□ PostgreSQL 연결 확인 (Virtual Key, 비용 추적 저장)
□ Redis 연결 확인 (멀티 인스턴스 RPM/TPM 동기화)
□ Docker 이미지 특정 버전으로 핀 (latest 사용 금지)
□ HTTPS/TLS 설정 (Ingress 또는 LoadBalancer)
보안
□ LITELLM_MASTER_KEY 강력한 랜덤 문자열 (sk- 시작)
□ 모든 API 키 환경변수로 관리 (config.yaml에 하드코딩 금지)
□ Virtual Key로 팀별 접근 분리 (공유 API 키 사용 금지)
□ LITELLM_SALT_KEY 설정 (저장된 크레덴셜 암호화)
고가용성
□ 최소 2개 replica (3개 권장)
□ HPA 설정 (CPU 50%, 메모리 80% 기준)
□ PodDisruptionBudget 설정 (롤링 업데이트 시 가용성)
□ Liveness/Readiness Probe 설정
비용 관리
□ 팀별 Virtual Key + 예산 한도 설정
□ 일별/월별 다중 예산 윈도우 적용
□ 예산 소진 알림 설정 (Slack/PagerDuty)
모니터링
□ Prometheus 메트릭 수집
□ Grafana 대시보드 (성공률, 레이턴시, 비용)
□ Langfuse 전체 추적 연동
□ 비용 초과 알림 설정
□ 폴백 발생률 추적
⚠️ 보안 사고 노트 (2026년 3월)
2026년 3월, LiteLLM v1.82.7과 v1.82.8에 공급망 보안 사고가 발생했습니다. 두 버전은 즉시 풀리고 v1.83.0에 클린 빌드가 배포됐습니다. 현재 이 버전을 사용하고 있다면 즉시 업그레이드가 필요합니다.
이 사고에서 얻어야 할 교훈은 두 가지입니다. Docker 이미지를 latest나 main-latest로 사용하면 보안 사고가 발생한 버전이 자동으로 배포될 수 있습니다. 특정 버전으로 핀하고, 업그레이드는 검증 후 수동으로 진행해야 합니다.
# ❌ 위험
image: docker.litellm.ai/berriai/litellm:latest
# ✅ 안전 (특정 stable 태그 또는 버전 핀)
image: docker.litellm.ai/berriai/litellm:main-stable
# 또는
image: docker.litellm.ai/berriai/litellm:v1.84.0
✅ 결론
항목 설정
| 멀티 인스턴스 RPM/TPM | redis_host + redis_port + redis_password |
| API 키 보안 | 환경변수 참조 (os.environ/KEY) |
| 팀별 접근 제어 | Virtual Key + models 화이트리스트 |
| 비용 관리 | max_budget + budget_duration + 다중 윈도우 |
| K8s 성능 | Pod당 1 worker + HPA 수평 확장 |
| 이미지 보안 | 특정 버전 핀 (latest 금지) |
| 모니터링 | Prometheus 메트릭 + Langfuse 추적 |
| 가용성 | 최소 3 replica + PDB + liveness probe |
3편의 인프라 기반이 완성되면 LiteLLM은 단순한 로드밸런서를 넘어 팀 전체의 LLM 사용을 통제하는 게이트웨이가 됩니다. 4편에서는 시맨틱 라우팅, 멀티 리전 패턴, 실전 아키텍처 3가지를 다룹니다.
LiteLLM 시리즈 완결
- ✅ Router 구조와 라우팅 전략 6가지 https://cell-devlog.tistory.com/273
- ✅ 폴백 전략과 장애 대응 https://cell-devlog.tistory.com/274
- ✅ 프로덕션 배포: Redis + Proxy 서버 https://cell-devlog.tistory.com/275
- ✅ 고급 라우팅과 실전 아키텍처 https://cell-devlog.tistory.com/276
'AI 개발' 카테고리의 다른 글
| Cloudflare가 LLM 추론을 두 단계로 쪼갠 이유 — Workers AI 인프라 완전 해부 (0) | 2026.05.27 |
|---|---|
| LiteLLM Load Balancing 4편 — 시맨틱 라우팅, 커스텀 전략, 실전 아키텍처 3가지 (0) | 2026.05.26 |
| LiteLLM Load Balancing 2편 — 폴백 전략과 장애 대응 완전 가이드 (0) | 2026.05.26 |
| LiteLLM Load Balancing 완전 정복 1편 — Router 구조와 라우팅 전략 6가지 (0) | 2026.05.26 |
| 회사에서 지금 몇 개의 AI 모델이 돌고 있나요 — AI-BOM이 뜨는 이유 (0) | 2026.05.26 |