LiteLLM 자체 호스팅으로 LLM 게이트웨이 구축
LiteLLM을 Ubuntu 24.04 VPS에 배포해 모든 공급자를 하나의 OpenAI 호환 엔드포인트로 통합하고, 가상 키별 예산, 폴백, 고정 이미지를 설정하는 방법을 설명합니다.
자체 호스팅 LLM 게이트웨이의 역할
LiteLLM은 직접 호스팅하는 오픈 소스 LLM 게이트웨이입니다. 모든 애플리케이션이 하나의 HTTP 엔드포인트로 요청을 보내면, 게이트웨이가 이를 적절한 공급자에게 전달합니다. LLM은 거대 언어 모델(Large Language Model)을 의미합니다. 이 게이트웨이는 OpenAI의 Chat Completions API(애플리케이션 프로그래밍 인터페이스)를 지원하므로, 기존에 OpenAI와 통신하던 모든 클라이언트 라이브러리는 베이스 URL과 키라는 두 가지만 변경하면 바로 사용할 수 있습니다.
이러한 간접 계층을 두는 것이 핵심입니다. 애플리케이션이 직접 공급자의 자격 증명을 관리할 필요가 없습니다. 모델을 교체할 때 5개의 서비스 코드를 수정하는 대신, 서버의 설정 파일 한 줄만 바꾸면 됩니다. 모든 호출이 하나의 프로세스를 거치므로, 예산을 설정하고 사용량을 기록할 수 있는 중앙 지점이 생깁니다.
게이트웨이를 실행하면 다음과 같은 기능을 활용할 수 있습니다.
- 단일 엔드포인트. 애플리케이션은
https://gateway.example.com/v1을 대상으로 요청을 보내며,bulk이나strong과 같이 사용자가 정의한 모델 이름을 호출합니다. - 가상 키. 각 애플리케이션은 고유한 모델 허용 목록과 지출 한도가 설정된 개별 키를 발급받습니다. 다른 애플리케이션에 영향을 주지 않고 특정 키만 취소할 수 있습니다.
- 폴백(Fallback). 호출 실패나 프롬프트 크기 초과 시 다른 모델로 자동으로 재시도합니다.
- 로그 기록. 모든 요청은 비용 정보를 포함하여 기록되므로, 어떤 애플리케이션이 비용을 발생시켰는지 명확히 파악할 수 있습니다.
게이트웨이를 직접 운영해야 하는 이유
관리형 라우터는 사용자와 동일한 형태를 띠지만, 모든 요청의 중간에 타인의 프로세스가 개입합니다. 직접 운영하면 공급자의 키와 프롬프트 텍스트를 사용자가 제어하는 서버에 보관할 수 있습니다. 물론 그에 따른 비용은 실재합니다. 이제 모든 애플리케이션이 의존하는 구성 요소를 직접 운영해야 하기 때문입니다. 이 가이드의 마지막 섹션에서는 그 비용에 대해 다룹니다. 대부분의 문서에서 간과하는 부분이기 때문입니다.
준비 사항
- Ubuntu 24.04가 설치된 VPS(가상 사설 서버)와 Docker 및 Compose 플러그인.
- 외부 기기가 TLS(전송 계층 보안)를 통해 게이트웨이에 접근할 경우, 해당 서버를 가리키는 도메인 이름.
- 최소 하나 이상의 공급자 API 키.
게이트웨이는 추론을 수행하지 않습니다. 요청을 전달하고 응답을 스트리밍으로 반환하므로, CPU 부하는 모델 크기가 아닌 요청량에 따라 결정됩니다. 1 vCPU 사양의 서버로도 내부 애플리케이션 몇 개는 무리 없이 운영할 수 있습니다. 데이터베이스는 요청마다 사용량 행을 기록하므로 데이터가 계속 증가합니다.
config.yaml을 먼저 작성합니다
설정 파일은 클라이언트가 요청할 수 있는 모델을 결정합니다. 최상위 섹션 중 중요한 것은 4개입니다: model_list, litellm_settings, router_settings, general_settings.
model_list:
- model_name: bulk
litellm_params:
model: anthropic/claude-haiku-4-5
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: strong
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: strong
litellm_params:
model: openai/gpt-5.5
api_key: os.environ/OPENAI_API_KEY
litellm_settings:
num_retries: 2
request_timeout: 120
json_logs: true
set_verbose: false
router_settings:
allowed_fails: 3
cooldown_time: 30
fallbacks: [{"bulk": ["strong"]}]
context_window_fallbacks: [{"bulk": ["strong"]}]
general_settings:
background_health_checks: true
health_check_interval: 300model_name은 클라이언트가 전송하는 이름입니다. litellm_params.model은 실제 모델이며, provider/model로 작성합니다. 모델 이름은 공급업체 이름이 아니라 작업을 기준으로 지정합니다. bulk을 요청하는 애플리케이션은 다음 달 bulk을 다른 모델로 바꾸더라도 계속 동작합니다.
api_key: os.environ/ANTHROPIC_API_KEY은 LiteLLM이 런타임에 해당 변수를 읽도록 지시합니다. 리터럴 키가 파일에 직접 나타나지 않는다는 점이 중요합니다. config.yaml은 커밋하는 파일이기 때문입니다.
strong라는 이름을 사용하는 항목이 2개 있습니다. 이는 의도적인 구성입니다. 둘 이상의 배포가 동일한 model_name을 사용하면 라우터는 이들을 서로 바꿔 사용할 수 있는 배포로 간주하고 해당 이름에 대한 요청을 분산합니다. 공급자 중 하나에 일시적인 문제가 발생해도 strong이 계속 동작하는 방식이 바로 이것입니다. 배포 하나가 cooldown 상태가 되면(아래 설명 참조) strong에 대한 새 요청은 다른 배포로 전송됩니다.
num_retries: 2은 재시도 가능한 오류를 최대 2번 재시도합니다. 이 재시도에서는 동일한 모델 그룹에 있는 다른 사용 가능한 배포를 선택할 수 있습니다. 2026-10-29에 확인한 LiteLLM 아키텍처 문서에서는 그룹 내 재시도와 그룹 간 fallback을 별도의 단계로 설명합니다. 따라서 strong에 대한 재시도는 weighted failover를 활성화하지 않아도 다른 공급자에 도달할 수 있습니다. fallback은 이러한 재시도를 모두 사용한 뒤에만 실행됩니다.
allowed_fails과 cooldown_time는 라우터 옵션이므로 router_settings 아래에 배치합니다. 프록시는 해당 섹션을 기준으로 라우터를 구성합니다. v1.95.0에서는 litellm_settings 아래에 작성한 cooldown_time이 라우터에 전달되지 않으므로 cooldown 시간이 조용히 기본값인 5초로 유지됩니다. cooldown_time: 30은 cooldown 상태가 된 배포가 로테이션에서 제외되는 시간을 설정합니다. 설정 참조 문서에서는 allowed_fails를 배포가 cooldown 상태가 되기 전에 cooldown_time 동안 초과해야 하는 실패 횟수로 설명합니다. v1.95.0 소스에서는 allowed_fails: 3이 기본값입니다. 이 기본값을 사용하면 라우터는 내장된 트리거를 따릅니다. 다른 숫자를 설정해야만 실패 횟수가 적용됩니다. 이러한 트리거에는 다음이 포함됩니다. 배포가 2개 이상인 그룹에서 429 응답이 발생하면 즉시 cooldown 상태가 됩니다. 해당 그룹에서 현재 분에 최소 5개의 요청이 처리된 뒤 실패율이 50%를 초과해도 cooldown 상태가 됩니다. 잘못된 키로 인한 401처럼 라우터가 재시도하지 않는 오류도 해당합니다. 별도의 트리거는 단일 배포에 적용됩니다. 현재 분의 모든 요청이 실패하고 트래픽이 SINGLE_DEPLOYMENT_TRAFFIC_FAILURE_THRESHOLD에 도달하면 해당 배포가 cooldown 상태가 됩니다. SINGLE_DEPLOYMENT_TRAFFIC_FAILURE_THRESHOLD의 기본값은 1000 requests입니다. 공급자가 짧은 시간에 500 오류를 연속해서 반환하면 모든 요청마다 해당 공급자를 다시 시도하지 않습니다. 반면 몇 분에 한 번 발생하는 드문 500 오류는 cooldown을 발생시키지 않습니다.
fallbacks과 context_window_fallbacks는 트리거가 다릅니다. 두 번째 항목이 유용하지만 사람들이 자주 생략합니다.
fallbacks은 기본 호출이 실패할 때 실행됩니다.context_window_fallbacks은 공급자가 요청이 해당 모델의 context window보다 길다는 이유로 요청을 거부할 때 실행됩니다. 따라서 너무 큰 프롬프트는 호출자에게 오류를 반환하는 대신 처리할 공간이 있는 모델로 전달됩니다.
content_policy_fallbacks도 있습니다. 공급자가 content policy를 이유로 요청을 거부할 때 사용합니다. 해당 호출을 보낼 적절한 대상이 있을 때만 설정합니다.
Docker Compose를 사용하여 VPS에 LiteLLM 배포하기
세 개의 파일인 config.yaml, docker-compose.yml, .env를 담을 디렉터리를 생성합니다. 공식 퀵스타트 가이드는 latest 태그를 가져오도록 되어 있습니다. 대신 특정 릴리스 태그를 고정하십시오. 그래야 다음 달에 docker compose up -d를 실행해도 오늘과 동일한 게이트웨이가 유지되며, 롤백 또한 한 줄의 명령어로 가능해집니다.
services:
litellm:
image: ghcr.io/berriai/litellm:v1.95.0
restart: unless-stopped
command: ["--config", "/app/config.yaml", "--num_workers", "1"]
ports:
- "127.0.0.1:4000:4000"
volumes:
- ./config.yaml:/app/config.yaml:ro
env_file: .env
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: litellm
healthcheck:
test: ["CMD-SHELL", "pg_isready -U litellm"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:Compose는 여기서 .env를 두 번 읽습니다. 한 번은 compose 파일 내부의 ${POSTGRES_PASSWORD}을 치환하기 위함이고, 다른 한 번은 env_file을 통해 모든 변수를 컨테이너 내부로 전달하기 위함입니다.
v1.95.0은 2026년 8월 기준 최신 릴리스였습니다. 프로젝트의 릴리스 페이지를 확인하고 배포 시점의 최신 버전을 고정하십시오. 각 릴리스는 서명을 게시하므로, 이미지를 신뢰하기 전에 검증할 수 있습니다.
cosign verify --key https://raw.githubusercontent.com/BerriAI/litellm/v1.95.0/cosign.pub ghcr.io/berriai/litellm:v1.95.0포트 설정 줄은 127.0.0.1:4000:4000이며, 이는 루프백 인터페이스에만 포트를 게시합니다. 대신 4000:4000으로 작성하면 게이트웨이가 인터넷 전체에서 접근 가능해집니다. Docker는 iptables의 FORWARD 체인에 자체 규칙을 추가하는데, 이 규칙은 ufw보다 먼저 평가되므로 ufw deny 4000로는 이를 차단할 수 없기 때문입니다. 이는 자가 호스팅 게이트웨이가 외부로 노출되는 가장 흔한 경로입니다. Docker가 ufw를 우회하여 컨테이너 포트를 게시하는 방식을 참조하십시오. 외부 트래픽은 리버스 프록시를 통해서만 유입되어야 합니다.
공급자 키를 이미지에 포함하지 않기
.env 파일은 모든 비밀 정보를 담고 있습니다. 이 파일은 런타임에 환경 변수로 전달되므로 이미지에 포함되지 않으며, 저장소에 커밋되지도 않습니다.
LITELLM_MASTER_KEY=sk-REPLACE_ME
LITELLM_SALT_KEY=sk-REPLACE_ME_TOO
POSTGRES_PASSWORD=REPLACE_ME_AS_WELL
DATABASE_URL=postgresql://litellm:REPLACE_ME_AS_WELL@db:5432/litellm
STORE_MODEL_IN_DB=True
LITELLM_MODE=PRODUCTION
LITELLM_LOG=ERROR
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-proj-...무작위 값을 사용하여 두 개의 LiteLLM 키를 생성한 다음, 파일 권한을 제한합니다.
printf 'sk-%s\n' "$(openssl rand -hex 32)"
chmod 600 .envLITELLM_MASTER_KEY은 관리자 자격 증명입니다. 이 값은 관리 API를 인증하며 /ui의 관리자 UI 비밀번호로 사용됩니다. 어떠한 애플리케이션도 이 값을 보유해서는 안 됩니다.
LITELLM_SALT_KEY는 데이터베이스에 저장된 공급자 자격 증명을 암호화합니다. 이 값은 한 번 설정한 뒤 그대로 유지해야 합니다. 나중에 이 값을 변경하면 이미 저장된 자격 증명을 복호화할 수 없게 됩니다. 이 경우 게이트웨이는 정상적으로 시작되지만, 해당 공급자를 호출할 때마다 인증 오류가 발생합니다.
STORE_MODEL_IN_DB=True을 사용하면 config.yaml을 수정하지 않고도 관리자 UI에서 모델을 추가하거나 편집할 수 있습니다. 이는 편리하지만, 진실의 원천(source of truth)이 두 곳으로 나뉘게 됩니다. 어느 쪽을 우선할지 결정하고, 그 내용을 설정 파일 옆에 기록해 두십시오.
키를 설정 파일에 포함하지 않는 논리는 에이전트에 제공하는 도구에서 키를 제외하는 논리와 같습니다. AI 에이전트에서 공급자 비밀 정보 제외하기에서 해당 패턴을 다루며, Docker Compose의 env 파일과 비밀 정보에서 구체적인 구현 방법을 설명합니다.
서비스를 실행하고 첫 부팅 과정을 확인합니다.
docker compose up -d
docker compose logs -f litellm작동 여부 확인
인증이 필요 없는 프로브 2개와 인증이 필요한 프로브 1개가 있으며, 각각 실패하는 원인이 다릅니다.
curl -s http://127.0.0.1:4000/health/liveliness
curl -s http://127.0.0.1:4000/health/readiness/health/liveliness은(는) 인증이 필요 없으며 프로세스가 실행 중일 때 "I'm alive!"을(를) 반환합니다. /health/readiness 또한 인증이 필요 없습니다. 이 프로브는 "status": "healthy" 및 db 필드가 포함된 JSON 객체를 반환하거나, 데이터베이스에 연결할 수 없는 경우 503 오류를 반환합니다. 단일 가상 키를 조회할 수 없는 게이트웨이에서도 liveliness는 정상(green)으로 유지되므로, 모니터링 대상을 readiness로 설정하십시오.
인증이 필요한 체크는 공급자(provider)와 통신하는 프로브입니다.
curl -s http://127.0.0.1:4000/health \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"이 프로브는 healthy_endpoints 및 unhealthy_endpoints 배열로 응답합니다. unhealthy_endpoints에 있는 모델에서 인증 오류가 발생한다면 .env의 공급자 키가 잘못되었거나 누락된 것이며, 이는 현재 찾아내야 할 실패 원인입니다. background_health_checks: true이(가) 설정되어 있으므로 프록시는 health_check_interval초마다 자체적으로 이러한 프로브를 실행하고 /health은(는) 마지막 결과를 반환합니다. 따라서 폴링을 수행할 때마다 공급자에게 테스트 요청을 보내지는 않습니다.
가상 키와 키별 예산
모든 애플리케이션은 마스터 키를 기반으로 생성된 고유의 키를 부여받습니다.
curl -s http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{
"key_alias": "nightly-summariser",
"models": ["bulk"],
"max_budget": 5,
"budget_duration": "30d",
"rpm_limit": 60,
"tpm_limit": 200000
}'응답에는 sk-로 시작하는 key 필드가 포함됩니다. 이 문자열이 애플리케이션에 전달되는 유일한 정보입니다.
models은 이 키가 요청할 수 있는 항목의 허용 목록입니다. 위 키는bulk만 요청할 수 있습니다.budget_duration: "30d"이 포함된max_budget: 5는 매달 5달러로 설정됩니다. LiteLLM은 30일 단위가 아니라 매월 1일 자정(UTC 기준,litellm_settings아래timezone을 설정하지 않은 경우)에30d예산을 초기화하므로, 키가 5달러를 모두 소진하면 다음 달 1일까지 작동이 중단됩니다.rpm_limit과tpm_limit은 해당 키에 대한 분당 요청 수와 분당 토큰 수를 제한합니다.key_alias는 6주 뒤 지출 로그에서 확인할 수 있는 식별자입니다. 항상 설정하십시오.
예산이 소진되면 호출은 budget_exceeded 유형의 오류와 함께 다음 형태의 메시지를 반환하며 실패합니다.
Budget has been exceeded! Key=<key> Current cost: <spend>, Max budget: <max_budget>상태 코드는 릴리스 버전에 따라 다릅니다. 고정된 v1.95.0 버전에서는 HTTP 429를 반환합니다. v1.104.0 버전부터는 기본값이 HTTP 422로 변경되었으며, litellm_settings 아래 budget_exceeded_status_code: 429를 설정하면 이전 코드로 복원할 수 있습니다. 실제 rpm_limit 및 tpm_limit 제한에 도달한 경우에는 릴리스 버전과 관계없이 항상 429를 반환합니다. 업그레이드 후 어떤 코드가 반환되는지 확인하십시오.
상태 코드가 혼란을 야기하는 이유는 두 코드 모두 "예산 소진"을 명시하지 않기 때문입니다. 클라이언트 라이브러리는 429를 속도 제한으로 간주하여 예산이 초기화될 때까지 재시도(backoff)를 수행할 수 있으며, 422는 처리할 수 없는 요청으로 보고합니다. 상태 코드와 함께 응답 본문을 기록하지 않으면 예산 소진 현상이 매번 다른 오류처럼 보일 수 있습니다.
키 검사 및 조정은 동일한 관리 API를 통해 수행합니다.
curl -s "http://127.0.0.1:4000/key/info?key=sk-..." \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"
curl -s -X POST http://127.0.0.1:4000/key/update \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{"key": "sk-...", "max_budget": 25}'게이트웨이에서 강제하는 예산은 에이전트 자체에 문제가 발생한 경우에도 유지됩니다. 이것이 바로 VPS에서 AI 에이전트 비용을 제어하는 방법의 핵심인 이유입니다.
저렴한 모델로 대량 작업 전송
클라이언트를 게이트웨이로 지정합니다. 기본 URL, 키, 모델 이름은 다음과 같습니다.
curl -s http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-<the virtual key>" \
-H 'Content-Type: application/json' \
-d '{
"model": "bulk",
"messages": [{"role": "user", "content": "Say hello in five words."}]
}'모든 OpenAI 클라이언트 라이브러리는 동일하게 동작합니다. base_url을 https://gateway.example.com/v1로, api_key을 가상 키로 설정하십시오.
이제 호출자가 알지 못하는 사이에 config.yaml의 라우팅 정책이 적용됩니다. bulk에 대한 요청은 저렴한 모델로 전달됩니다. 해당 호출이 재시도 후에도 실패하면, 요청은 strong를 대상으로 다시 시도됩니다. 프롬프트가 bulk에 비해 너무 길면, context_window_fallbacks는 오류를 반환하는 대신 이를 strong로 보냅니다. 분류 작업이나 요약 백로그와 같은 대량 작업은 기본적으로 저렴하게 실행되며, 어려운 요청에 대해서만 더 많은 비용이 발생합니다.
이 지점이 바로 게이트웨이가 도구를 사용하는 에이전트와 함께 그 가치를 증명하는 곳입니다. 동일한 VPS의 MCP (model context protocol) 서버와 이를 구동하는 에이전트 모두 하나의 엔드포인트를 가리킬 수 있으므로, 둘 중 어느 것도 재배포할 필요 없이 배후의 모델을 변경할 수 있습니다.
폴백(fallback) 발생 여부를 확인하는 방법
이 장애 모드는 아무것도 고장 난 것처럼 보이지 않기 때문에 비용 손실을 유발합니다. 폴백이 성공하면 일반적인 응답 본문과 함께 HTTP 200 상태 코드를 반환합니다. 저렴한 모델이 하루 종일 중단되어 모든 호출이 비싼 모델로 조용히 처리되더라도, 첫 번째 증거는 청구서에서나 나타나게 됩니다.
증거는 응답 헤더에 존재합니다. 다음 헤더를 확인하십시오.
curl -s -D - -o /dev/null http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer sk-<the virtual key>" \
-H 'Content-Type: application/json' \
-d '{"model":"bulk","messages":[{"role":"user","content":"ping"}]}' \
| grep -i '^x-litellm'x-litellm-model-group은 요청이 라우팅된 모델 이름이며, 예시로bulk가 있습니다.x-litellm-model-id은 응답한 배포의 ID이며,/v1/model/info?litellm_model_id=...을 통해 조회할 수 있습니다. 이 ID가 사용자의bulk배포 목록에 없는 경우 폴백이 발생한 것입니다.x-litellm-attempted-fallbacks및x-litellm-attempted-retries는 해당 횟수를 집계합니다. 정상적인 호출에서는 두 값 모두 0보다 큰 값을 보이지 않습니다.x-litellm-response-cost는 해당 호출 1건에 대한 미국 달러 기준 비용입니다.x-litellm-call-id은 로그에서 동일한 호출을 찾기 위해 사용하는 식별자입니다.
모든 요청에서 x-litellm-attempted-fallbacks을 기록하고, 이 값이 0보다 클 때마다 알림을 설정하십시오. 이 숫자 하나가 정상적으로 작동하는 라우팅 정책과 "항상 비싼 모델 사용"으로 조용히 변질된 라우팅 정책을 구분하는 기준이 됩니다.
이 작업의 완전한 형태는 트레이싱(tracing)이며, 별도의 설정이 필요합니다: 에이전트 호출 트레이싱을 위한 자체 호스팅 Langfuse. LiteLLM은 콜백을 제공하므로, 자격 증명과 두 줄의 코드만 추가하면 연결할 수 있습니다.
litellm_settings:
success_callback: ["langfuse"]
failure_callback: ["langfuse"]LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://langfuse.example.comLangfuse v3 이상 버전의 경우, LiteLLM의 Langfuse 페이지에서는 이제 langfuse_otel 프리셋 사용을 권장합니다. 위에서 언급한 langfuse 콜백도 여전히 문서화되어 있습니다. failure_callback과 success_callback을 모두 설정하십시오. 이를 건너뛰면 오류가 발생하지 않은 트레이스만 남게 됩니다. 이와 별개로 LiteLLM은 요청당 비용 정보를 Postgres에 기록하며, 관리자 UI인 /ui가 해당 테이블을 읽어옵니다. 트래픽이 증가함에 따라 테이블 크기도 커지므로 작은 디스크를 사용할 경우 주의 깊게 모니터링해야 합니다.
리버스 프록시 뒤에 게이트웨이 배치
박스 외부의 어떤 연결도 포트 4000에 도달해서는 안 된다. nginx 또는 Caddy에서 TLS를 종료하고 loopback 주소로 전달한다.
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 600s;
}그 두 줄이 사람들이 빠뜨리는 줄이다. proxy_buffering off이 중요한 이유는 streaming completion이 server-sent event의 연속으로 전송되기 때문이다. nginx에서 buffering을 활성화하면 전달하기 전에 청크를 버퍼에 모으므로 클라이언트로의 전달이 지연된다. proxy_read_timeout 600s가 중요한 이유는 nginx의 기본값인 60초가 전체 응답에 대한 제한이 아니라 게이트웨이에서 두 번의 read 사이에 기다리는 최대 시간이기 때문이다. non-streamed completion은 generation이 끝날 때까지 아무것도 전송하지 않는다. 따라서 generation이 오래 걸리면 60초 동안 바이트가 도착하지 않고, 클라이언트는 504를 받으며 error log에는 upstream timed out (110: Connection timed out) while reading response header from upstream가 기록된다. 계속 청크를 전송하는 streamed completion은 60초보다 훨씬 오래 실행될 수 있다. 모델이 그 시간 동안 응답하지 않을 때만 timeout이 발생한다.
인증서 설정에는 nginx에서 Let's Encrypt와 Certbot 사용이 가장 간단한 방법이다. 이미 여러 컨테이너를 서비스하는 박스라면 여러 Compose 앱 앞에 Traefik 배치로 라우팅과 인증서를 한 곳에서 관리할 수 있다.
게이트웨이는 이제 단일 장애 지점이다
구축한 구성을 정확히 이해해야 한다. 현재 관리하는 모든 애플리케이션은 하나의 VPS에서 실행되는 하나의 컨테이너에 의존한다. 이 컨테이너가 중단되면 정상적으로 동작하는 provider를 포함해 어떤 model도 호출할 수 없다. 이에 따라 다음 4가지 사항을 고려해야 한다.
- 잘못된 설정 하나로 모든 서비스가 동시에 중단된다.
restart: unless-stopped는 crash를 감지하면 재시작하며, config.yaml을 파싱하지 못하는 컨테이너도 반복해서 재시작한다. 설정을 변경할 때마다docker compose logs litellm를 확인하고, 로그를 지켜볼 수 있을 때 설정을 변경해야 한다. - cache miss가 발생하면 Postgres가 요청 처리 경로에 포함된다. Virtual key 조회는 key가 cache에 없을 때 Postgres를 사용한다. spend 기록은 응답 후 비동기 방식으로 실행된다.
/health/readiness가 503을 반환한다면 gateway는 실행 중이지만 두 작업을 모두 수행할 수 없다는 경고다. - 먼저 CPU core를 사용하고 그다음 instance를 추가해야 한다. 프로젝트의 production 지침인 instance당 worker 1개(
--num_workers 1)는 Kubernetes 또는 instance를 자동으로 추가하는 다른 pod autoscaler를 전제로 한다. 이 예시처럼 단일 VM이나 bare container를 사용하는 경우에는 반대다. worker 수를 시스템의 vCPU 수로 설정해야 한다. 앞에서 사용한 1 vCPU 시스템에서는 여전히--num_workers 1이다. 더 큰 시스템에서는 worker 수를 늘리되, 각 worker가 자체 memory를 사용한다는 점을 기억해야 한다. load balancer 뒤에 두 번째 gateway를 배치하면 단일 container에 대한 의존을 없앨 수 있다. 그러나 database 의존성까지 제거되지는 않는다. - 재생성할 수 없는 데이터를 백업해야 한다. 여기에는
config.yaml와.env, 그리고 database의pg_dump가 포함된다.LITELLM_SALT_KEY를 잃으면 해당 dump에 포함된 암호화된 provider credential을 사용할 수 없게 된다. 따라서 env 파일과 dump는 같은 backup job에 포함해야 한다: off-box storage로 보내는 restic backup.
업그레이드하려면 image tag를 수정한 뒤 docker compose up -d를 실행하면 된다. LiteLLM은 기본적으로 시작할 때 prisma migrate deploy를 실행하므로 새 container는 최초 부팅 시 database schema를 마이그레이션한다. tag를 변경하기 전에 dump를 생성해야 한다. 이미 실행된 마이그레이션은 이전 image를 다시 사용해도 되돌릴 수 없기 때문이다.
건너뛴 각 version의 release note를 확인해야 한다. 최종적으로 사용하는 version의 release note만 확인해서는 안 된다. v1.95.0에서 v1.104.0 이상으로 이동하면 여러 breaking change를 거치게 된다. 여기에는 앞에서 설명한 budget status code, 설정되지 않았거나 비어 있거나 공개적으로 알려진 master key가 있으면 시작하지 않는 proxy, 시작 시 database setup에 실패하면 종료되는 proxy가 포함된다. restart: unless-stopped에서는 이 종료가 restart loop로 나타난다. v1.102.x 이하에서 업그레이드하는 경우 v1.104.0 release note에서는 두 개의 LiteLLM_SpendLogs index를 직접 생성하도록 요구한다.
FAQ
LiteLLM이 모든 호출에 눈에 띄는 지연 시간을 추가합니까?
프로젝트는 2026년 10월 README에서 초당 1000 requests일 때 95번째 백분위수 기준 8 ms라고 공개했다. 이 수치는 공급업체가 제시한 값으로 보아야 한다. 실제 지연 시간을 좌우하는 값은 애플리케이션과 gateway 사이의 네트워크 거리다. 모든 호출에 왕복 1회가 추가되기 때문이다. gateway를 호출하는 애플리케이션과 같은 region에서 실행한 다음, 실제 response에 포함된 x-litellm-overhead-duration-ms header로 자체 오버헤드를 측정한다.
nginx를 앞에 배치한 뒤 streaming이 작동하지 않는 이유는 무엇입니까?
nginx는 기본적으로 upstream response를 buffer하고, streaming completion은 server-sent event의 연속이기 때문이다. proxy_buffering이 활성화되면 nginx가 chunk를 buffer에 모은 뒤 전달하므로 client에 대한 전달이 지연된다. location block에 proxy_buffering off;을 설정한다. 같은 block에서 proxy_read_timeout도 늘린다. 기본적으로 nginx는 gateway에서 60초 동안 data를 받지 못하면 포기한다. 장시간 실행되는 non-streamed generation은 이 시간에 쉽게 도달하며, client는 504를 받는다.
virtual key의 budget이 소진되면 어떻게 됩니까?
호출은 budget_exceeded 유형의 error와 Budget has been exceeded! Key=<key> Current cost: <spend>, Max budget: <max_budget> 형식의 message로 실패한다. 고정한 v1.95.0에서는 status가 HTTP 429이고, v1.104.0부터는 기본값이 HTTP 422다. 어느 code에도 budget이 표시되지 않는다는 점이 함정이다. client library는 429를 rate limit으로 보고 재시도할 수 있으므로, 사용자는 message를 읽지 않고 throttling을 추적하기 시작한다. status code와 함께 response body를 log에 기록한다. master key에 대해 /key/info?key=sk-...를 사용하여 key의 실제 위치를 확인하고, budget이 너무 낮게 설정되었다면 /key/update으로 한도를 높인다.
gateway가 hosted model뿐 아니라 local model에도 route할 수 있습니까?
그렇다. model_list에 항목을 하나 더 추가하면 된다. ollama_chat/ prefix와 api_base를 사용한다. 예를 들어 model: ollama_chat/llama3.1을 api_base: http://ollama:11434과 함께 사용할 수 있다. Compose의 기본 networking에서는 container 내부의 localhost가 해당 container를 의미하므로, Compose service name이나 Docker network에서 host의 address를 사용해야 한다. 127.0.0.1은 절대 사용하지 않는다. container가 host의 network stack을 공유하는 host networking은 예외다. local model을 실행하는 작업은 별도로 수행해야 한다. VPS에서 Ollama로 LLM 자체 호스팅하기를 참조한다.