
Статья подготовлена вместе с экспертом
Сергей Новиков, менеджер продукта Managed Kubernetes

Представьте, что интернет-магазин масштабирует сервис загрузки фотографий товаров с 2 до 10 подов. Покупатель загружает фото в под №3, а под №7 это фото не видит — у каждой реплики свой локальный диск. Та же картина на медиаплатформе: ролик попадает в один под, а транскодировщик крутится в другом. Или в системе документооборота, где PDF-файл должен быть доступен всем репликам одновременно.
S3-CSI решает именно эту проблему: аддон монтирует S3-бакет как обычный каталог, и все поды кластера видят одни и те же файлы. Конфигурация через persistent volume не требует сетевой файловой системы и инфраструктуры поверх кластера — достаточно бакета Object Storage и нескольких строк манифеста.
Расскажем, как это устроено изнутри, когда S3-CSI подходит, а когда нет, и как подключить аддон в VK Cloud Managed Kubernetes с примерами YAML-конфигурации.

Сергей Новиков, менеджер продукта Managed Kubernetes
Kubernetes отлично масштабирует stateless-приложения: HTTP-сервис без внутреннего состояния можно размножить с replicas: 2 до replicas: 50 за секунды, и каждая реплика будет обрабатывать запросы независимо. Проблема начинается, когда приложение работает с файлами.
Как только несколько подов должны читать и писать одни и те же данные, хранение файлов в Kubernetes превращается в отдельную задачу. Загрузчики файлов, CMS, медиасервисы, системы документооборота — все они работают с пользовательским контентом и не укладываются в модель полностью stateless-приложений: им нужно общее файловое пространство, которое переживает рестарты и доступно сразу с нескольких нод.
EmptyDir и hostPath. EmptyDir создаётся вместе с подом и уничтожается вместе с ним: данные теряются при каждом рестарте. hostPath монтирует директорию хост-ноды в контейнер, что решает проблему эфемерности, но привязывает под к конкретной ноде — горизонтальное масштабирование невозможно, потому что планировщик не гарантирует попадание пода на ту же ноду.
PersistentVolume на блочном хранилище (режим RWO). Стандартный способ подключить постоянное хранилище. Блочные диски (аналоги AWS EBS, Google Persistent Disk) поддерживают только режим ReadWriteOnce: том монтируется к одной ноде одновременно. При горизонтальном масштабировании вторая реплика просто не получит доступ к тому же диску, если попала на другую ноду. Единственная точка записи — единственная точка отказа: если под с RWO-томом умирает, новая реплика должна дождаться отмонтирования диска, прежде чем получит к нему доступ.
ReadWriteMany через NFS или CephFS. Решение для по-настоящему разделяемого доступа: режим RWX позволяет монтировать том на нескольких нодах одновременно. Но цена такого решения — NFS-сервер или кластер Ceph нужно разворачивать и обслуживать отдельно. Ceph требует минимум 3 ноды (на каждой запускаются роли monitor, manager, OSD) и Rook-оператора для управления. Это отдельная операционная нагрузка, отдельный контур мониторинга, отдельная команда, которая понимает, почему упал OSD-демон. Плюс блочное и сетевое хранилище оплачивается по provisioned capacity: платите за зарезервированный объём, даже если диск заполнен наполовину.
CSI (Container Storage Interface) — стандартный интерфейс Kubernetes для подключения систем хранения данных. Драйвер состоит из двух компонентов: Controller Plugin (развёртывается как Deployment или StatefulSet) и Node Plugin (DaemonSet на каждой ноде кластера).
CSI-драйвер для объектного хранилища работает иначе, чем блочный: вместо протокола блочного хранилища (iSCSI, NVMe-oF) он монтирует S3-бакет в файловую систему через FUSE (Filesystem in Userspace). FUSE перехватывает системные вызовы файловой системы на уровне ядра и транслирует их в S3-операции (GET, PUT, LIST). Для приложения это выглядит как обычная директория — никакого S3-специфичного кода.
Существует несколько реализаций FUSE для S3: GeeseFS (рекомендованный вариант, баланс POSIX-совместимости и скорости), s3fs (широкая POSIX-поддержка, медленнее на параллельных операциях), goofys (максимальная скорость, минимум POSIX-совместимости). GeeseFS — клиент по умолчанию в драйвере csi-s3, у AWS-драйвера свой клиент Mountpoint
Поток работы: создание PersistentVolumeClaim запускает CSI Controller, который регистрирует PV и связывает его с бакетом Object Storage. При планировании пода Node Plugin на соответствующей ноде монтирует бакет через FUSE в указанный mountPath. С точки зрения контейнера по пути /data/uploads (или любому другому) находится обычная директория.
Принципиальное отличие от блочных дисков такое: S3-бакет может быть смонтирован одновременно на любом числе нод. Каждый под кластера, который ссылается на один PVC в режиме RWX, получает доступ к одному пространству файлов. При горизонтальном масштабировании через HPA или ручное kubectl scale новая реплика автоматически монтирует тот же бакет через Node Plugin на своей ноде и немедленно видит все файлы.
Отмонтировать диск и ждать, пока освободится предыдущий под, не нужно — как и ограничивать запись одной репликой. Все реплики работают с одним и тем же пространством объектов.
S3-бакет существует независимо от любых объектов Kubernetes. CrashLoopBackOff пода, rolling update Deployment, node drain перед обслуживанием, полное пересоздание кластера — файлы остаются в бакете нетронутыми. Node Plugin размонтирует бакет при завершении пода, но содержимое бакета никуда не девается. EmptyDir уничтожает данные вместе с подом, hostPath привязывает их к конкретной ноде и теряет при миграции. При использовании dynamic provisioning с reclaimPolicy: Retain бакет не удаляется даже при удалении PVC.
Масштабирование Kubernetes-приложений с файловыми данными обычно сводится к выбору между сложностью (NFS/Ceph) и ограничениями (RWO). S3-CSI убирает этот компромисс для сценариев с пользовательским контентом. При replicas: N в Deployment каждая из N реплик монтирует один и тот же бакет через свой Node Plugin. Файл, который загрузил пользователь через реплику №1, немедленно доступен репликам №2 и №3: физически он хранится в бакете, а не на локальном диске пода. HPA может добавить реплику за секунды, и она сразу включается в работу с полным доступом к данным.
Блочный диск в Kubernetes работает по модели provisioned capacity: вы заказываете диск на 200 ГБ и платите за 200 ГБ, даже если сейчас занято 40 ГБ. При оверпровизионинге — а его рекомендуют, чтобы не упираться в лимит в пиковые моменты — разрыв между оплачиваемым и используемым объёмом бывает значительным. VK Object Storage работает по модели pay-per-use: оплата только за фактически занятый объём. При росте данных это обычно оказывается ощутимо дешевле provisioned SSD-дисков, особенно для архивных данных, старых версий файлов и резервных копий.
Заполненный блочный диск — классическая операционная боль: поды останавливаются с ошибкой No space left on device, начинается цепочка ручных операций — ресайз PV в облаке, ресайз файловой системы внутри пода, перезапуск пода. S3-бакет не имеет физического потолка в привычном смысле: Object Storage масштабируется автоматически. Данные растут — бакет растёт вместе с ними, а операционные действия сводятся к настройке политик жизненного цикла объектов, а не к экстренному расширению диска в три часа ночи.
Rolling update Deployment с RWO-томом создаёт окно недоступности: пока старый под завершается и отмонтирует диск, новый под ждёт. При RWX через S3-CSI этой проблемы нет: все поды, старые и новые, работают с одним бакетом без очереди. То же при node drain: даже если нода уходит на обслуживание и все её поды переезжают на другие ноды, файлы в бакете никуда не деваются — Node Plugin на новой ноде монтирует тот же бакет, и под продолжает работу.
В VK Cloud Managed Kubernetes S3-CSI подключается как аддон: откройте панель управления, перейдите в раздел Managed Kubernetes, выберите кластер, затем раздел «Аддоны» и активируйте S3-CSI. Аддон доступен как при создании нового кластера, так и для уже работающего. После активации в кластере появится StorageClass для S3-CSI.
Подходит, когда бакет уже существует — например, данные уже там лежат или бакет создан заранее с нужными политиками. PersistentVolume создаётся вручную, с указанием имени бакета и данных доступа через Secret.
# Secret с данными доступа к Object Storage apiVersion: v1 kind: Secret metadata: name: s3-csi-secret namespace: production type: Opaque stringData: accessKey: "YOUR_ACCESS_KEY" secretKey: "YOUR_SECRET_KEY" --- # PersistentVolume, ссылающийся на существующий бакет apiVersion: v1 kind: PersistentVolume metadata: name: media-uploads-pv spec: capacity: storage: 1Ti # номинальное значение; реальный объём не ограничен accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: s3-csi csi: driver: s3.csi.aws.com volumeHandle: media-uploads-bucket volumeAttributes: bucketName: "media-uploads-bucket" endpoint: "https://hb.vkcs.cloud" nodeStageSecretRef: name: s3-csi-secret namespace: production --- # PVC, ссылающийся на PV apiVersion: v1 kind: PersistentVolumeClaim metadata: name: media-uploads-pvc namespace: production spec: accessModes: - ReadWriteMany resources: requests: storage: 1Ti storageClassName: s3-csi volumeName: media-uploads-pv
Статическое провизионирование стоит выбирать, когда бакет уже содержит данные, нужна точная настройка политик бакета (versioning, lifecycle) или идёт миграция с другого хранилища.
Достаточно объявить PVC — CSI Controller сам создаст PV и бакет в Object Storage. Удобно для новых проектов и автоматизации через Helm.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: user-uploads-pvc namespace: production spec: accessModes: - ReadWriteMany resources: requests: storage: 500Gi # номинальное значение для PV storageClassName: s3-csi
По умолчанию reclaimPolicy: Delete — бакет удаляется вместе с PVC. Для продакшена рекомендуется Retain (прописывается в StorageClass или PV явно): с ним данные переживают даже случайное удаление PVC.
apiVersion: apps/v1 kind: Deployment metadata: name: media-service namespace: production spec: replicas: 5 selector: matchLabels: app: media-service template: metadata: labels: app: media-service spec: containers: - name: media-service image: registry.example.com/media-service:latest volumeMounts: - name: uploads mountPath: /data/uploads # приложение работает с обычными файловыми путями volumes: - name: uploads persistentVolumeClaim: claimName: user-uploads-pvc
Все 5 реплик монтируют один бакет. Приложение в контейнере работает с директорией /data/uploads как с обычной файловой системой: читает, пишет новые файлы, создаёт поддиректории. Никакого S3 SDK в коде приложения не требуется.
S3-CSI — правильное решение для медиафайлов, пользовательских загрузок, документов, архивов, логов и любых сценариев с крупными файлами и преимущественно последовательным доступом. Неправильное — для баз данных, Spark shuffle, приложений с интенсивным file locking.
| Параметр | EmptyDir / hostPath | PV (RWO, блочный SSD) | PV (RWX, NFS/Ceph) | S3-CSI (аддон) |
| Доступ с нескольких подов | Нет | Нет (один под/нода) | Да | Да |
| Данные при перезапуске пода | Теряются / привязка к ноде | Сохраняются | Сохраняются | Сохраняются |
| Данные при пересоздании кластера | Теряются | Сохраняются | Сохраняются | Сохраняются |
| Масштабирование на N нод | Невозможно | Нет (RWO) | Да | Да |
| Оплата хранения | — | Provisioned capacity | Provisioned + инфраструктура Ceph/NFS | Pay-per-use |
| Ограничение объёма | Объём ноды | Зарезервированный размер диска | Зарезервированный размер | Практически нет |
| Операционная сложность | Минимальная | Низкая | Высокая (Ceph — от 3 нод) | Низкая (аддон) |
| Подходит для медиа/загрузок | Нет | Нет (при масштабировании) | Да | Да |
S3-CSI закрывает пробел между блочными дисками (просто, но только RWO) и сетевыми файловыми системами (RWX, но с операционной нагрузкой). Для приложений с пользовательским контентом — медиа, загрузки, документы, архивы — компромисс понятный: горизонтальное масштабирование без NFS, данные переживают рестарты и пересоздание кластера, оплата за фактический объём, а не за зарезервированный.
POSIX-ограничения через FUSE — это граница применимости сценария, а не дефект реализации. Базы данных, Spark shuffle, приложения с file locking выбирают другие решения. Для пользовательских файлов в веб-приложениях подходит S3-CS.
Аддон в VK Cloud Managed Kubernetes снимает операционный барьер: не нужно разворачивать Ceph, настраивать NFS-сервер, управлять отдельным хранилищным кластером. Активация через панель управления, YAML-манифест с PVC — и поды получают разделяемое хранилище за минуты.
Драйвер Kubernetes, который монтирует S3-бакет как обычный каталог внутри контейнера через FUSE. Приложение работает с обычными файловыми путями без S3-специфичного кода, а данные физически хранятся в объектном хранилище.
При росте числа реплик каждая новая реплика монтирует тот же S3-бакет через Node Plugin на своей ноде и немедленно видит все файлы. Синхронизация между подами не требуется: файл, записанный в одну реплику, доступен остальным мгновенно.
Блочный диск оплачивается по provisioned capacity — платите за зарезервированный объём вне зависимости от фактического использования. Object Storage работает по модели pay-per-use, оплачивая только фактически занятый объём. При неравномерном использовании или росте данных экономия бывает значительной.
Файлы остаются в S3-бакете нетронутыми. Бакет существует независимо от подов, нод и кластера. После рестарта подов Node Plugin монтирует тот же бакет, и все данные снова доступны без каких-либо действий со стороны операционной команды.
Панель управления VK Cloud → Managed Kubernetes → выбрать кластер → раздел «Аддоны» → активировать S3-CSI. Аддон доступен при создании нового кластера и для уже работающего. После активации появляется StorageClass, и можно создавать PVC с типом доступа ReadWriteMany.
Наши специалисты свяжутся с вами в ближайшее время и ответят на все вопросы.

Будем держать в курсе новостей и облачных трендов




