В першій частині – LiteLLM: AI Gateway для LLM – overview можливостей знайомились з можливостями LiteLLM взагалі – тепер можна запускати в Kubernetes і підключати клієнтів.
Заодно перевіримо інтеграцію із вже існуючим стеком моніторингу – поки тільки метрики до VictoriaMetrics. Логи в VictoriaLogs будуть по дефолту, а VictoriaTraces вже підключимо в наступній частині – хоча це робиться дуже просто.
Отже, що сьогодні будемо робити?
- задеплоїмо в Kubernetes – з власним Helm-чартом аби створити ресурси для External Secrets Operator (ESO) (див. AWS: Kubernetes та External Secrets Operator для AWS Secrets Manager)
- запускати будемо з двома Kubernetes Pods та PodDisruptionBudget – бо сервіс важливий
- підключимо AWS PostgreSQL RDS та Redis
- API ключі і паролі передамо із AWS Secrets Manager з External Secrets Operator
- налаштуємо Ingress на AWS ALB
- створимо пару юзерів і перевіримо збір метрик до VictoriaMetrics
Що варто глянути перед запуском:
- BerriAI/litellm/example_config_yaml: приклади конфігів для різних use cases
- config_settings: всі параметри конфіг-файлу
Зміст
Планування деплою
Див. документацію Deployment Options та High Availability Setup (Resolve DB Deadlocks).
Що маємо – AWS Elastic Kubernetes Service, вже існуючий сервер AWS RDS з PostgreSQL, вже існуючий AWS Application Load Balancer.
Для LiteLLM окрім PostgreSQL рекомендується мати Redis – для Caching і синхронізації лімітів TPM та RPM, теж додам – подивимось, як воно працює і що дає.
Є Terraform providers, і доволі багато, наприклад scalepad/litellm, але я буду робити без Terraform – трохи clickops в AWS і чистий Helm для деплою.
В ідеалі – менеджмент всяких ключів зробити з Terraform через ephemeral resources – але знов-таки конкретно в моєму випадку поки обійдусь (див. Terraform: використання Ephemeral resources та Write-only attributes).
LiteLLM Helm chart
Є офіційний Helm chart, хоча він “[BETA] Helm Chart is BETA” – але доволі зручний, виглядає достатньо читабельним, тому спробуємо його.
Ще десь знайшов (чи підкинули в чатику RTFM у Telegram) ресурс HelmRelease для Flux CD – там можна для прикладу глянути всякі цікаві параметри.
Перше, з чого почнемо – це подивитись що є в values чарту, аби розуміти що ми можемо налаштувати з коробки – а що треба буде описувати самому.
Можна просто в консолі з helm show values:
$ helm show values oci://ghcr.io/berriai/litellm-helm
Але зручніше скачати і подивитись в IDE.
Шукаємо останню версію, на момент написання це була 1.87.1 (апдейти виходять дуже часто, тому варто відразу налаштувати Renovate – див. Renovate: GitHub та Helm Charts versions management):
$ helm show chart oci://ghcr.io/berriai/litellm-helm Pulled: ghcr.io/berriai/litellm-helm:1.87.1 ... version: 1.87.1
Качаємо чарт, розпаковуємо:
$ helm pull oci://ghcr.io/berriai/litellm-helm --version 1.87.1 --untar $ cd litellm-helm/
Дивимось, що є в чарті і що нам варто змінити під себе.
Корисні Helm values
Що треба буде змінити:
replicaCount: для Production варто поставити 2 чи 3image.tag: задаємо конкретну версію, а не юзаємоlatestserviceAccount.name: якщо використовується AWS RDS з IAM Database Authentication (див. AWS: RDS з IAM database authentication, EKS Pod Identities та Terraform) або треба видати доступ до AWS Secrets Manager, то можна передати власний ServiceAccount – але в моєму випадку RDS без IAM, а в AWS Secrets Manager ходить External Secrets Operator, у якого налаштовані власні доступиenvironmentSecrets: можемо створити власний Kubernetes Secret і передати його тут – так і буде з сікретами від ESOenvironmentConfigMaps: можемо створити окремий ConfigMap зі змінними оточення – корисно, можемо передати параметри типуmax_requests_before_restart, див. CLI Argumentsingress: налаштуємо Ingress з AWS ALBmasterkeySecretNameтаmasterkeySecretKey: передамо параметри для отримання$LITELLM_MASTER_KEYproxy_config: можна прямо в values описати параметри самого LiteLLM – але я зробив через окремий ConfigMap і передав в values черезproxyConfigMapautoscalingтаkeda: добре, що є – але для нас поки не актуальноtolerations,affinity: нам треба, бо критичні сервіси працюють на виділеній групі WorkerNodesdb: опишемо підключення до PostgreSQL- так як у нас сервер зовнішній – то відключимо
deployStandalone - логін-пароль передамо через Secret, який буде створювати ESO
- так як у нас сервер зовнішній – то відключимо
redis: включимо деплой дефолтного із саб-чарту, хоча можна підключити зовнішній
Отже, окрім чарту у власному templates/ треба буде описати тільки два ресурси – ConfigMap з параметрами LiteLLM та ExternalSecret для External Secrets Operator.
Підготовка до деплою
Значення всяких ключів і паролів треба мати до деплою Helm chart – тому починаємо з них.
Створення LLM Providers API Key
У нас для тесту будуть Anthropic та OpenAI – створюємо ключі для них:
Генеруємо $LITELLM_MASTER_KEY:
$ echo "sk-$(openssl rand -hex 16)" sk-b75***630
Зберігаємо їх, потім разом з даними для PostgreSQL додамо до AWS Secrets Manager.
Створення PostgreSQL User && Database
Генеруємо пароль юзера:
$ pwgen 12 1 bai***vah
Створюємо юзера і базу:
ops_grafana_db=> CREATE USER ops_litellm_user WITH PASSWORD 'bai***vah'; CREATE ROLE ops_grafana_db=> CREATE DATABASE ops_litellm_db OWNER ops_litellm_user; CREATE DATABASE ops_grafana_db=> GRANT ALL PRIVILEGES ON DATABASE ops_litellm_db TO ops_litellm_user; GRANT
Перевіряємо підключення:
$ export PGPASSWORD='bai***vah'; psql -h db.monitoring.ops.example.co -U ops_litellm_user -d ops_litellm_db
psql (18.4, server 16.8)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: none)
Type "help" for help.
ops_litellm_db=> \l ops_litellm_db
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | Locale | ICU Rules | Access privileges
----------------+------------------+----------+-----------------+-------------+-------------+--------+-----------+---------------------------------------
ops_litellm_db | ops_litellm_user | UTF8 | libc | en_US.UTF-8 | en_US.UTF-8 | | | =Tc/ops_litellm_user +
| | | | | | | | ops_litellm_user=CTc/ops_litellm_user
Тепер у нас є 4 сікрети – два API ключі для OpenAI та Anthropic, мастер-ключ самого LiteLLM і пароль для PostgreSQL.
Створення secret в AWS Secrets Manager
Створюємо новий сікрет з типом “Other type of secret”, значення задаємо в JSON:
{
"LITELLM_MASTER_KEY": "sk-***",
"OPENAI_API_KEY": "sk-***",
"ANTHROPIC_API_KEY": "sk-***",
"DATABASE_USERNAME": "ops_litellm_user",
"DATABASE_PASSWORD": "***"
}
Зберігаємо як /ops/litellm-prod-secrets – потім це ім’я будемо використовувати в External Secrets Operator:
Власний Helm чарт для LiteLLM
Створюємо власний чарт, в ньому через dependencies підключаємо чарт від BerriAI – пишемо Chart.yaml:
apiVersion: v2
name: litellm
description: Helm chart for LiteLLM proxy
type: application
version: 0.1.0
appVersion: "1.0.0"
dependencies:
- name: litellm-helm
version: "1.87.1"
repository: "oci://ghcr.io/berriai"
Переходимо до сікретів.
External Secrets Operator для AWS Secrets Manager
Описуємо файл templates/secretstore.yml з самим SecretStore і до нього ExternalSecret.
В ExternalSecret вказуємо dataFrom.extract і створений вище AWS Secret /ops/litellm-prod-secrets зі змінними:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: litellm-secret-store
spec:
provider:
aws:
service: SecretsManager
region: {{ .Values.aws.region }}
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: litellm-external-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: litellm-secret-store
kind: SecretStore
target:
name: litellm-secrets
creationPolicy: Owner
deletionPolicy: Delete
dataFrom:
- extract:
key: /ops/litellm-prod-secrets
З dataFrom.extract ESO отримає JSON з AWS Secrets Manager і запише його до Kubernetes Secret litellm-secrets в data у вигляді $KEY:VALUE, а Pods змонтують цей сікрет собі з envFrom.secretRef і передать ці $KEY:VALUE як змінні оточення в контейнерах з LiteLLM.
ConfigMap для LiteLLM Proxy Config
Робимо окремим ресурсом – простіше читати values, простіше менеджити і апдейтити.
Поки мінімальний – треба просто запустити сервіс, тюнити будемо потім.
Створюємо файл templates/proxy-config.yaml з двома моделями – тут в мене вже трохи параметрів для моніторингу, про це в наступному пості:
apiVersion: v1
kind: ConfigMap
metadata:
name: litellm-proxy-config
data:
config.yaml: |
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
store_prompts_in_spend_logs: true
litellm_settings:
# Monitoring settings
require_auth_for_metrics_endpoint: false
prometheus_initialize_budget_metrics: true
# Enable the 'stream' label to split requests by streaming vs. non-streaming
prometheus_emit_stream_label: true
# Enable the 'end_user' label for cost tracking
enable_end_user_cost_tracking_prometheus_only: true
callbacks:
- prometheus
- otel
- arize
service_callback:
- prometheus_system
# Redis: Cache settings
cache: true
cache_params:
type: redis
model_list:
- model_name: gpt-5-5
litellm_params:
model: openai/gpt-5-5
api_key: os.environ/OPENAI_API_KEY
- model_name: claude-sonnet-4-6
litellm_params:
model: anthropic/claude-sonnet-4-6
api_key: os.environ/ANTHROPIC_API_KEY
Kubernetes WorkerNode Taints
В моєму сетапі для Kubernetes Pods з LiteLLM треба вказати tolerations, бо для critical services є окрема WorkerNode Group, див. Kubernetes: Pods та WorkerNodes – контроль розміщення подів на нодах.
Вже забув які задавав taints – перевіряємо з kubectl describe node:
$ kk describe node ip-10-0-59-76.ec2.internal | grep -A5 Taints
Taints: CriticalAddonsOnly=true:NoExecute
CriticalAddonsOnly=true:NoSchedule
Docker image tag для LiteLLM
Версії описані в Recent Releases, але там чомусь остання 1.87.0 – хоча вже є 1.87.1 (остання на момент написання цього поста).
Можна глянути appVersion в самому чарті версії 1.87.1:
$ helm show chart oci://ghcr.io/berriai/litellm-helm --version 1.87.1 | grep appVersion ... appVersion: 1.87.1
Створення власних Values
Весь файл values/ops/litellm-ops-1-33-values.yaml в мене поки вийшов таким:
# AWS region for External Secrets Operator (SecretStore pulls from Secrets Manager).
aws:
region: us-east-1
# Values for the upstream litellm-helm subchart (wrapper chart nests everything here).
litellm-helm:
# Number of LiteLLM proxy replicas.
replicaCount: 2
deploymentAnnotations:
# Restart pods automatically when mounted ConfigMaps or Secrets change.
reloader.stakater.com/auto: "true"
image:
# LiteLLM proxy image tag; should match the vendored chart version.
tag: "1.87.1"
serviceAccount:
# Use the namespace default ServiceAccount (no dedicated SA created by the chart).
create: false
name: ""
# K8s Secrets mounted as env vars (synced from AWS Secrets Manager via ExternalSecret).
environmentSecrets:
- litellm-secrets
# Master key for LiteLLM admin API and proxy authentication.
masterkeySecretName: litellm-secrets
masterkeySecretKey: LITELLM_MASTER_KEY
proxyConfigMap:
# Proxy config is provided by the wrapper chart (helm/templates/proxy-config.yaml).
create: false
name: litellm-proxy-config
db:
# Use existing Postgres instead of deploying a chart-managed database.
useExisting: true
deployStandalone: false
endpoint: db.monitoring.ops.example.co
database: ops_litellm_db
secret:
name: litellm-secrets
usernameKey: DATABASE_USERNAME
passwordKey: DATABASE_PASSWORD
redis:
enabled: true
architecture: standalone
image:
registry: docker.io
repository: bitnami/redis
tag: "latest"
ingress:
enabled: true
className: alb
annotations:
# Share an internal ALB with other ops-1-33 services.
alb.ingress.kubernetes.io/group.name: ops-1-33-internal-alb
# Route traffic directly to pod IPs (required for ALB on EKS).
alb.ingress.kubernetes.io/target-type: ip
# TLS certificate for aigw.ops.example.co (ACM, us-east-1).
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:492***148:certificate/5fe4cb67-5af5-49d6-99e0-eb2145b66390
hosts:
- host: aigw.ops.example.co
paths:
- path: /*
pathType: ImplementationSpecific
# Schedule on CriticalAddonsOnly nodes (system/add-on node group).
tolerations:
- key: CriticalAddonsOnly
value: "true"
operator: Equal
effect: NoSchedule
- key: CriticalAddonsOnly
value: "true"
operator: Equal
effect: NoExecute
pdb:
# Keep at least one pod available during voluntary disruptions (2 replicas total).
enabled: true
minAvailable: 1
Тут, як вище і планувалось:
- створюємо два Kubernetes Pods
- в
deploymentAnnotationsз Reloader задаємо автоматичний рестарт при змінах в ConfigMap (де будеproxy_config), див. Kubernetes: ConfigMap и Secrets – auto-reload данных в подах - з
environmentSecretsпередаємо ім’я Kubernetes Secret, який створить External Secrets Operator - в
masterkeySecretNameтаmasterkeySecretKeyпередаємо значення$LITELLM_MASTER_KEY - в
proxyConfigMapпередаємо власний Kubernetes ConfigMap з параметрами LiteLLM - в
dbописуємо підключення до існуючого AWS RDS та credentials для нього з того ж Kubernetes Secret від ESO - в
redisдодаємо запуск Redis із саб-чарту Bitnami (ага, ага) - в
ingress– в мене на проекті використовується shared ALB, передаємо черезalb.ingress.kubernetes.io/group.name, див. Kubernetes: єдиний AWS Load Balancer для різних Kubernetes Ingress - описуємо
tolerations - і обов’язково PodDisruptionBudget – див. Kubernetes: забезпечення High Availability для Pods
Redis та Docker image tag
Не знаю, як нині модно-маладьоджно крутити Redis в Kubernetes, бо я це останній раз робив років 5 тому і що до того, як Bitnami скурвилась внесла зміни.
Але з дефолтним мінімальним “redis.enabled=true” все завелось, єдиний нюанс був з Docker tag, бо по-дефолту тягнуло docker.io/bitnami/redis:7.2.4-debian-12-r9, якого чи то нема взагалі, чи він “закритий paywall” – я Bitnami давно не користуюсь, тому не дуже в курсі які там саме зміни були.
Тому зараз просто взяв @latest – поки система більше в PoC то можна і так. А якщо вже будемо йти в повноцінний продакшен – то копну в Redis окремо, або просто візьму AWS ElastiCache.
Створення Makefile
Спрощуємо собі локальне життя (сюди ж можна додати і таргети для CI/CD) – додаємо Makefile аби не писати кожен раз команди:
helm-pull-local: helm pull oci://ghcr.io/berriai/litellm-helm --version 1.87.1 -d charts/ helm-dependency-update: helm dependency update helm-template-ops-1-33: helm -n ops-litellm-ns template litellm \ . -f values/ops/litellm-ops-1-33-values.yaml helm-diff-ops-1-33: helm -n ops-litellm-ns diff upgrade --install litellm \ . -f values/ops/litellm-ops-1-33-values.yaml \ --dry-run=server helm-install-ops-1-33: helm -n ops-litellm-ns upgrade --install litellm \ . -f values/ops/litellm-ops-1-33-values.yaml \ --debug
І все разом тепер виглядає так:
$ tree . . ├── CLAUDE.md ├── helm │ ├── Chart.lock │ ├── charts │ │ └── litellm-helm-1.87.1.tgz │ ├── Chart.yaml │ ├── Makefile │ ├── templates │ │ ├── proxy-config.yaml │ │ └── secretstore.yml │ └── values │ └── ops │ └── litellm-ops-1-33-values.yaml └── README.md
Деплой в Kubernetes
Створюємо Kubernetes Namespace – зараз вручну, взагалі у нас це робиться з Terraform при створенні кластера:
$ kk create ns ops-litellm-ns
Перевіряємо, що чарт генериться нормально:
$ make helm-template-ops-1-33
helm -n ops-litellm-ns template litellm \
. -f values/ops/litellm-ops-1-33-values.yaml
...
---
# Source: litellm/templates/proxy-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: litellm-proxy-config
data:
config.yaml: |
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
model_list:
- model_name: gpt-5-5
litellm_params:
model: openai/gpt-5-5
api_key: os.environ/OPENAI_API_KEY
...
Встановлюємо:
$ make helm-install-ops-1-33
Перевіряємо поди:
$ kk get pod NAME READY STATUS RESTARTS AGE litellm-6d9bdbd689-6fl7q 0/1 ContainerCreating 0 22s litellm-6d9bdbd689-gh4dw 0/1 ContainerCreating 0 22s
Та Ingress:
$ kk get ingress NAME CLASS HOSTS ADDRESS PORTS AGE litellm alb aigw.ops.example.co internal-k8s-***.us-east-1.elb.amazonaws.com 80 33s
Чекаємо пару хвилин, перевіряємо, що DNS оновились:
$ dig aigw.ops.example.co +short 10.0.41.213 10.0.53.252
І відкриваємо дашборду LiteLLM:
Логінимось з admin та $LITELLM_MASTER_KEY, перевіряємо доступні моделі – обидві задані в proxy-config.yaml на місці:
Перевірка роботи LiteLLM з Claude Code
Цікаво було глянути як проксювати доступ до Anthropic через LiteLLM у Claude Code.
Вже десь далі, може, опишу як налаштовував наш Backend API та інші сервіси, хоча там все доволі просто – треба просто додати води додати base_url та замінити API-ключі.
Створення LiteLLM User та Virtual API Key з LiteLLM API
Створюємо нового юзера і його API Key:
$ curl -X POST https://aigw.ops.example.co/user/new \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_email": "[email protected]",
"user_role": "internal_user"
}'
Зберігаємо ключ – “key":"sk-CUn***wVg“.
Перевіряємо юзерів в LiteLLM:
Запуск Claude Code через LiteLLM
Тут просто треба задати пару змінних – ключ та ендпоінт:
$ export ANTHROPIC_API_KEY=sk-CUn***wVg $ export ANTHROPIC_BASE_URL=https://aigw.ops.example.co
І запускаємо claude.
Вибираємо модель, яка є в LiteLLM:
❯ /model ⎿ Set model to Sonnet 4.6 and saved as your default for new sessions
Робимо запит типу “How are you?” – і вуаля! Маємо трейс в LiteLLM:
Правда, на проектному акаунті Anthropic було zero USD 🙂
Але потім поповнили рахунок – все працює.
Monitoring та метрики до VictoriaMetrics
Тут дуже коротко, просто перевірити, що дані є. В наступному пості будемо розбиратись детальніше – думав описати в цьому, але він і так довгий вийшов.
Документація – Prometheus metrics та OpenTelemetry – Tracing LLMs with any observability tool, плюс трохи писав в попередньому пості в частині Monitoring, OpenTelemetry та Traces.
Включаємо ендпоінт /metrics для VictoriaMetrics:
litellm_settings: callbacks: - prometheus
LiteLLM /metrics authentication
В документації говориться, що “By default /metrics endpoint is unauthenticated” – але при деплої з цього чарту при доступі до метрик пише що “Unauthorized access to metrics endpoint” – хоча в values ніде параметрів не побачив.
Можна відключити явно з require_auth_for_metrics_endpoint=false – бо все одно Internal ALB, але можемо скористатись нагодою постворювати юзерів ще.
Створюємо нового read-only admin user – роль proxy_admin_viewer (див. User Roles), бо звичайному юзеру доступу до метрик все одно не дало:
curl -X POST https://aigw.ops.example.co/user/new \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_email": "[email protected]",
"user_role": "proxy_admin_viewer"
}'
Зберігаємо отриманий ключ до $LITELLM_RO_ADMIN_KEY в AWS Secret Store, оновлюємо деплой, перевіряємо метрики з header “Authorization: Bearer $LITELLM_RO_ADMIN_KEY“:
$ curl -s -H "Authorization: Bearer $LITELLM_RO_ADMIN_KEY" https://aigw.ops.example.co/metrics/ | tail # HELP litellm_check_batch_cost_jobs_processed_total Total number of batches successfully cost-tracked by CheckBatchCost # TYPE litellm_check_batch_cost_jobs_processed_total counter # HELP litellm_check_batch_cost_errors_total Total number of errors in CheckBatchCost by error type # TYPE litellm_check_batch_cost_errors_total counter # HELP litellm_check_batch_cost_last_run_timestamp Unix timestamp of the last CheckBatchCost job run # TYPE litellm_check_batch_cost_last_run_timestamp gauge litellm_check_batch_cost_last_run_timestamp 0.0 # HELP litellm_in_flight_requests Number of HTTP requests currently in-flight on this uvicorn worker # TYPE litellm_in_flight_requests gauge litellm_in_flight_requests 1.0
Тепер можна збирати до VictoriaMetrics.
vmagent та VMPodScrape
Є кілька варіантів:
- включити
serviceMonitorв values чарту LiteLLM – він створить Prometheus ServiceMonitor, а VictoriaMetrics Operator, якщо заданоoperator.disable_prometheus_converter=falseстворить VMServiceScrape (писав у VMServiceScrape з ServiceMonitor та VictoriaMetrics Prometheus Converter) - можемо просто задати
inlineScrapeConfigдляvmagentі вказатиstatic_configsабоkubernetes_sd_configs - або можемо просто створити
VMPodScrape
При inlineScrapeConfig нам static_configs не дуже підходить – бо LiteLLM має кілька подів, і метрики збирати треба з усіх, але просто для перевірки можна поки зробити так.
Додаємо новий job_name, bearer_token поки вказуємо явно, потім зробимо через сікрети включив require_auth_for_metrics_endpoint=false в параметрах LiteLLM:
...
- job_name: litellm
metrics_path: /metrics/
bearer_token: sk-0H0***Dyg
static_configs:
- targets: ["litellm.ops-litellm-ns.svc.cluster.local:4000"]
Деплоїмо і перевіряємо метрики в VictoriaMetrics:
Відомий system prompt на 40,000 токенів в Claude Code 🙂
Якщо робити власний VMPodScrape – то він виглядає так:
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMPodScrape
metadata:
name: litellm
namespace: ops-litellm-ns
spec:
selector:
matchLabels:
app.kubernetes.io/name: litellm
podMetricsEndpoints:
- port: http
path: /metrics/
Власне, все – сервіс готовий до налаштувань.
Працює вже з тиждень, потроху переключаємо наші production сервіси – поки що політ нормальний.
Далі треба буде додати моделей, налаштувати метрики і трейси, подивитись які цікаві алерти та дашборди в Grafana можна створити – наступний пост вже в чорнетках.
Корисні посилання
![]()







