Как вы управляете секретами (API keys для LLM) в Kubernetes?

Краткий тезис

Управление секретами (API‑keys, токены доступа) в Kubernetes требует строгой изоляции от кода и конфигураций. Основной подход — использование Secrets Operator|External Secrets Operator (ESO) для синхронизации ключей из внешнего хранилища (Vault, Secrets Manager|Secrets Manager|AWS Secrets Manager|Secrets Manager) напрямую в Kubernetes Secrets. Никогда не храните ключи в values.yaml, ConfigMap или в репозитории кода. Ротация секретов, разграничение доступа с помощью RBAC и аудит всех запросов — обязательные элементы безопасности.


1. Проблема: почему API‑keys — особая зона риска в Kubernetes

API‑keys для LLM (например, OpenAI, Anthropic) — это критичные учётные данные. Если они скомпрометированы, злоумышленник может:

  • генерировать ответы от вашего имени, тратя ваш бюджет;
  • получить доступ к приватным данным, если LLM используется в приложении.

В Kubernetes стандартный объект Secret хранит данные в base64 — это не шифрование, только кодирование. Без дополнительных мер любой, у кого есть доступ к etcd или права get secret, может прочитать ключи.

Черновик подсказывает:

  • Не хранить секреты в values.yaml или в коде.
  • Использовать External Secrets Operator.
  • Ротация через ESO.
  • Доступ через RBAC.
  • Аудит.

Расширим каждый пункт.


2. Инструменты для управления секретами в Kubernetes

ИнструментОписаниеПлюсыМинусы
External Secrets Operator (ESO)Синхронизирует секреты из внешнего провайдера (Vault, AWS, GCP, Azure) в Kubernetes Secrets.Бесплатный, облачный/on‑prem, автоматическая ротация, CRD.Требуется настройка провайдера.
Secrets Store CSI DriverМонтирует секреты как volume в Pod.Нет копирования секретов в etcd, можно использовать с любым провайдером.Сложнее в настройке, не все приложения поддерживают чтение из файлов.
Vault Agent InjectorИнжектирует sidecar, который разворачивает секреты в Pod.Интеграция с Vault, динамические секреты.Vendor‑lock, дополнительный ресурс sidecar.
Встроенные Kubernetes SecretsОбъект Secret.Встроено, просто.Base64, сложная ротация, нет внешнего источника.

Рекомендация: для большинства production‑систем используйте ESO + HashiCorp Vault или AWS Secrets Manager — это даёт централизованное хранение, ротацию и аудит.


3. Термин: External Secrets Operator (ESO)

External Secrets Operator (ESO) — это Custom Resource Definition (CRD) для Kubernetes, который синхронизирует секреты из внешних источников в объекты Secret. ESO запускается как контроллер в кластере.

Как он работает:

  1. Вы создаёте ресурс ExternalSecret, в котором указываете провайдера (например, Vault) и ключ для загрузки.
  2. ESO читает значение из провайдера, создаёт или обновляет Kubernetes Secret с этим значением.
  3. При изменении секрета в провайдере ESO автоматически обновляет Secret в кластере (если настроен refreshInterval).

Пример манифеста для ESO с Vault:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: llm-api-key
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: openai-key          # имя создаваемого Kubernetes Secret
    creationPolicy: Owner
  data:
  - secretKey: api-key        # ключ внутри создаваемого Secret
    remoteRef:
      key: secret/data/llm    # путь в Vault
      property: openai_api_key

После применения этого манифеста ESO создаст Secret/openai-key с ключом api-key.


4. Термин: SecretStore и ClusterSecretStore

SecretStore — ресурс ESO, определяющий, как подключиться к внешнему хранилищу (Vault, AWS Secrets Manager, etc.). ClusterSecretStore работает на уровне кластера (не namespace).

Пример для AWS Secrets Manager:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-store
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        jwt:
          serviceAccountRef:
            name: my-service-account

Рекомендуется использовать ClusterSecretStore с IRSA (IAM Roles for Service Accounts) в AWS — это связывает сервис‑аккаунт Pod с IAM‑ролью без встроенных ключей AWS.


5. Ротация секретов (Rotation)

Ротация — периодическая смена API‑keys для снижения риска компрометации. ESO поддерживает автоматическую ротацию:

  • Вы задаёте refreshInterval (например, 1h).
  • ESO каждые refreshInterval проверяет, изменился ли секрет во внешнем хранилище.
  • Если изменился — ESO обновляет соответствующий Kubernetes Secret.
  • Pod, который использует этот Secret, должен перезагрузиться, чтобы подхватить новое значение.

Проблема: простой Kubernetes не перезапускает Pod при изменении Secret. Решения:

  • Stakater Reloader — наблюдатель, который при изменении Secret перезапускает Deployment.
  • Rolling update вручную.
  • Secrets Store CSI Driver — секрет монтируется как том, Pod видит изменения без перезапуска (если приложение читает файл повторно).

Пример с Reloader:

metadata:
  annotations:
    stakater.com/reload-on-change: "true"

6. Разграничение доступа: RBAC, ServiceAccount

RBAC (Role‑Based Access Control) в Kubernetes позволяет ограничить, какие Pod могут читать конкретные Secret.

