Kubernetes Deployment Guide¶
Estimated time to read: 6 minutes
Deploy LLMTrace on Kubernetes using the official Helm chart. The chart provisions:
LLMTrace Proxy: — the transparent LLM observability proxy (Deployment + Service + HPA)
ClickHouse: — analytical trace/span storage (Bitnami sub-chart)
PostgreSQL: — metadata storage for tenants, configs, audit events (Bitnami sub-chart)
Redis: — cache layer for hot queries, cost cap tracking, sessions (Bitnami sub-chart)
Prerequisites¶
| Tool | Version | Purpose |
|---|---|---|
| Kubernetes cluster | ≥ 1.26 | Target platform |
| Helm | ≥ 3.12 | Chart installation |
| kubectl | ≥ 1.26 | Cluster access |
| Docker (optional) | ≥ 24 | Building the proxy image |
Quick Start¶
Use the GHCR Image (or Build Your Own)¶
Pre-built multi-arch images are published to GHCR on every release:
# Use the official image
ghcr.io/epappas/llmtrace-proxy:latest
# Or build and push your own
docker build -t your-registry.io/llmtrace-proxy:0.1.3 .
docker push your-registry.io/llmtrace-proxy:0.1.3
Update Helm Dependencies¶
This downloads the Bitnami sub-charts (ClickHouse, PostgreSQL, Redis) into the charts/ directory.
Install (Development)¶
helm install llmtrace ./deployments/helm/llmtrace \
--namespace llmtrace \
--create-namespace \
--set proxy.image.repository=ghcr.io/epappas/llmtrace-proxy
Install (Production)¶
helm install llmtrace ./deployments/helm/llmtrace \
--namespace llmtrace \
--create-namespace \
-f ./deployments/helm/llmtrace/values-production.yaml \
--set proxy.image.repository=ghcr.io/epappas/llmtrace-proxy \
--set postgresql.auth.password=YOUR_SECURE_PASSWORD \
--set secrets.postgresUrl="postgres://llmtrace:YOUR_SECURE_PASSWORD@llmtrace-postgresql:5432/llmtrace"
4a. Install (ML / GPU Overlay)¶
Use the ML overlay when you want a persistent model cache, GPU limits, and Prometheus Operator resources:
helm install llmtrace ./deployments/helm/llmtrace \
--namespace llmtrace \
--create-namespace \
-f ./deployments/helm/llmtrace/values-production.yaml \
-f ./deployments/helm/llmtrace/values-ml.yaml \
--set proxy.image.repository=ghcr.io/epappas/llmtrace-proxy
Verify¶
# Watch pods come up
kubectl get pods -n llmtrace -w
# Check proxy health
kubectl port-forward -n llmtrace svc/llmtrace 8080:80
curl http://localhost:8080/health
Architecture Overview¶
┌─────────────┐
Internet ──────│ Ingress │
└──────┬──────┘
│
┌──────▼──────┐
│ Service │ port 80 → 8080
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼────┐ ┌────▼─────┐ ┌────▼─────┐
│ Proxy │ │ Proxy │ │ Proxy │ ← HPA (2-20 replicas)
│ Pod (1) │ │ Pod (2) │ │ Pod (N) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
┌───────┴─────────────┴─────────────┴───────┐
│ │
┌────▼─────┐ ┌──────────┐ ┌────────┐
│ClickHouse│ │PostgreSQL│ │ Redis │
│ (traces) │ │(metadata)│ │(cache) │
└──────────┘ └──────────┘ └────────┘
Configuration Reference¶
Proxy Settings¶
| Parameter | Description | Default |
|---|---|---|
proxy.replicaCount | Number of proxy replicas | 1 |
proxy.image.repository | Proxy container image | llmtrace-proxy |
proxy.image.tag | Image tag (defaults to appVersion) | "" |
proxy.upstreamUrl | Upstream LLM provider URL | https://api.openai.com |
proxy.storageProfile | Storage profile: lite, memory, production | production |
proxy.enableSecurityAnalysis | Enable prompt injection detection | true |
proxy.enableTraceStorage | Enable trace persistence | true |
proxy.enableStreaming | Enable SSE streaming passthrough | true |
proxy.timeoutMs | Request timeout (ms) | 30000 |
proxy.maxConnections | Max concurrent connections | 1000 |
proxy.logging.level | Log level | info |
proxy.logging.format | Log format (text or json) | json |
proxy.securityAnalysis.* | ML detector, model cache, and threshold tuning | see values.yaml |
proxy.enforcement.* | Pre-request enforcement policy | see values.yaml |
proxy.streamingAnalysis.* | Incremental streaming analysis | see values.yaml |
proxy.outputSafety.* | Output toxicity and hallucination controls | see values.yaml |
proxy.boundaryDefense.* | Boundary token defence controls | see values.yaml |
proxy.shutdown.timeoutSeconds | Graceful shutdown budget | 30 |
proxy.modelCache.* | Persistent model cache PVC + init container | see values.yaml |
proxy.extraConfig | Raw YAML appended to config.yaml | "" |
Service & Networking¶
| Parameter | Description | Default |
|---|---|---|
service.type | Kubernetes Service type | ClusterIP |
service.port | Service port | 80 |
ingress.enabled | Enable Ingress resource | false |
ingress.className | Ingress class name | "" |
ingress.hosts[0].host | Ingress hostname | llmtrace.local |
ingress.tls | TLS configuration | [] |
Autoscaling¶
| Parameter | Description | Default |
|---|---|---|
autoscaling.enabled | Enable HPA | false |
autoscaling.minReplicas | Minimum replicas | 2 |
autoscaling.maxReplicas | Maximum replicas | 10 |
autoscaling.targetCPUUtilizationPercentage | CPU target | 70 |
autoscaling.targetMemoryUtilizationPercentage | Memory target | 80 |
Monitoring¶
| Parameter | Description | Default |
|---|---|---|
monitoring.enabled | Master toggle for monitoring resources | false |
monitoring.serviceMonitor.enabled | Create ServiceMonitor for /metrics | false |
monitoring.prometheusRule.enabled | Create alerting rules | false |
Secrets¶
| Parameter | Description | Default |
|---|---|---|
secrets.create | Create Secret from values | true |
secrets.existingSecret | Use pre-existing Secret | "" |
secrets.clickhouseUrl | ClickHouse HTTP URL | http://llmtrace-clickhouse:8123 |
secrets.clickhouseDatabase | ClickHouse database name | llmtrace |
secrets.postgresUrl | PostgreSQL connection URL | postgres://llmtrace:llmtrace@... |
secrets.redisUrl | Redis connection URL | redis://llmtrace-redis-master:6379 |
Sub-charts¶
| Parameter | Description | Default |
|---|---|---|
clickhouse.enabled | Deploy bundled ClickHouse | true |
postgresql.enabled | Deploy bundled PostgreSQL | true |
redis.enabled | Deploy bundled Redis | true |
Set any sub-chart to enabled: false and configure secrets.*Url to point to your external service.
Common Operations¶
Upgrade¶
helm upgrade llmtrace ./deployments/helm/llmtrace \
--namespace llmtrace \
-f ./deployments/helm/llmtrace/values-production.yaml \
--set proxy.image.tag=0.2.0
Scale Manually¶
View Logs¶
# All proxy pods
kubectl logs -n llmtrace -l app.kubernetes.io/name=llmtrace -f
# Single pod
kubectl logs -n llmtrace deployment/llmtrace -f
Port-Forward for Local Access¶
# Proxy HTTP
kubectl port-forward -n llmtrace svc/llmtrace 8080:80
# Proxy gRPC (if enabled)
kubectl port-forward -n llmtrace svc/llmtrace 50051:50051
# ClickHouse (for debugging)
kubectl port-forward -n llmtrace svc/llmtrace-clickhouse 8123:8123
# PostgreSQL (for debugging)
kubectl port-forward -n llmtrace svc/llmtrace-postgresql 5432:5432
Uninstall¶
helm uninstall llmtrace --namespace llmtrace
# Also delete PVCs (WARNING: destroys data)
kubectl delete pvc -n llmtrace --all
Using External Services¶
To use externally managed databases instead of the bundled sub-charts:
# values-external.yaml
clickhouse:
enabled: false
postgresql:
enabled: false
redis:
enabled: false
secrets:
create: true
clickhouseUrl: "http://clickhouse.your-infra.svc:8123"
clickhouseDatabase: "llmtrace"
postgresUrl: "postgres://user:[email protected]:5432/llmtrace"
redisUrl: "redis://redis.your-infra.svc:6379"
helm install llmtrace ./deployments/helm/llmtrace \
-f values-external.yaml \
--namespace llmtrace --create-namespace
Using an Existing Secret¶
If you manage secrets externally (e.g., via Sealed Secrets, External Secrets Operator, or Vault):
The existing Secret must contain these keys:
| Key | Description |
|---|---|
LLMTRACE_CLICKHOUSE_URL | ClickHouse HTTP endpoint |
LLMTRACE_CLICKHOUSE_DATABASE | ClickHouse database name |
LLMTRACE_POSTGRES_URL | PostgreSQL connection string |
LLMTRACE_REDIS_URL | Redis connection string |
Ingress with TLS (cert-manager)¶
The production values file includes a ready-to-use Ingress configuration for nginx + cert-manager:
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: llmtrace.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: llmtrace-tls
hosts:
- llmtrace.example.com
Ensure cert-manager is installed and a ClusterIssuer named letsencrypt-prod exists.
Network Policies¶
Enable network policies to restrict traffic to the proxy:
This creates a NetworkPolicy that: - Allows inbound HTTP (8080) and gRPC (50051) traffic - Allows outbound to ClickHouse, PostgreSQL, Redis, DNS, and HTTPS (443)
Monitoring¶
The proxy exposes a /health endpoint suitable for Kubernetes probes. For Prometheus integration, add pod annotations:
proxy:
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
Troubleshooting¶
Pods stuck in CrashLoopBackOff¶
Check logs and config:
kubectl logs -n llmtrace -l app.kubernetes.io/name=llmtrace --previous
kubectl describe configmap -n llmtrace llmtrace-config
Cannot connect to ClickHouse/PostgreSQL/Redis¶
Verify the backing services are healthy:
kubectl get pods -n llmtrace
kubectl logs -n llmtrace -l app.kubernetes.io/name=clickhouse
kubectl logs -n llmtrace -l app.kubernetes.io/name=postgresql
kubectl logs -n llmtrace -l app.kubernetes.io/name=redis
Check the secret values match the actual service endpoints:
kubectl get secret -n llmtrace llmtrace -o jsonpath='{.data}' | \
jq 'to_entries[] | {key: .key, value: (.value | @base64d)}'
HPA not scaling¶
Ensure the metrics-server is installed: