Как вы управляете секретами (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:
    1. Читает ExternalSecret.
    2. Подключается к провайдеру (через SecretStore) и получает значение.
    3. Создаёт/обновляет Kubernetes Secret с этими данными.
    4. Если секрет изменился, можно настроить 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 OperatorVault CSI ProviderSealed 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.

Sequence

  1. DevOps инженер обновляет ключ в AWS Secrets Manager.
  2. ESO (через 1 час) синхронизирует новый ключ в K8s Secret llm-api-secret.
  3. Reloader (аннотация на Deployment) видит изменение и перекатывает поды агента.
  4. Агент стартует с новым ключом, старый ключ становится неактивным.

Потенциальный 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), который выводит в лог полученный ключ.

Шаги:

  1. Разверните Minikube и установите ESO helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace.
  2. Создайте IAM‑пользователя с правами secretsmanager:GetSecretValue и сохраните Access Key в Kubernetes Secret:
    kubectl create secret generic aws-credentials --from-literal=access-key=... --from-literal=secret-key=...
  3. Создайте SecretStore, ExternalSecret (манифест из п.3) и убедитесь, что Secret создан.
  4. Напишите простой Flask‑сервер, который читает OPENAI_API_KEY из переменной окружения и выводит его длину в лог (чтобы не светить сам ключ).
  5. Задеплойте Deployment с аннотацией Reloader и смонтированным Secret.
  6. Измените значение ключа в AWS Secrets Manager и через 5 минут проверьте, что лог пода обновился.

Ожидаемый результат вы научитесь управлять секретами без хранения их в Git, поймёте механизм синхронизации и ротации.

Связь с другими вопросами

ВопросТема
253Как спроектировать архитектуру Agentic RAG?
254Как организовать долговременную память для агентов?
256Как мониторить логи и метрики агентов в Kubernetes?
260Как обеспечить безопасность Multi‑tenant RAG?
270Как настроить CI/CD для RAG‑сервиса с секретами?
300Как использовать Vault для хранения embeddings?

Навигация