Secrets Store CSI Driver: как секреты попадают в под

Каждый инженер, работающий с Kubernetes, поначалу думает одно и то же: секреты — это просто.

Создаёшь Kubernetes Secret, монтируешь его в под — и идёшь дальше. В небольших окружениях этот подход работает отлично. Пароль к базе данных, токен API, TLS-сертификат для NGINX — поначалу всё выглядит управляемым.

Но кластеры Kubernetes редко остаются маленькими.

Когда окружение разрастается, секреты начинают расползаться повсюду. Одни и те же учётные данные копируются по неймспейсам. CI/CD-пайплайны начинают динамически подставлять секреты. Команды создают разные версии для staging и production. Ротация учётных данных превращается в рискованную операцию, потому что никто до конца не уверен, какие воркнагрузки до сих пор зависят от старого значения.

Нарастающая сложность управления секретами

источник: google.com

В какой-то момент кто-то случайно коммитит секрет в Git — и с этого момента платформенная команда начинает переосмыслять весь подход.

Нативные Kubernetes Secrets хранятся в etcd. Несмотря на то что они выглядят абстрагированными за YAML-объектами, в конечном счёте они персистируются внутри самой control plane Kubernetes. По мере того как организации взрослеют, команды безопасности начинают задавать неудобные, но важные вопросы:

  • Где именно хранятся секреты?

  • Кто может их получить?

  • Как безопасно выполнять ротацию секретов?

  • Могут ли воркнагрузки получать доступ только к тем секретам, которые им действительно нужны?

  • Почему одни и те же секреты дублируются в нескольких кластерах и неймспейсах?

Вот тут инженеры, как правило, и открывают для себя Secrets Store CSI Driver.

Концепция Secrets Store CSI Driver

Традиционно архитектура выглядела примерно так:

Традиционная архитектура хранения секретов в Kubernetes

Секрет становится ещё одним Kubernetes-объектом: он сохраняется в etcd, реплицируется по control plane, кешируется внутри системы и управляется как любой другой ресурс кластера.

CSI-драйвер полностью меняет эту модель.

Новая архитектура доставки секретов через CSI-драйвер

Теперь Kubernetes перестаёт быть постоянным хранилищем секретов и превращается в механизм их доставки во время выполнения. Секреты остаются в специализированных системах управления секретами — таких как:

  • AWS Secrets Manager

  • Azure Key Vault

  • Google Secret Manager

  • HashiCorp Vault

— и извлекаются динамически только в момент фактического запуска пода.

На первый взгляд это незначительное архитектурное изменение, но с операционной точки зрения оно огромно.

Секреты централизуются. Ротация упрощается. Персистентность резко снижается. Воркнагрузки получают секреты только тогда, когда это нужно. И, пожалуй, самое важное: Kubernetes перестаёт быть долговременным хранилищем чувствительных учётных данных.

Чтобы понять, почему эта архитектура работает столь элегантно, сначала необходимо разобраться в самом CSI.

CSI — невидимый слой за современным хранилищем Kubernetes

Схема работы Container Storage Interface в Kubernetes

источник: x.com/twtayaan

В общих чертах, CSI (Container Storage Interface, интерфейс контейнерного хранилища) — это стандартизированный способ взаимодействия Kubernetes с внешними системами хранения данных. До появления CSI каждый вендор хранилищ создавал собственную интеграцию с Kubernetes. Каждая реализация вела себя по-своему, обновления давались болезненно, а сам Kubernetes нёс в себе глубоко встроенную логику, специфичную для различных хранилищ.

CSI решил эту проблему, введя универсальный интерфейс.

Kubernetes больше не нужно знать, как EBS работает изнутри, как Ceph выделяет хранилище или как NFS монтирует удалённые тома. Он просто отправляет стандартизированные CSI-вызовы, а драйвер берёт на себя детали реализации на стороне бэкенда.

Хитрость в том, что Secrets Store CSI Driver повторно использует именно этот фреймворк хранения — но для секретов.

Вместо того чтобы монтировать диски, он монтирует содержимое секретов.

Поначалу идея звучит странно, но оказывается исключительно мощной, потому что у Kubernetes уже есть очень зрелая система жизненного цикла томов. CSI-драйвер просто встраивается в этот жизненный цикл.

Большинство инженеров устанавливают Secrets Store CSI Driver через Helm и особо не задумываются о том, что работает под капотом. Но если заглянуть в поды, развёрнутые на узле, сразу бросается в глаза кое-что интересное.

Под драйвера — это не один контейнер.

Он содержит несколько контейнеров, работающих совместно:

