Introduction
In our K3s AI infrastructure, LiteLLM serves as the central model gateway — a proxy that unifies multiple model providers behind a single OpenAI-compatible API. Combined with the Model Context Protocol (MCP), it becomes the backbone for agent-to-model communication. This post documents our production setup and the lessons learned along the way.
Architecture Overview
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Hermes │────▶│ LiteLLM │────▶│ NVIDIA │
│ Agents │ │ Gateway │ │ Nemotron │
└──────────────┘ │ │ └──────────────┘
┌──────────────┐ │ (port 4000) │ ┌──────────────┐
│ Cron Jobs │────▶│ │────▶│ Mistral │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ▲ ┌──────────────┐
│ Webhooks │───────────┘ │ OpenAI │
└──────────────┘ MCP Endpoint └──────────────┘
/mcp
LiteLLM Deployment on K3s
Configuration
Our LiteLLM deployment uses a model list configuration that routes requests to different providers based on the task:
# litellm-config.yaml
model_list:
# Primary model for coding tasks
- model_name: hermes-coder
litellm_params:
model: nvidia/nemotron-3-ultra-550b-a55b
api_base: https://integrate.api.nvidia.com/v1
api_key: ${NVIDIA_API_KEY}
timeout: 7200
# Mistral models for research and analysis
- model_name: mistral-reasoning
litellm_params:
model: mistral/devstral-latest
api_base: https://api.mistral.ai/v1
api_key: ${MISTRAL_API_KEY}
timeout: 3600
# Lightweight model for quick tasks
- model_name: hermes-lean
litellm_params:
model: nvidia/nemotron-3-nano-omni-30b-a3b-reasoning
api_base: https://integrate.api.nvidia.com/v1
api_key: ${NVIDIA_API_KEY}
timeout: 7200
Kubernetes Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: litellm-gateway
namespace: ia-services-payed
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: litellm
template:
metadata:
labels:
app: litellm
version: main-stable
spec:
containers:
- name: litellm
image: ghcr.io/anthropics/litellm:main-stable
ports:
- containerPort: 4000
env:
- name: MODEL_LIST
valueFrom:
secretKeyRef:
name: litellm-config
key: model-list.yaml
- name: PROXY_SERVER_KEY
valueFrom:
secretKeyRef:
name: litellm-secrets
key: api-key
- name: HTTP_TIMEOUT
value: "7200"
- name: REQUEST_TIMEOUT
value: "3600"
- name: LOG_PII
value: "false"
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "1"
livenessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 30
periodSeconds: 15
readinessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 10
periodSeconds: 10
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: litellm
namespace: ia-services-payed
spec:
selector:
app: litellm
ports:
- port: 4000
targetPort: 4000
type: ClusterIP
MCP Integration
MCP Endpoint Configuration
LiteLLM exposes the MCP endpoint at http://litellm.ia-services-payed.svc.cluster.local:4000/mcp. Agents and cron jobs connect to this endpoint to access tools and models:
# Example MCP client connection
from mcp import ClientConnection
# Connect via the K3s service DNS
connection = await ClientConnection.connect(
"litellm.ia-services-payed.svc.cluster.local",
port=4000,
path="/mcp",
)
# List available tools
tools = await connection.list_tools()
for tool in tools:
print(f"{tool.name}: {tool.description[:80]}...")
MCP Server Registration
Each MCP server registers itself with the LiteLLM gateway:
# Register a new MCP server
curl -X POST http://litellm.ia-services-payed.svc.cluster.local:4000/mcp/register \
-H "Authorization: Bearer ${LITE...KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "kubernetes-mcp",
"url": "http://mcp-k8s.forgejo-runners.svc.cluster.local:8080/mcp",
"timeout": 300
}'
Multi-Model Routing Strategy
Routing Rules
We use a tiered routing approach:
| Task Type | Primary Model | Fallback | Rationale |
|---|---|---|---|
| Code generation | hermes-coder (Nemotron) | devstral | Nemotron excels at code |
| Research/analysis | devstral | nemotron-35b | Mistral’s reasoning |
| Quick tasks | nemotron-3-nano | devstral | Fast, cost-effective |
| Complex reasoning | devstral | hermes-coder | Best overall reasoning |
| Creative writing | hermes-coder | devstral | Style quality |
Health-Based Routing
LiteLLM automatically routes to healthy models:
# If the primary model is unhealthy, LiteLLM falls back
# Check model health
import httpx
async def check_model_health(model_name: str) -> bool:
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.post(
f"{API_BASE}/v1/models",
headers={"Authorization": f"Bearer {API_KEY}"},
)
return response.status_code == 200
except httpx.TimeoutException:
return False
except httpx.RequestError:
return False
Operational Lessons
Lesson 1: Timeout Configuration Matters
NVIDIA’s API can be slow for large models. We set:
HTTP_TIMEOUT=7200s # 2 hours — for model loading
REQUEST_TIMEOUT=3600s # 1 hour — for individual requests
Without these, requests timeout during model cold starts.
Lesson 2: Rate Limiting at the Proxy Layer
Configure LiteLLM’s rate limits to prevent any single consumer from overwhelming the backend:
model_aliases:
"nvidia/nemotron-3-ultra-550b-a55b": "nemotron-35b"
litellm_settings:
model_fallbacks:
"nemotron-35b": ["nemotron-35b", "devstral-large"]
num_retries: 3
retry_after: 5
Lesson 3: Logging and Observability
Enable structured logging for debugging:
litellm_settings:
success_callback: ["langsmith", "opentelemetry"]
failure_callback: ["langsmith", "opentelemetry"]
cache: true
cache_params:
type: "redis"
host: "redis-monitoring.svc.cluster.local"
port: 6379
Security Considerations
API Key Management
Never hardcode API keys. Use Kubernetes secrets:
# Create the secret from existing environment variables
kubectl create secret generic litellm-secrets \
--from-literal=api-key="${PROXY_SERVER_KEY}" \
--namespace=ia-services-payed
kubectl create secret generic litellm-config \
--from-file=model-list.yaml \
--namespace=ia-services-payed
Network Policies
Restrict who can access the LiteLLM gateway:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: litellm-ingress
namespace: ia-services-payed
spec:
podSelector:
matchLabels:
app: litellm
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: forgejo-runners
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: cicd
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: flux-system
ports:
- port: 4000
policyTypes:
- Ingress
Monitoring LiteLLM
Key metrics to track:
- Request latency — P50, P95, P99 per model
- Token usage — input/output per model and consumer
- Error rate — by status code and error type
- Model health — uptime and response time per backend
- Cache hit rate — effectiveness of the response cache
Grafana dashboard queries (Prometheus):
# Request rate by model
rate(litellm_proxy_request_count[5m])
# Average latency by model
litellm_proxy_latency_seconds / 1000
# Error rate
sum(rate(litellm_proxy_request_error_count[5m])) / sum(rate(litellm_proxy_request_count[5m]))
Conclusion
LiteLLM + MCP provides a robust, scalable model gateway for K3s-based AI infrastructure. The key takeaways:
- Configure generous timeouts for model loading
- Use Kubernetes secrets for API key management
- Implement health-based model fallbacks
- Monitor token usage and latency per model
- Restrict access with NetworkPolicies
With these practices in place, your model gateway will handle the complexity of multi-model routing while your agents focus on their actual tasks.