Как вы управляете секретами (API keys для LLM) в Kubernetes?
Краткий тезис
Управление секретами (API keys для LLM) в Kubernetes требует отказа от хранения ключей в открытом виде в манифестах или values.yaml Helm-чартов. Основной подход — использовать Secrets Operator|External Secrets Operator (ESO), который синхронизирует секреты из внешних хранилищ (Vault, Secrets Manager|Secrets Manager|AWS Secrets Manager|Secrets Manager, GCP Secret Manager) в объекты Secret внутри кластера. Ротация ключей выполняется путём пересоздания подов после обновления секрета. Это обеспечивает безопасность, audit‑логи и централизованное управление доступами.
1. Проблема: почему API keys нельзя хранить в открытом виде
Любое приложение, обращающееся к LLM (GPT, Claude, YandexGPT), нуждается в API key — токене аутентификации. Если ключ:
- Попадает в Git (даже в приватный репозиторий) → утечка через компрометацию репозитория, историю коммитов, CI‑логи.
- Лежит в ConfigMap или прямо в Deployment (в переменной окружения) → любой, кто получит доступ к поду или etcd, сможет прочитать ключ.
- Хранится в values.yaml Helm-чарта → ключ распространяется вместе с чартом и может быть прочитан при развёртывании.
Последствия финансовые потери (ключ используют злоумышленники), компрометация данных (LLM может быть вызвана извне), блокировка аккаунта провайдером.
2. Основные подходы к управлению секретами
| Подход | Описание | Плюсы | Минусы |
|---|---|---|---|
| Sealed Secrets (Bitnami) | Шифрование секретов в репозитории; расшифровка только внутри кластера. | Простота, работает офлайн, не требует внешнего хранилища. | Сложная ротация (нужно пересоздавать SealedSecret), нет автоматической синхронизации с внешними системами. |
| Helm Secrets (Mozilla sops) | Шифрование файлов secrets.yaml с помощью GPG/KMS. | Интеграция с Helm, хранение в Git. | Нет ротации, ручное управление ключами шифрования. |
| Vault CSI Provider | Монтирование секретов из HashiCorp Vault как томов CSI (Container Storage Interface). | Не сохраняет секреты в etcd, динамическая ротация (при изменении в Vault под перезапускается). | Сложная настройка, зависимость от Vault, требует операторского опыта. |
| External Secrets Operator (ESO) | Создание объекта ExternalSecret, который синхронизируется из внешнего хранилища (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Vault и др.). | Автоматическая синхронизация, ротация без перезапуска опционально, audit‑логи, поддержка многих бэкендов. | Требуется доступ из кластера к внешнему хранилищу, дополнительные метрики и RBAC. |
Рекомендуемый подход для AI‑систем Secrets Operator|External Secrets Operator (ESO), так как он даёт централизованное управление через GitOps (ArgoCD/Flux) и легко интегрируется с облачными провайдерами, где часто хранят ключи LLM.
3. External Secrets Operator: архитектура и работа
- ExternalSecret — пользовательский ресурс (CRD), который определяет, откуда и в какой Kubernetes Secret нужно скопировать данные.
- Provider — бэкенд-хранилище (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, и др.).
- ClusterSecretStore / SecretStore — конфигурация доступа к провайдеру (credentials, endpoint).
- Operator — контроллер, который в цикле
reconcile:- Читает ExternalSecret.
- Подключается к провайдеру (через SecretStore) и получает значение.
- Создаёт/обновляет Kubernetes Secret с этими данными.
- Если секрет изменился, можно настроить refreshInterval для периодической синхронизации.
Пример манифеста для AWS Secrets Manager:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-store
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
secretRef:
accessKeyIDSecretRef:
name: aws-credentials
key: access-key
secretAccessKeySecretRef:
name: aws-credentials
key: secret-key
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: llm-api-key
spec:
refreshInterval: "1h" # проверка изменений каждый час
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: llm-api-secret # имя создаваемого Kubernetes Secret
creationPolicy: Owner
data:
- secretKey: OPENAI_API_KEY # ключ внутри Kubernetes Secret
remoteRef:
key: /production/llm/openai-key # имя секрета в AWS
После применения ExternalSecret оператор создаст Secret с именем llm-api-secret и значением OPENAI_API_KEY.
4. Как под приложения получает ключ
В манифесте Deployment или Pod секрет монтируется как переменная окружения:
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-agent
spec:
template:
spec:
containers:
- name: agent
image: my/llm-agent:latest
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-api-secret
key: OPENAI_API_KEY
Никакой прямой ссылки на Vault или AWS — все данные уже в Kubernetes Secret. Ключ не хранится в манифесте Deployment, не передаётся через Git.
5. Ротация ключей и перезапуск подов
При смене API‑ключа во внешнем хранилище (например, в AWS Secrets Manager) ESO обновит Kubernetes Secret (через refreshInterval или при ручном триггере). Однако поды, использующие старый ключ, не узнают об изменении, пока не будут перезапущены.
Стратегии
- Вручную после обновления секрета выполнить
kubectl rollout restart deployment/llm-agent. - Автоматически (рекомендовано): использовать Reloader (от Stakater) или Keel — контроллеры, которые следят за изменениями объектов
Secret/ConfigMapи автоматически перезапускают связанные поды. Например, аннотация в Deployment:
metadata:
annotations:
reloader.stakater.com/match: "true"
spec:
template: ...
Когда Secret llm-api-secret обновляется, Reloader перекатывает Deployment.
- Rolling update через пересоздание если приложение не поддерживает hot‑reload, простейший способ — перезапуск подов.
Security best practice никогда не храните credentials для доступа к провайдеру (AWS access key, Vault token) в открытом виде. Для ESO такие учётные данные можно разместить в начальном Kubernetes Secret (который создан вручную или через Sealed Secrets). В production используют Service Account с IAM Roles (на AWS EKS) — для этого используют IRSA (IAM Roles for Service Accounts) или Pod Identity.
6. Сравнение: ESO vs Vault CSI vs Sealed Secrets
| Критерий | External Secrets Operator | Vault CSI Provider | Sealed Secrets |
|---|---|---|---|
| Поддержка множества бэкендов | ✅ AWS, GCP, Azure, Vault, Yandex, etc. | ❌ Только Vault | ❌ Нет внешнего бэкенда |
| Автоматическая синхронизация | ✅ Есть (refreshInterval) | ✅ Есть (монтирование тома) | ❌ Нет |
| Ротация без перезапуска подов | ❌ Требует Reloader или перезапуска | ✅ Если приложение читает файл тома, изменения подхватываются сразу | ❌ Нет |
| Хранение в Git | ❌ Нет (но можно хранить ExternalSecret‑манифест, так как он не содержит значений) | ❌ Нет | ✅ SealedSecret можно хранить в Git |
| Сложность настройки | Средняя | Высокая (нужен Vault) | Низкая |
| Безопасность | Высокая (секреты не в etcd при правильной настройке – через Vault? Нет, ESO создаёт K8s Secret, он хранится в etcd зашифрованным с помощью EncryptionConfiguration) | Высокая (секреты не в etcd) | Средняя (ключ расшифровки внутри кластера) |
Рекомендация для Agentic RAG в production используйте External Secrets Operator + Reloader. Для небольших проектов или демо — Sealed Secrets (проще, не требует внешнего хранилища, но ротация сложнее).
7. Безопасность на уровне Kubernetes
- Шифрование etcd включите
encryption-at-restдля хранения Secret‑ов в зашифрованном виде. - RBAC ограничьте доступ к ресурсам
SecretиExternalSecret(например, только namespace). - Audit: ESO генерирует события при синхронизации, их можно отправлять в SIEM.
- Network policy разрешите исходящий трафик от пода ESO только к внешнему хранилищу (AWS, Vault).
- Pod Security Standards запретите
privilegedконтейнеры, используйтеrunAsNonRoot.
8. Практический пример: интеграция с LLM-прокси
Рассмотрим архитектуру Agentic RAG с использованием LangChain или LlamaIndex. Агент вызывает LLM через HTTP‑эндпоинт (например, OpenAI API). API‑ключ получается из переменной окружения, как описано в п.4.
- DevOps инженер обновляет ключ в AWS Secrets Manager.
- ESO (через 1 час) синхронизирует новый ключ в K8s Secret
llm-api-secret. - Reloader (аннотация на Deployment) видит изменение и перекатывает поды агента.
- Агент стартует с новым ключом, старый ключ становится неактивным.
Потенциальный downtime время между обновлением секрета и перезапуском пода (максимум ~1 час + время старта пода). Чтобы минимизировать, установите refreshInterval: 5m и используйте liveness/readiness probes, проверяющие валидность ключа.
9. Продвинутые практики
- Push-модель через webhooks некоторые операторы (например, Confluent for Kubernetes) могут оповещать ESO о смене секрета, уменьшая задержку.
- Dynamic secrets (Vault): Vault может генерировать временные API‑ключи с коротким сроком жизни. ESO получает их и монтирует в под. Ротация практически мгновенная.
- Sidecar-прокси вместо перезапуска подов можно внедрить sidecar, который подхватывает изменения секрета (например, Vault Agent Sidecar). Но это сложнее, чем Reloader.
10. Резюме
| Шаг | Что делать | Почему |
|---|---|---|
| 1 | Установить External Secrets Operator | Централизованное управление, поддержка всех облаков |
| 2 | Настроить SecretStore (указать credentials) | Оператор получает доступ к внешнему хранилищу |
| 3 | Создать ExternalSecret для API‑ключа | Синхронизация из внешнего источника |
| 4 | В Deployment ссылаться на K8s Secret через secretKeyRef | Ключ не хранится в манифесте |
| 5 | Установить Reloader или настроить kubectl rollout restart | Применить новую версию ключа |
| 6 | Включить шифрование etcd и RBAC | Дополнительный слой безопасности |
Пет-проект для закрепления
Задача развернуть Agentic RAG‑сервис в локальном Kubernetes (Minikube/K3s), который использует API‑ключ OpenAI, хранящийся в AWS Secrets Manager (или локальном Vault в dev‑режиме) и управляемый через ESO.
Инструменты
- Minikube (или Kind)
- External Secrets Operator (установка через Helm)
- AWS Secrets Manager (можно использовать эмулятор, например,
localstackили просто создать Secret в AWS и использовать IAM‑пользователя с минимумом прав) - Reloader (установка через Helm)
- Простой Python‑сервер (Flask + LangChain), который выводит в лог полученный ключ.
Шаги:
- Разверните Minikube и установите ESO
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace. - Создайте IAM‑пользователя с правами
secretsmanager:GetSecretValueи сохраните Access Key в Kubernetes Secret:
kubectl create secret generic aws-credentials --from-literal=access-key=... --from-literal=secret-key=... - Создайте
SecretStore,ExternalSecret(манифест из п.3) и убедитесь, чтоSecretсоздан. - Напишите простой Flask‑сервер, который читает
OPENAI_API_KEYиз переменной окружения и выводит его длину в лог (чтобы не светить сам ключ). - Задеплойте Deployment с аннотацией Reloader и смонтированным Secret.
- Измените значение ключа в AWS Secrets Manager и через 5 минут проверьте, что лог пода обновился.
Ожидаемый результат вы научитесь управлять секретами без хранения их в Git, поймёте механизм синхронизации и ротации.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 253 | Как спроектировать архитектуру Agentic RAG? |
| 254 | Как организовать долговременную память для агентов? |
| 256 | Как мониторить логи и метрики агентов в Kubernetes? |
| 260 | Как обеспечить безопасность Multi‑tenant RAG? |
| 270 | Как настроить CI/CD для RAG‑сервиса с секретами? |
| 300 | Как использовать Vault для хранения embeddings? |
Навигация
- Предыдущий: 254
- Следующий: 256
- Индекс: 00. Индекс разборов