- node-driver-registrar
- secrets-store
- liveness-probe

Самый непонятый компонент — как ни странно, один из самых важных — это node-driver-registrar. Чтобы понять, зачем нужен регистратор, необходимо разобраться в работе kubelet.

Kubelet — агент узла, оркестрирующий всё

Kubelet — это основной агент узла, работающий на каждом воркер-узле Kubernetes. Он управляет жизненным циклом подов, подготовкой томов, настройкой сети и взаимодействием со средой выполнения контейнеров. Когда стартует под, именно kubelet несёт ответственность за подготовку всего необходимого до того, как будут запущены контейнеры.

Роль kubelet на воркер-узле Kubernetes

источник: TechiesCamp

Но kubelet не может сам по себе обнаруживать CSI-драйверы. Нужен какой-то механизм, который «представит» драйвер kubelet’у, — и именно это делает контейнер-регистратор. Когда основной CSI-драйвер стартует, он открывает Unix domain socket: /csi/csi.sock

Этот сокет становится конечной точкой коммуникации для gRPC-вызовов CSI.

Контейнер-регистратор — то, что никто толком не объясняет

Контейнер-регистратор подключается к этому сокету изнутри, а затем выполняет процедуру регистрации в kubelet. Концептуально он сообщает kubelet следующее:

«На этом узле запущен CSI-драйвер с именем secrets-store.csi.k8s.io, и вот сокет, который ты должен использовать для взаимодействия с ним».

Последовательность действий выглядит так:

Схема регистрации CSI-драйвера в kubelet

После того как kubelet принимает регистрацию, он сохраняет метаданные в:

/var/lib/kubelet/plugins_registry/

С этого момента kubelet официально знает о существовании драйвера и умеет с ним общаться.

Этот маленький sidecar-регистратор — по сути, слой обнаружения между kubelet и экосистемой CSI.

Настоящий «мозг» системы — сам контейнер secrets-store.

Он реализует CSI Node API, с которыми взаимодействует kubelet. Такие операции, как:

  • NodePublishVolume

  • NodeUnpublishVolume

  • NodeGetInfo

— обрабатываются именно здесь.

Эти API изначально создавались для управления жизненным циклом хранилищ. Kubernetes использует их для присоединения и монтирования дисков. Увлекательная деталь состоит в том, что Secrets Store CSI Driver повторно использует эти же API для доставки секретов во время выполнения.

Когда под наконец стартует, kubelet начинает подготовку его томов. Если под содержит CSI-том, ссылающийся на:

        - csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: SecretProviderClassName
          name: secrets-store
            - mountPath: /mnt/secrets-store
              name: secrets-store
              readOnly: true

kubelet проверяет зарегистрированные CSI-драйверы и определяет, что за обработку этого запроса отвечает Secrets Store CSI Driver.

Kubelet вызывает: NodePublishVolume()

Именно в этот момент и начинается путь секрета к поду.

Запрос проходит через Unix-сокет по gRPC. CSI-драйвер получает запрос и немедленно обращается к Kubernetes API Server за ещё одним критически важным ресурсом — SecretProviderClass.

SecretProviderClass — схема за каждым монтированием

SecretProviderClass — это ресурс с областью видимости в пределах неймспейса (namespaced resource) в Secrets Store CSI Driver. Он передаёт конфигурацию драйвера и провайдер-специфичные параметры непосредственно CSI-драйверу.

Кастомный ресурс SecretProviderClass должен содержать следующие компоненты:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: my-provider
spec:
  provider: vault                             # допустимые значения: akeyless, azure, vault, gcp
  parameters:                                 # параметры, специфичные для провайдера

Этот CRD (Custom Resource Definition, определение кастомного ресурса) служит конфигурационной схемой для драйвера. Он сообщает системе:

  • какой провайдер использовать

  • какие объекты-секреты нужно получить

  • конфигурацию аутентификации

  • опциональное поведение синхронизации

Ниже приведены примеры для наиболее распространённых провайдеров.

AZURE

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: my-provider
  namespace: default
spec:
  provider: azure
  parameters:
    usePodIdentity: "false"
    useManagedIdentity: "false"
    keyvaultName: "$KEYVAULT_NAME"
    objects: |
      array:
        - |
          objectName: $SECRET_NAME
          objectType: secret
          objectVersion: $SECRET_VERSION
        - |
          objectName: $KEY_NAME
          objectType: key
          objectVersion: $KEY_VERSION
    tenantId: "$TENANT_ID"

GCP

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: gcp-secrets-provider
  namespace: default