Best practice:

  • Создайте отдельный ServiceAccount для Pod, которому нужен доступ к LLM‑ключам.
  • Создайте Role с правами get, list, watch на нужный Secret.
  • Создайте RoleBinding, привязывающую ServiceAccount к Role.
  • В Pod укажите serviceAccountName.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: llm-app-sa
---

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["openai-key"]
  verbs: ["get", "watch"]
---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: llm-app-read-key
subjects:
- kind: ServiceAccount
  name: llm-app-sa
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

Никакой другой Pod (кроме этого ServiceAccount) не сможет прочитать ключ.


7. Аудит доступа к секретам

Аудит позволяет отследить, кто и когда запрашивал секрет. Для этого:

  • Включите аудитные логи Kubernetes API‑сервера (уровень RequestResponse).
  • Внешнее хранилище (Vault, AWS Secrets Manager) предоставляет собственные логи доступа.
  • Используйте инструменты вроде Falco для обнаружения подозрительного доступа.

Пример настройки аудита в Kubernetes:

apiVersion: apiserver.config.k8s.io/v1
kind: AuditConfiguration
metadata:
  name: audit-config
rules:
- level: RequestResponse
  resources:
  - group: ""
    resources: ["secrets"]

Логи нужно отправлять в централизованную систему (Elasticsearch, Splunk) для анализа.


8. Практическая реализация в Agentic RAG‑системе

Предположим, у нас есть микросервис llm‑inference, который вызывает API OpenAI. Нам нужно передать ему ключ.

Шаги:

  1. Создайте секрет во внешнем хранилище: например, в Vault secret/data/llm с ключом openai_api_key.
  2. Установите ESO в кластер (helm install external-secrets ...).
  3. Определите SecretStore (ссылку на Vault) или ClusterSecretStore.
  4. Создайте ExternalSecret, который будет синхронизировать openai_api_key в Kubernetes Secret openai-key.
  5. Настройте ServiceAccount, Role, RoleBinding для вашего Pod.
  6. В Pod используйте монтирование Secret как переменные окружения:
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          serviceAccountName: llm-app-sa
          containers:
          - name: llm-inference
            env:
            - name: OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: openai-key
                  key: api-key
    
  7. Добавьте Reloader для автоматического перезапуска при смене ключа.

Почему не хранить в env в манифесте? — Манифест часто хранится в Git (GitOps), и ключ попадёт в историю коммитов.


9. Альтернативы и их ограничения

  • helm secrets / sops — шифрует values.yaml, но расшифровка требует доступа к ключам вне кластера. Подходит для GitOps, но не решает ротацию.
  • Vault Agent Sidecar — хорош для динамических секретов, но усложняет архитектуру.
  • Sealed Secrets by Bitnami — шифрует Secret в Git, расшифровывается контроллером в кластере. Удобно, но не централизованное хранение.

Для production с Agentic RAG (где может быть несколько сервисов с LLM‑ключами) лучший выбор — ESO + Vault.


10. Типичные ошибки и как их избежать

ОшибкаПоследствияРешение
Хранение ключа в ConfigMap или env в Docker‑образеКлюч виден всем, кто имеет доступ к образу или логамИспользовать Secret и монтирование
Не задан refreshInterval в ESOСекрет не обновляется автоматическиУказать refreshInterval
Раздача прав get secret на уровне кластераЛюбой разработчик может читать ключиИспользовать RBAC с resourceNames
Отсутствие аудитаНевозможно узнать, кто украл ключВключить аудит и настроить мониторинг
Ключ передаётся через env Pod без перезапуска при сменеPod использует старый ключ до перезапускаИспользовать Reloader или CSI driver

Пет-проект для закрепления

Задача: Развернуть локальный Minikube + Vault в dev‑режиме, настроить ESO для синхронизации фейкового OpenAI‑ключа и развернуть небольшое приложение (например, FastAPI), которое вызывает OpenAI API через смонтированный секрет.

Инструменты:

Шаги:

  1. Запустите Minikube: minikube start.
  2. Установите Vault через Helm.
  3. Включите Vault в dev‑режиме (vault server -dev или через helm с server.dev.enabled=true).
  4. Положите в Vault ключ: vault kv put secret/llm openai_api_key=sk-test123.
  5. Установите ESO: helm repo add external-secrets https://charts.external-secrets.io && helm install external-secrets external-secrets/external-secrets.
  6. Создайте SecretStore, указывающий на Vault (с нужным токеном).
  7. Создайте ExternalSecret, как в примере выше.
  8. Разверните простое FastAPI приложение, которое читает OPENAI_API_KEY из окружения и делает тестовый запрос (можно к mock‑серверу, чтобы не тратить реальные деньги).
  9. Настройте ServiceAccount, Role, RoleBinding.
  10. Проверьте, что ключ доступен только вашему Pod.

Ожидаемый результат: Приложение успешно запускается, видит ключ, а любой другой Pod не может его прочитать. При изменении ключа в Vault через refreshInterval (или вручную) секрет обновляется, а Pod с Reloader перезапускается.


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

ВопросТема
420Общая архитектура Agentic RAG, где требуются API‑ключи
421Память агентов, где тоже могут быть секреты для внешних инструментов
419Тестирование, включая проверку доступа к секретам
400Определение Agentic RAG, где LLM используется как агент
423Rate limits — тоже часть управления API‑ключами (квоты)
410Многопоточные системы, где секреты нужно распределять

Навигация