spec:
  provider: gcp

  parameters:
    secrets: |
      - resourceName: "projects/PROJECT_ID/secrets/db-password/versions/latest"
        path: "db-password"

      - resourceName: "projects/PROJECT_ID/secrets/api-key/versions/latest"
        path: "api-key"

      - resourceName: "projects/PROJECT_ID/secrets/oauth-secret/versions/latest"
        path: "oauth-secret"

AWS

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: aws-secrets-provider
  namespace: default
spec:
  parameters:
    objects: |
      - objectName: "/prod/secrets/"
        objectType: "secretsmanager"
        jmesPath:
          - path: "MONGODB_URI"
            objectAlias: "MONGODB_ITOPS_URI"
          - path: "ELASTICSEARCH_HOSTS"
            objectAlias: "ELASTICSEARCH_HOSTS"
  provider: aws
  secretObjects:
    - data:
        - key: MONGODB_URI
          objectName: MONGODB_URI
        - key: ELASTICSEARCH_HOSTS
          objectName: ELASTICSEARCH_HOSTS
      secretName: kubernetes-secret
      type: Opaque

Сам CSI-драйвер не знает напрямую, как взаимодействовать с AWS Secrets Manager, Azure Key Vault или Google Secret Manager. Вместо этого для каждой платформы существуют отдельные плагины-провайдеры.

Плагин-провайдер — место, где живёт облачная логика

CSI-драйвер просто делегирует провайдер-специфичную работу.

Внутри архитектура теперь выглядит так:

Архитектура взаимодействия CSI-драйвера с плагинами провайдеров

Такое разделение крайне важно: оно сохраняет CSI-драйвер универсальным и при этом позволяет провайдерам развиваться независимо.

На этом этапе в дело вступает плагин-провайдер.

Механизмы аутентификации варьируются в зависимости от облачной платформы:

  • IRSA на AWS

  • Workload Identity на GCP

  • Managed Identity на Azure

Но концептуально процесс остаётся одинаковым.

Провайдер получает временные учётные данные, привязанные к идентификатору воркнагрузки самого пода. Это устраняет необходимость в статических учётных данных, встроенных в приложения.

Процесс аутентификации провайдера через идентификатор воркнагрузки

После успешной аутентификации провайдер получает запрошенный секрет из внешнего хранилища и возвращает его содержимое CSI-драйверу.

Распространённое заблуждение состоит в том, что раз Secrets Store CSI Driver использует тома Kubernetes, значит, он должен выделять постоянное хранилище — EBS, Ceph, SSD или локальные диски. На самом деле всё иначе. Одна из важнейших деталей реализации Secrets Store CSI Driver: секреты монтируются с использованием tmpfs — файловой системы, хранящейся в памяти. Секрет никогда не записывается на постоянное хранилище и существует только в оперативной памяти в течение жизни пода.

Секрет не записывается на постоянное хранилище — ни EBS, ни SSD, ни локальные диски. Он существует исключительно в RAM в течение жизни пода.

Монтирование секрета в tmpfs — файловую систему в памяти

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

Схема жизненного цикла секрета при удалении пода

Наконец, как только операция монтирования завершается успешно, kubelet завершает жизненный цикл тома и запускает контейнер приложения.

Один из наиболее интересных аспектов Secrets Store CSI Driver заключается в следующем: хотя сами смонтированные секреты эфемерны, Kubernetes всё равно нужен способ отслеживать, что произошло в процессе монтирования.

Представьте, с какими внутренними задачами сталкивается Kubernetes:

  • Какой под смонтировал какой секрет?

  • Какой SecretProviderClass был использован?

  • Какие версии объектов были получены?

  • Куда именно был смонтирован секрет?

  • Прошло ли монтирование успешно?

  • Что должно произойти при ротации?

  • Как работает очистка при удалении пода?

Именно здесь в архитектуру тихо входит ещё один CRD: SecretProviderClassPodStatus.

Что на самом деле делает SecretProviderClassPodStatus

Каждый раз, когда CSI-драйвер успешно монтирует секреты для пода, он создаёт ресурс SecretProviderClassPodStatus в том же неймспейсе.

Концептуально этот ресурс выполняет роль записи отслеживания состояния во время выполнения.

Он сообщает Kubernetes:

«Этот под смонтировал данные объекты-секреты, используя данный SecretProviderClass, и вот где на узле находится точка монтирования».

Что содержит ресурс

Реальный SecretProviderClassPodStatus содержит удивительно подробную информацию о состоянии во время выполнения.

Он отслеживает:

  • успешность монтирования

  • какие объекты-секреты были загружены

  • версии объектов

  • принадлежность поду

  • пути точек монтирования

  • связанный SecretProviderClass

Например:

status:
  mounted: true

  objects:
    - id: secret/db-secret
      version: a1b2c3d4

    - id: secret/api-key
      version: x9y8z7

  podName: payments-api

  secretProviderClassName: app-secrets

  targetPath: /var/lib/kubelet/pods/.../mount

Это становится крайне полезным при отладке.

Предположим, команда говорит:

«Наш под до сих пор использует старый пароль к базе данных».

Теперь можно проверить:

  • какая версия была смонтирована

  • произошла ли ротация

  • какой под использовал какую версию секрета

  • корректно ли обновилось монтирование.

Без этого CRD диагностика состояния секретов во время выполнения была бы значительно сложнее.

Элегантный механизм очистки

Ещё одна изящная деталь — управление владением (ownership management).

Ресурс SecretProviderClassPodStatus автоматически устанавливает под в качестве его owner reference.

Это означает:

Pod deleted → PodStatus resource deleted automatically

Это идеально согласуется с философией эфемерности CSI-драйвера. Система очищается сама. После удаления воркнагрузки не остаётся никаких устаревших ресурсов отслеживания.

А как быть с приложениями, которым всё ещё нужны переменные окружения?

На этом этапе мы осознаём нечто важное.

Монтируемые файлы с секретами отлично подходят для современных приложений, но многие существующие приложения по-прежнему ожидают переменные окружения (env vars). Это особенно характерно для:

  • сторонних Helm-чартов

  • легаси-приложений

  • Java-приложений

  • старых приложений по принципу двенадцати факторов

  • фреймворков, поддерживающих только переменные окружения

Поэтому закономерно возникает следующий вопрос:

«Может ли CSI-драйвер всё же создавать Kubernetes Secrets?»

Вопрос о поддержке переменных окружения в CSI-драйвере

Связь внешних секретов с нативными Kubernetes Secrets

CSI-драйвер может опционально синхронизировать смонтированные секреты в нативный объект Kubernetes Secret. Это создаёт интересную гибридную модель.

Внутри последовательность действий принимает следующий вид:

Схема гибридной синхронизации внешних секретов с Kubernetes Secrets

Как работает secretObjects

Внутри SecretProviderClass можно определить:

secretObjects:
  - secretName: kubernetes-secret
    type: Opaque

    data:
      - key: MONGODB_URI
        objectName: MONGODB_URI

      - key: ELASTICSEARCH_HOSTS
        objectName: ELASTICSEARCH_HOSTS

Это говорит CSI-драйверу:

«После монтирования этих внешних секретов также создай Kubernetes Secret, содержащий указанные ключи».

CSI-драйвер выполняет эту синхронизацию автоматически в ходе жизненного цикла монтирования.

Итого — использование синхронизированного секрета как переменной окружения

Как только Kubernetes Secret создан, воркнагрузки могут использовать его в обычном порядке.

Например:

env:
  - name: MONGODB_URI
    valueFrom:
      secretKeyRef:
        name: kubernetes-secret
        key: MONGODB_URI

  - name: ELASTICSEARCH_HOSTS
    valueFrom:
      secretKeyRef:
        name: kubernetes-secret
        key: ELASTICSEARCH_HOSTS

И именно в этот момент вся архитектура наконец складывается в единое целое:

Полная схема взаимодействия всех компонентов при монтировании секрета в под

То, что поначалу казалось «просто монтированием секрета в под», оказывается глубоко оркестрированным процессом, в котором участвуют kubelet, CSI lifecycle API, плагины-провайдеры, идентификация воркнагрузок, внешние хранилища секретов, логика согласования Kubernetes и эфемерные монтирования в памяти — и всё это работает вместе в режиме реального времени.

Общая диаграмма полного пути секрета от внешнего хранилища до пода

Глоссарий и дополнительные материалы

Если вы хотите глубже изучить внутреннее устройство Secrets Store CSI Driver, ниже приведены лучшие ресурсы — официальная документация, реализации провайдеров и практические статьи инженеров.

Концепции Secrets Store CSI Driver

Репозиторий Secrets Store CSI Driver на GitHub

Официальный репозиторий проекта: Репозиторий Secrets Store CSI Driver на GitHub

Документация CSI (Container Storage Interface)

Официальный репозиторий спецификации: Репозиторий спецификации CSI на GitHub

Secrets Store CSI Driver против External Secrets Operator

Хорошее сравнение по следующим аспектам:

  • монтирование во время выполнения

  • синхронизированные Kubernetes Secrets

  • операционные компромиссы

© 2026 meganuke