Привет, друзья! Если вы работаете с Kubernetes, то наверняка сталкивались с задачей организации постоянного хранилища для stateful-приложений. В ранних версиях Kubernetes это вызывало большие сложности: встраиваемые (in-tree) volume-плагины нужно было изменять в ядре кластера, из-за чего обновления замедлялись, а интеграция с внешними хранилищами усложнялась. На помощь пришел Container Storage Interface (CSI) — стандартный API, который позволяет провайдерам хранения писать драйверы вне дерева (out-of-tree), не внося изменения в ядро Kubernetes.
В этой статье разберём принципы CSI, посмотрим на его архитектуру и на то, как работают драйверы. Для примера возьмем типичный сценарий с NFS-драйвером, опираясь на официальную спецификацию CSI. Итак, пойдём по порядку.

Содержание:
Что такое CSI и зачем он нужен
CSI — это спецификация API, разработанная под эгидой CNCF (Cloud Native Computing Foundation), которая определяет, как контейнеризированные приложения взаимодействуют с блоковыми и файловыми хранилищами. Основная идея — отделить хранение от оркестратора, а заодно чтобы разработчики хранения не зависели от обновлений Kubernetes.
До появления CSI все плагины были «вшиты» в kubelet, что приводило к проблемам:
Релизы задерживались: новые фичи хранения ждали выхода очередной версии.
Поддержка усложнялась: каждый вендор модифицировал core.
CSI вышел в General Availability в Kubernetes 1.13 (2019 год) и теперь является единственным способом добавлять новые типы томов. Он использует gRPC для коммуникации с ядром Kubernetes, поддерживает идемпотентные вызовы (повторный вызов не меняет состояние) и фокусируется на безопасности: драйверам не нужен root-доступ к хосту.
Принципы работы CSI-драйверов
CSI строится на нескольких ключевых принципах, которые делают его гибким и масштабируемым:
Стандартизация интерфейса. Все драйверы реализуют один protobuf-контракт (csi.proto). Protobuf (Protocol Buffers) — это языконезависимый формат сериализации данных от Google, который описывает структуру сообщений и методы RPC в виде
.proto-файла. В CSI файлcsi.protoописывает все возможные запросы и ответы (CreateVolume,NodePublishVolumeи т.д.) с их полями и типами. Из этого файла генерируется код для разных языков (Go, Python, Java), что гарантирует совместимость между Kubernetes и любым драйвером. CSI предусматривает три сервиса:Identity — обязательный для всех драйверов, служит точкой входа для обнаружения возможностей.
Controller — опционален, отвечает за глобальные операции с томами.
Node — обязателен для драйверов с Node Plugin, управляет локальными операциями на нодах. Это позволяет Kubernetes взаимодействовать с любым хранилищем через единый API, без хардкода.
Разделение ответственности. Драйвер делится на Controller (управление ресурсами) и Node (локальные операции). Это минимизирует привилегии: Controller не трогает данные, Node только монтирует.
Sidecar-контейнеры для интеграции. Kubernetes не вызывает RPC напрямую. Вместо этого используются вспомогательные контейнеры (external-provisioner, external-attacher и т.д.), которые «слушают» API-сервер и делегируют вызовы драйверу.
Идемпотентность и безопасность. RPC можно вызывать повторно без вреда. Драйверы работают в изолированных подах, без доступа к «внутрянке» kubelet.
Поддержка полного жизненного цикла тома. От создания до удаления — весь цикл покрыт RPC-вызовами, включая управление снимками и расширение томов.
Благодаря этим принципам можно быстро добавлять поддержку новых хранилищ: написали драйвер, задеплоили его как Deployment/DaemonSet — и всё готово.
Архитектура CSI в Kubernetes
Архитектура CSI проста, но элегантна. Вот её основные компоненты:
Controller Plugin. Деплоится как Deployment и отвечает за глобальные операции: создание/удаление томов, подключение/отключение к нодам, управление снимками. Например, в AWS это вызывает EC2 API для создания EBS.
Sidecar-контейнеры контроллера:
external-provisioner. Слушает PVC, вызывает
CreateVolume/DeleteVolume. Также используетValidateVolumeCapabilitiesдля проверки совместимости.external-attacher. Подключает и отключает тома через
ControllerPublishVolume/ControllerUnpublishVolume.external-resizer. Расширяет тома с помощью
ControllerExpandVolume(если драйвер поддерживает capabilityEXPAND_VOLUME).external-snapshotter. Управляет снимками через
CreateSnapshot,DeleteSnapshotиListSnapshots(если драйвер это поддерживает).livenessprobe. Мониторинг здоровья через Probe RPC из Identity Service.
Node Plugin. DaemonSet на каждом рабочем узле. Монтирует/размонтирует тома локально. Регистрируется в kubelet через
node-driver-registrar. Важно: плагин работает на каждой ноде и имеет привилегированный доступ к файловой системе (на Linux требуетсяCAP_SYS_ADMIN). Это необходимо, чтобы выполнять монтирование и работать с блочными устройствами.Sidecar-контейнеры для плагина на рабочих узлах:
node-driver-registrar. Регистрирует Node Plugin в kubelet. Использует NodeGetInfo, чтобы получить информацию о ноде.
Все компоненты общаются по gRPC через Unix-сокеты. При этом Kubernetes ничего не знает о самом драйвере: sidecar-контейнеры — это как «телефон», через который ядро Kubernetes связывается с драйвером. За счет этого мы получаем полную стандартизацию и унификацию внешнего взаимодействия с драйвером.
Capabilities: как Kubernetes узнает о возможностях драйвера
Capabilities — это механизм, который позволяет драйверу сообщить Kubernetes о своих возможностях и поддерживаемых операциях. Это ключевой элемент архитектуры CSI, который обеспечивает динамическое обнаружение функциональности драйвера без необходимости хардкода в Kubernetes.
Как работают Capabilities
Каждый сервис CSI (Identity, Controller, Node) имеет свой метод для возврата списка поддерживаемых capabilities:
Identity Service.
GetPluginCapabilities— сообщает о поддержке Controller Service и топологических ограничениях.Controller Service.
ControllerGetCapabilities— перечисляет возможности контроллера (создание томов, снимки, расширение и т.д.).Node Service.
NodeGetCapabilities— описывает возможности ноды (подготовительное монтирование, статистика, расширение и т.д.).
Kubernetes вызывает эти методы при инициализации драйвера и использует полученную информацию для принятия решений о том, какие RPC можно вызывать, какие операции доступны и как обрабатывать запросы пользователей.
Зачем нужны Capabilities
Kubernetes использует capabilities для принятия решений на разных этапах работы.
Пример 1: Расширение тома
Пользователь запрашивает расширение PVC. Если драйвер сообщил о capability EXPAND_VOLUME в ControllerGetCapabilities, Kubernetes:
Вызовет
ControllerExpandVolumeдля расширения тома на стороне хранилища,Затем вызовет
NodeExpandVolumeдля расширения файловой системы на ноде.
Если capability отсутствует, Kubernetes вернет ошибку при попытке расширения.
Пример 2: Подготовительное монтирование томов
Если драйвер не сообщает о capability STAGE_UNSTAGE_VOLUME в NodeGetCapabilities:
Kubernetes пропустит вызовы NodeStageVolume и NodeUnstageVolume.
Будет вызывать только NodePublishVolume и NodeUnpublishVolume.
Это нормально для файловых систем типа NFS, которые не требуют подготовительного монтирования.
При��ер 3: Статистика томов
Если драйвер сообщает о capability GET_VOLUME_STATS:
Kubelet будет периодически вызывать
NodeGetVolumeStatsдля сбора метрик.Метрики отображаются в
kubectl describe pvc.Используются для мониторинга и принятия решений о расширении.
Важно: Capabilities определяются на этапе инициализации драйвера и не изменяются во время работы. Если драйвер не сообщает о какой-либо capability, Kubernetes не будет пытаться использовать соответствующую функциональность, даже если она технически реализована в драйвере.
Жизненный цикл тома
Жизненный цикл тома в CSI состоит из нескольких четко определённых этапов, каждый из которых реализуется через соответствующие RPC-вызовы. Полный жизненный цикл тома включает следующие этапы:
Provisioning (создание тома) — создание ресурса в бэкенд-хранилище.
Attach (подключение к ноде) — подключение тома к конкретной ноде кластера.
Stage (подготовительное монтирование) — подготовка тома на ноде (опционально).
Publish (монтирование в под) — монтирование тома в файловую систему пода.
Use (использование) — работа приложения с томом.
Unpublish (размонтирование из пода) — размонтирование тома из пода.
Unstage (очистка подготовительного монтирования) — очистка подготовительного пути (опционально).
Detach (отключение от ноды) — отключение тома от ноды.
Delete (удаление тома) — удаление ресурса из бэкенд-хранилища.
Давайте разберём каждый этап подробно.
Этап 1: Provisioning — создание тома
Цель. Создать том в бэкенд-хранилище и связать его с PVC в Kubernetes.
Шаг 1.1: Создание StorageClass
Перед созданием тома необходимо определить StorageClass, который указывает, какой CSI-драйвер использовать и какие параметры передавать при создании тома.
Пример StorageClass для NFS:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.example.com
share: /exports
mountPermissions: "0777"
allowVolumeExpansion: true
volumeBindingMode: ImmediateПример StorageClass для блочного устройства (AWS EBS):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-csi
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumerШаг 1.2: Создание PVC
Пользователь создает PersistentVolumeClaim, который запрашивает том определенного размера и параметров.
Пример PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes: - ReadWriteOnce
storageClassName: nfs-csi
resources:
requests:
storage: 10GiШаг 1.3: RPC-вызов CreateVolume
Когда external-provisioner (sidecar-контейнер) обнаруживает новый PVC, он вызывает RPC-метод CreateVolume у Controller Service.
Параметры CreateVolume:
name: уникальное имя тома (генерируется Kubernetes, например, pvc-12345678-1234-1234-1234-123456789012)
capacity_range: запрошенный размер тома (например, 10Gi).
volume_capabilities: требования к доступу:
access_mode: режим доступа на ноде / нодах.
access_type: MOUNT (файловая система) или BLOCK (блочное устройство).
parameters: параметры из StorageClass (например,
server,shareдля NFS).secrets: учетные данные для доступа к хранилищу (опционально).
content_source: источник для клонирования или восстановления из снимка (опционально).
Пример запроса CreateVolume для NFS:
CreateVolumeRequest {
name: "pvc-12345678-1234-1234-1234-123456789012"
capacity_range {
required_bytes: 10737418240 // 10Gi
}
volume_capabilities {
access_mode {
mode: SINGLE_NODE_WRITER
}
access_type {
mount {
fs_type: "nfs"
}
}
}
parameters {
key: "server"
value: "nfs-server.example.com"
}
parameters {
key: "share"
value: "/exports"
}
}Ответ CreateVolume:
CreateVolumeResponse {
volume {
capacity_bytes: 10737418240
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
volume_context {
key: "server"
value: "nfs-server.example.com"
}
volume_context {
key: "share"
value: "/exports/pvc-12345678"
}
}
}Что происходит на стороне драйвера:
Для NFS. Драйвер создаёт директорию на NFS-сервере (например, /exports/pvc-12345678) и настраивает права доступа.
Для блочного устройства (AWS EBS). Драйвер вызывает AWS API для создания EBS volume в указанной зоне доступности.
Пример команды для отладки (проверка созданного тома на NFS-сервере):
# На NFS-сервере
ls -la /exports/pvc-12345678
# Должна быть создана директория с соответствующими правамиШаг 1.4: Создание PV и привязка к PVC
После успешного вызова CreateVolume, external-provisioner создает PersistentVolume и привязывает его к PVC.
Пример созданного PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-12345678
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: nfs-csi
csi:
driver: nfs.csi.k8s.io
volumeHandle: nfs://nfs-server.example.com/exports/pvc-12345678
volumeAttributes:
server: nfs-server.example.com
share: /exports/pvc-12345678
claimRef:
name: my-pvc
namespace: defaultПроверка статуса:
kubectl get pvc my-pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE#
my-pvc Bound pv-12345678 10Gi RWO nfs-csi 5s
kubectl get pv pv-12345678 #
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE#
pv-12345678 10Gi RWO Delete Bound default/my-pvc nfs-csi 5sЭтап 2: Attach — подключение тома к ноде
Цель: Подключить том к конкретной ноде кластера, сделав его доступным для использования на этой ноде.
Шаг 2.1: Создание пода с использованием PVC
Пользователь создаёт под, который использует PVC через volumeMounts.
Пример пода:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: app
image: nginx:latest
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvcШаг 2.2: Планирование пода на ноду
kube-scheduler назначает под на конкретную ноду кластера. Для томов с volumeBindingMode: WaitForFirstConsumer планирование пода происходит до создания тома (том создаётся после планирования пода), и том создаётся в зоне доступности выбранной ноды. Для volumeBindingMode: Immediate том создаётся сразу при создании PVC, до планирования пода.
Шаг 2.3: RPC-вызов ControllerPublishVolume
После того как под запланирован на ноду, external-attacher (sidecar-контейнер) вызывает RPC-метод ControllerPublishVolume у Controller Service.
Параметры ControllerPublishVolume:
volume_id: идентификатор тома (из
CreateVolumeResponse).node_id: уникальный идентификатор ноды (из
NodeGetInfo).volume_capability: требования к доступу (из
CreateVolume).readonly: флаг только для чтения (опционально).
secrets: учетные данные для доступа к хранилищу (опционально).
volume_context: контекст тома (из
CreateVolumeResponse).
Пример запроса ControllerPublishVolume для NFS:
ControllerPublishVolumeRequest {
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
node_id: "node-1"
volume_capability {
access_mode {
mode: MULTI_NODE_MULTI_WRITER
}
access_type {
mount {
fs_type: "nfs"
}
}
}
readonly: false
}Ответ ControllerPublishVolume:
ControllerPublishVolumeResponse {
publish_context {
key: "devicePath"
value: "nfs://nfs-server.example.com/exports/pvc-12345678"
}
}Что происходит на стороне драйвера:
Для NFS. Это может быть виртуальной операцией (no-op), так как NFS доступен всем нодам одновременно. Драйвер может просто проверить доступность
share.Для блочного устройства (AWS EBS). Драйвер вызывает AWS API для подключения EBS volume к EC2 инстансу (например,
AttachVolume). На стороне EC2 инстанса появляется новое блочное устройство (например,/dev/xvdf).
Пример для блочного устройства (AWS EBS):
ControllerPublishVolumeRequest {
volume_id: "vol-1234567890abcdef0"
node_id: "i-0123456789abcdef0"
volume_capability {
access_mode {
mode: SINGLE_NODE_WRITER
}
access_type {
block {}
}
}
}
ControllerPublishVolumeResponse {
publish_context {
key: "devicePath"
value: "/dev/xvdf"
}
}Проверка подключения (для блочных устройств):
# На ноде, к которой подключен том lsblk
# Должно появиться новое устройство (например, /dev/xvdf)
# Для AWS EBS можно проверить через AWS CLI aws ec2 describe-volumes --volume-ids vol-1234567890abcdef0
# Должно показать, что том подключен к инстансуВажно. Для shared storage (NFS, CIFS) этап Attach может быть пропущен, если драйвер не сообщает о capability PUBLISH_UNPUBLISH_VOLUME в ControllerGetCapabilities. В этом случае Kubernetes пропускает вызовы ControllerPublishVolume и ControllerUnpublishVolume. Для блочных устройств это критический этап, так как том может быть подключен только к одной ноде за раз (для access mode ReadWriteOnce).
Этап 3: Stage — подготовительное монтирование (опционально)
Цель. Подготовить том на ноде для использования. Этот этап выполняется один раз на ноду, даже если том используется несколькими подами.
Важно. Этап Stage выполняется только если драйвер сообщает о capability STAGE_UNSTAGE_VOLUME в NodeGetCapabilities. Для файловых систем типа NFS этот этап может быть пропущен.
Шаг 3.1: RPC-вызов NodeStageVolume
Kubelet вызывает RPC-метод NodeStageVolume у Node Service.
Параметры NodeStageVolume:
volume_id: идентификатор тома.
staging_target_path: путь для подготовительного монтирования (например,
/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-12345678-1234-1234-1234-123456789012/globalmount). Формат пути:/var/lib/kubelet/plugins/kubernetes.io/csi/pv/<volume-handle>/globalmount, где<volume-handle>— этоvolume_idиз CreateVolumeResponse.volume_capability: требования к доступу.
publish_context: контекст из
ControllerPublishVolumeResponse(например,devicePathдля блочных устройств).secrets: учетные данные (опционально).
volume_context: контекст тома из
CreateVolumeResponse.
Пример запроса NodeStageVolume для блочного устройства:
NodeStageVolumeRequest {
volume_id: "vol-1234567890abcdef0"
staging_target_path: "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount"
volume_capability {
access_mode {
mode: SINGLE_NODE_WRITER
}
access_type {
mount {
fs_type: "ext4"
mount_flags: "noatime"
}
}
}
publish_context {
key: "devicePath"
value: "/dev/xvdf"
}
}Что происходит на стороне драйвера:
Для блочного устройства:
Драйвер проверяет, отформатировано ли устройство (например, через
blkid).Если не отформатировано, форматирует его (например,
mkfs.ext4 /dev/xvdf).Монтирует устройство в
staging path(например,mount -t ext4 /dev/xvdf /var/lib/kubelet/plugins/.../globalmount).
Для NFS. Этот этап обычно пропускается, так как NFS не требует подготовительного монтирования.
Пример команд, которые выполняет драйвер для блочного устройства:
# Проверка форматирования blkid /dev/xvdf
# Если не отформатировано, форматируем
mkfs.ext4 /dev/xvdf
# Монтирование в staging path
mkdir -p /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount
mount -t ext4 -o noatime /dev/xvdf /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmountПроверка подготовительного монтирования:
# На ноде mount | grep globalmount
# Должно показать монтирование блочного устройства в staging path
ls -la /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount
# Должна быть видна файловая система томаЭтап 4: Publish — монтирование тома в под
Цель. Смонтировать том в файловую систему пода, сделав его доступным для приложения.
Шаг 4.1: RPC-вызов NodePublishVolume
Kubelet вызывает RPC-метод NodePublishVolume у Node Service.
Параметры NodePublishVolume:
volume_id: идентификатор тома.
staging_target_path: путь подготовительного монтирования (если использовалось
NodeStageVolume).target_path: путь монтирования в под (например,
/var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mount).volume_capability: требования к доступу.
readonly: флаг только для чтения.
publish_context: контекст из
ControllerPublishVolumeResponse.secrets: учетные данные (опционально).
volume_context: контекст тома из
CreateVolumeResponse.
Пример запроса NodePublishVolume для NFS:
NodePublishVolumeRequest {
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
target_path: "/var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mount"
volume_capability {
access_mode {
mode: MULTI_NODE_MULTI_WRITER
}
access_type {
mount {
fs_type: "nfs"
mount_flags: "vers=4.1"
}
}
}
readonly: false
volume_context {
key: "server"
value: "nfs-server.example.com"
}
volume_context {
key: "share"
value: "/exports/pvc-12345678"
}
}Что происходит на стороне драйвера:
Для NFS:
# Драйвер выполняет команду монтирования mount -t nfs4 -o vers=4.1,noatime nfs-server.example.com:/exports/pvc-12345678 \ /var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mountДля блочного устройства:
# Драйвер создаёт bind mount из staging path в target path mount --bind /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount \ /var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mountПример запроса NodePublishVolume для блочного устройства:
NodePublishVolumeRequest {
volume_id: "vol-1234567890abcdef0"
staging_target_path: "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount"
target_path: "/var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mount"
volume_capability {
access_mode {
mode: SINGLE_NODE_WRITER
}
access_type {
mount {
fs_type: "ext4"
}
}
}
readonly: false
}Проверка монтирования в под:
# На ноде mount | grep pvc-12345678
# Должно показать монтирование тома в target path
# Внутри пода kubectl exec my-pod -- ls -la /data
# Должна быть видна файловая система тома
# Проверка записи kubectl exec my-pod -- sh -c "echo 'test' > /data/test.txt" kubectl exec my-pod -- cat /data/test.txt
# Должно вывести: testЭтап 5: Use — использование тома
Цель. Приложение использует том для чтения и записи данных.
После успешного монтирования том становится доступным в контейнере по пути, указанному в volumeMounts.mountPath. Приложение может читать и записывать данные в этот путь.
Пример использования в приложении:
# Python пример
with open('/data/app.log', 'a') as f:
f.write('Application started\n')
# Данные сохраняются на томе и будут доступны после перезапуска подаПроверка работы приложения:
# Запись данных kubectl exec my-pod -- sh -c "echo 'Hello from pod' > /data/hello.txt"
# Чтение данных kubectl exec my-pod -- cat /data/hello.txt
# Проверка статистики тома kubectl describe pvc my-pvc
# В разделе Status должны быть видны метрики использования (если драйвер поддерживает GET_VOLUME_STATS)Мониторинг использования тома:
Если драйвер поддерживает capability GET_VOLUME_STATS, kubelet периодически вызывает NodeGetVolumeStats для сбора метрик:
NodeGetVolumeStatsRequest {
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
volume_path: "/var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mount"
}
NodeGetVolumeStatsResponse {
usage {
total_bytes: 10737418240
available_bytes: 8589934592
used_bytes: 2147483648
}
volume_condition {
abnormal: false
message: "Volume is healthy"
}
}Эти метрики отображаются в kubectl describe pvc и используются для мониторинга в Prometheus.
Этап 6: Unpublish — размонтирование тома из пода
Цель. Размонтировать том из файловой системы пода при его удалении или volumeMount.
Шаг 6.1: RPC-вызов NodeUnpublishVolume
Когда под удаляется или volumeMount удаляется из пода, kubelet вызывает RPC-метод NodeUnpublishVolume у Node Service.
Параметры NodeUnpublishVolume:
volume_id: идентификатор тома.
target_path: путь монтирования в под.
Пример запроса NodeUnpublishVolume:
NodeUnpublishVolumeRequest {
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
target_path: "/var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mount"
}Что происходит на стороне драйвера:
Для NFS:
# Драйвер выполняет размонтирование umount /var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mountДля блочного устройства:
# Драйвер размонтирует bind mount umount /var/lib/kubelet/pods/pod-123/volumes/kubernetes.io~csi/pvc-12345678/mountВажно: Метод должен быть идемпотентным. Повторный вызов с тем же target_path не должен вызывать ошибку, если том уже размонтирован.
Проверка размонтирования:
# На ноде mount | grep pvc-12345678
# Не должно показывать монтирование в target path подаЭтап 7: Unstage — очистка подготовительного монтирования (опционально)
Цель. Очистить подготовительное монтирование тома, когда том больше не используется ни одним подом на ноде.
Шаг 7.1: RPC-вызов NodeUnstageVolume
После того как все поды, использующие том, удалены с ноды, kubelet вызывает RPC-метод NodeUnstageVolume у Node Service (если использовалось NodeStageVolume).
Параметры NodeUnstageVolume:
volume_id: идентификатор тома.
staging_target_path: путь подготовительного монтирования.
Пример запроса NodeUnstageVolume:
NodeUnstageVolumeRequest {
volume_id: "vol-1234567890abcdef0"
staging_target_path: "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount"
}Что происходит на стороне драйвера:
Для блочного устройства:
# Драйвер размонтирует staging path umount /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmount # Опционально: очистка директорииrmdir /var/lib/kubelet/plugins/kubernetes.io/csi/pv/vol-1234567890abcdef0/globalmountДля NFS. Этот этап обычно пропускается, так как подготовительное монтирование не использовалось.
Важно. Метод должен быть идемпотентным. Повторный вызов не должен вызывать ошибку, если подготовительное монтирование уже очищено.
Проверка очистки:
# На ноде mount | grep globalmount
# Не должно показывать монтирован��е в staging pathЭтап 8: Detach — отключение тома от ноды
Цель. Отключить том от ноды, когда том больше не используется на этой ноде.
Шаг 8.1: RPC-вызов ControllerUnpublishVolume
После размонтирования тома на ноде, external-attacher вызывает RPC-метод ControllerUnpublishVolume у Controller Service.
Параметры ControllerUnpublishVolume:
volume_id: идентификатор тома.
node_id: идентификатор ноды, от которой отключается том.
Пример запроса ControllerUnpublishVolume:
ControllerUnpublishVolumeRequest {
volume_id: "vol-1234567890abcdef0"
node_id: "i-0123456789abcdef0"
}Что происходит на стороне драйвера:
Для NFS. Если драйвер не сообщает о capability
PUBLISH_UNPUBLISH_VOLUME, этот этап пропускается. Если capability присутствует, операция может быть виртуальной (no-op), так как NFS доступен всем нодам одновременно.Для блочного устройства (AWS EBS). Драйвер вызывает AWS API для отключения EBS volume от EC2 инстанса (например,
DetachVolume).
Пример для блочного устройства (AWS EBS):
# Драйвер вызывает AWS API aws ec2 detach-volume --volume-id vol-1234567890abcdef0 --instance-id i-0123456789abcdef0
# На стороне EC2 инстанса блочное устройство исчезает lsblk
# Устройство /dev/xvdf больше не должно быть видноВажно. Метод должен быть идемпотентным. Повторный вызов не должен вызывать ошибку, если том уже отключен.
Проверка отключения (для блочных устройств):
# Для AWS EBS можно проверить через AWS CLI aws ec2 describe-volumes --volume-ids vol-1234567890abcdef0
# Должно показать, что том отключен от инстансаЭтап 9: Delete — удаление тома
Цель. Удалить том из бэкенд-хранилища при удалении PVC.
Шаг 9.1: Удаление PVC
Пользователь удаляет PersistentVolumeClaim:
kubectl delete pvc my-pvcШаг 9.2: RPC-вызов DeleteVolume
Когда external-provisioner обнаруживает удаление PVC, он вызывает RPC-метод DeleteVolume у Controller Service.
Параметры DeleteVolume:
volume_id: идентификатор тома для удаления.
secrets: учетные данные для доступа к хранилищу (опционально).
Пример запроса DeleteVolume:
DeleteVolumeRequest {
volume_id: "nfs://nfs-server.example.com/exports/pvc-12345678"
}Что происходит на стороне драйвера:
Для NFS. Драйвер удаляет директорию на NFS-сервере (например,
rm -rf /exports/pvc-12345678).Для блочного устройства (AWS EBS). Драйвер вызывает AWS API для удаления EBS volume (например,
DeleteVolume).
Пример для NFS:
# На NFS-сервереrm -rf /exports/pvc-12345678Пример для блочного устройства (AWS EBS):
# Драйвер вызывает AWS API aws ec2 delete-volume --volume-id vol-1234567890abcdef0Важно. Метод должен быть идемпотентным. Повторный вызов с тем же volume_id не должен вызывать ошибку, если том уже удален.
Проверка удаления:
# Проверка статуса PVC kubectl get pvc my-pvc
# Должно вернуть: Error from server (NotFound)
# Для NFS: проверка на сервереls -la /exports/pvc-12345678
# Должно вернуть: No such file or directory
# Для AWS EBS: проверка через AWS CLI aws ec2 describe-volumes --volume-ids vol-1234567890abcdef0
# Должно вернуть: InvalidVolume.NotFoundДополнительные операции жизненного цикла
Расширение тома (Volume Expansion)
Если пользователь изменяет размер PVC (например, с 10Gi на 20Gi), происходит процесс расширения тома:
1) ControllerExpandVolume (Controller Service): расширение тома на стороне хранилища.
Вызывается sidecar-контейнером
external-resizer.Ссылка:
ControllerExpandVolumeRPC.
2) NodeExpandVolume (Node Service): расширение файловой системы на ноде.
Вызывается kubelet после успешного
ControllerExpandVolume.Для
ext4: resize2fs /dev/xvdf.Ссылка:
NodeExpandVolumeRPC.
Пример расширения PVC:
# Изменение размера PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-csi
resources:
requests:
storage: 20Gi
# Увеличено с 10Gi до 20Gi
Снимки томов (Snapshots)
Для создания снимка тома используется следующий процесс:
1) CreateSnapshot (Controller Service): создание снимка тома.
Вызывается sidecar-контейнером
external-snapshotterпри созданииVolumeSnapshot.Ссылка:
CreateSnapshotRPC.
2) Восстановление из снимка: При создании PVC из VolumeSnapshot, CreateVolume получает content_source с snapshot_id
Пример создания снимка:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: my-snapshot
spec:
source:
persistentVolumeClaimName: my-pvcКлонирование томов
Для клонирования тома используется CreateVolume с параметром content_source, содержащим volume_id исходного тома.
Пример клонирования:
apiVersion: v1kind: PersistentVolumeClaim
metadata:
name: cloned-pvc
spec:
dataSource:
name: my-pvc
kind: PersistentVolumeClaim
accessModes:
- ReadWriteOnce
storageClassName: ebs-csi
resources:
requests:
storage: 10GiПодробнее про сервисы CSI
CSI определяет три сервиса: Identity (обязательный для всех), Controller (опционально, для глобальных операций), Node (обязательный для драйверов с Node Plugin).
Identity Service: точка входа в CSI
Identity Service — это первый и обязательный сервис, который должен реализовать каждый CSI-драйвер. Он вызывается при инициализации плагина и позволяет Kubernetes обнаружить возможности драйвера. Все три метода этого сервиса являются обязательными.
Важно. Identity Service должен быть доступен всегда, даже если Controller или Node Service временно недоступны. Это позволяет Kubernetes понять, что драйвер установлен, но может быть временно не гот��в.
Обязательные методы Controller Service
Возвращает базовую информацию о плагине:
name: уникальное имя драйвера (например,
nfs.csi.k8s.io).vendor_version: версия драйвера.
Этот метод вызывается первым при подключении к плагину. Kubernetes использует имя для идентификации драйвера в StorageClass и других ресурсах.
Сообщает Kubernetes, какие сервисы поддерживает плагин:
CONTROLLER_SERVICE. Плагин поддерживает Controller Service. На основе этого Kubernetes решает, нужно ли вызывать Controller Service или достаточно только Node Service (например, для headless-драйверов, которые работают только на нодах).VOLUME_ACCESSIBILITY_CONSTRAINTS. Плагин имеет топологические ограничения. Например, том доступен только в определенной зоне или регионе. Kubernetes будет учитывать это при планировании подов.
Проверка готовности плагина (health check). Возвращает READY или NOT_READY. Используется sidecar-контейнером livenessprobe для мониторинга здоровья драйвера.
Controller Service: управление жизненным циклом томов
Controller Service — это опциональный, но критически важный сервис для большинства CSI-драйверов. Он отвечает за глобальные операции с томами на уровне кластера: создание, удаление, подключение к нодам, снимки и расширение. Controller Service работает централизованно (обычно в Deployment) и не имеет прямого доступа к данным томов — он управляет только метаданными и конфигурацией хранилища.
Controller Service вызывается через sidecar-контейнеры Kubernetes (external-provisioner, external-attacher, external-resizer, external-snapshotter), которые отслеживают изменения в API-сервере и делегируют операции драйверу.
Создаёт новый том в хранилище. Это основной метод для динамического provisioning томов.
name: уникальное имя тома (генерируется Kubernetes).
capacity_range: запрошенный размер тома.
volume_capabilities: требования к доступу (
block/mount,read-only/read-write).parameters: параметры из StorageClass (например, тип диска, зона).
secrets: учетные данные для доступа к хранилищу (опционально).
content_source: источник для клонирования или восстановления из снимка (опционально).
Метод вызывается sidecar-контейнером external-provisioner при создании PVC. Драйвер должен создать том в бэкенд-хранилище и вернуть volume_id, который будет использоваться для всех последующих операций с этим томом.
Пример для NFS: Создание директории на NFS-сервере (например, /exports/my-vol-12345).
Удаляет том из хранилища. Вызывается при удалении PVC, когда том больше не используется.
volume_id: идентификатор тома для удаления.
Метод должен быть идемпотентным: повторный вызов с тем же volume_id не должен вызывать ошибку, если том уже удален. Вызывается sidecar-контейнером external-provisioner при удалении PVC.
Пример для NFS: Удаление директории на NFS-сервере.
Подключает том к конкретной ноде, делая его доступным для использования на этой ноде. Это критический шаг перед монтированием тома на ноде.
volume_id: идентификатор тома.
node_id: идентификатор ноды, к которой подключается том.
volume_capability: требования к доступу.
readonly: флаг только для чтения.
secrets: учетные данные (опционально).
Для shared storage (например, NFS) это может быть виртуальной операцией, но для блочных устройств (например, AWS EBS) это реальное подключение диска к инстансу. Вызывается sidecar-контейнером external-attacher после того, как под запланирован на ноду.
Пример для NFS: подготовка share (может быть no-op, так как NFS доступен всем нодам).
Отключает том от ноды. Вызывается когда под удаляется или мигрирует на другую ноду.
volume_id: идентификатор тома.
node_id: идентификатор ноды, от которой отключается том.
Метод должен быть идемпотентным. Вызывается sidecar-контейнером external-attacher после размонтирования тома на ноде.
Пример для NFS: Cleanup операций (может быть no-op).
Проверяет, поддерживает ли том запрошенные capabilities (например, access modes, volume mode).
volume_id: идентификатор тома (для существующих томов) или пустой (для проверки параметров).
volume_capabilities: запрошенные capabilities.
parameters: параметры из StorageClass.
Используется для валидации запросов перед созданием или использованием тома. Может вызываться как external-provisioner для проверки параметров StorageClass, так и другими компонентами для проверки совместимости.
Пример для NFS: проверка, что запрошенный access mode (ReadWriteMany, ReadOnlyMany) поддерживается NFS.
Возвращает список возможностей Controller Service. Это ключевой метод для обнаружения поддерживаемых операций. Kubernetes использует эту информацию для принятия решений: какие операции доступны, нужно ли вызывать определенные RPC и т.д.
CREATE_DELETE_VOLUME: Поддержка создания и удаления томов (динамическое provisioning).
PUBLISH_UNPUBLISH_VOLUME: Поддержка подключения/отключения томов к нодам.
LIST_VOLUMES: Поддержка получения списка томов.
GET_CAPACITY: Поддержка запроса доступной емкости.
CREATE_DELETE_SNAPSHOT: Поддержка создания и удаления снимков.
LIST_SNAPSHOTS: Поддержка получения списка снимков.
CLONE_VOLUME: Поддержка клонирования томов.
EXPAND_VOLUME: Поддержка расширения томов (используется
external-resizer).VOLUME_CONDITION: Поддержка условий тома (например, «том готов», «том поврежден»).
GET_VOLUME: Поддержка получения информации о конкретном томе
Важно. Controller Service опционален. Драйверы могут работать только с Node Service (headless-драйверы), но большинство production-драйверов требуют Controller Service для динамического provisioning и управления томами.
Опциональные методы Controller Service
Возвращает список всех томов, управляемых драйвером. Используется для отладки, мониторинга и административных задач.
max_entries: максимальное количество томов для возврата (опционально, для пагинации).
starting_token: токен для пагинации (опционально).
Метод должен быть реализован, если драйвер сообщает о capability LIST_VOLUMES в ControllerGetCapabilities. Может использоваться администраторами для получения списка всех томов в хранилище.
Пример для NFS: возврат списка всех директорий на NFS-сервере, которые были созданы через CSI.
Возвращает доступную ёмкость хранилища для создания новых томов. Используется Kubernetes при планировании создания томов и принятии решений о размещении.
volume_capabilities: требования к доступу (
block/mount,read-only/read-write).parameters: параметры из StorageClass (например, тип диска, зона).
Метод должен быть реализован, если драйвер сообщает о capability GET_CAPACITY в ControllerGetCapabilities. Kubernetes может использовать эту информацию для принятия решений о том, где создавать тома, и для предупреждения о нехватке места.
Пример для NFS: Возврат доступного пространства на NFS-сервере для определённого типа томов.
Создаёт снимок (snapshot) существующего тома. Используется для backup-операций и создания точек восстановления.
source_volume_id: идентификатор исходного тома.
name: уникальное имя снимка (генерируется Kubernetes).
secrets: учётные данные для доступа к хранилищу (опционально).
parameters: дополнительные параметры для создания снимка (опционально).
Метод должен быть реализован, если драйвер сообщает о capability CREATE_DELETE_SNAPSHOT в ControllerGetCapabilities. Вызывается sidecar-контейнером external-snapshotter при создании VolumeSnapshot. Возвращает snapshot_id, который можно использовать для восстановления тома.
Пример для NFS: Создание моментального снимка директории на NFS-сервере (например, через LVM snapshot или rsync).
Удаляет снимок тома. Используется для очистки старых снимков и освобождения места.
snapshot_id: идентификатор снимка для удаления.
secrets: учётные данные (опционально).
Метод должен быть реализован, если драйвер сообщает о capability CREATE_DELETE_SNAPSHOT в ControllerGetCapabilities. Метод должен быть идемпотентным: повторный вызов с тем же snapshot_id не должен вызывать ошибку, если снимок уже удалён. Вызывается sidecar-контейнером external-snapshotter при удалении VolumeSnapshot.
Пример для NFS: удаление моментального снимка директории на NFS-сервере.
Возвращает список снимков томов. Используется для управления снимками и получения информации о доступных точках восстановления.
snapshot_id: фильтр по идентификатору снимка (опционально).
source_volume_id: фильтр по исходному тому (опционально).
max_entries: максимальное количество снимков для возврата (опционально, для пагинации).
starting_token: токен для пагинации (опционально).
secrets: учетные данные (опционально).
Метод должен быть реализован, если драйвер сообщает о capability LIST_SNAPSHOTS в ControllerGetCapabilities. Может использоваться для получения списка всех снимков или фильтрации по исходному тому.
Пример для NFS: возврат списка всех моментальных снимков на NFS-сервере или снимков конкретного тома.
Расширяет том на стороне хранилища. Это первый этап процесса расширения тома, который выполняется до расширения файловой системы на ноде.
volume_id: идентификатор тома для расширения.
capacity_range: новый запрошенный размер тома.
secrets: учётные данные (опционально).
volume_capability: требования к доступу (опционально).
Метод должен быть реализован, если драйвер сообщает о capability EXPAND_VOLUME в ControllerGetCapabilities. Вызывается sidecar-контейнером external-resizer при изменении размера PVC. После успешного расширения тома на стороне хранилища, kubelet вызовет NodeExpandVolume для расширения файловой системы на ноде.
Пример для NFS: увеличение квоты или размера директории на NFS-сервере.
Пример для блочного устройства: расширение диска в облачном хранилище (например, AWS EBS volume).
Возвращает информацию о конкретном томе. Используется для отладки, мониторинга и получения статуса тома.
volume_id: идентификатор тома.
volume_attributes: атрибуты тома для фильтрации (опционально).
Метод должен быть реализован, если драйвер сообщает о capability GET_VOLUME в ControllerGetCapabilities. Возвращает детальную информацию о томе, включая его состояние, условия (volume conditions) и метаданные. Может использоваться для диагностики проблем с томами.
Пример для NFS: возврат информации о директории на NFS-сервере: размер, права доступа, статус.
Возвращает инкрементальные изменения метаданных тома для инкрементальных бэкапов. Позволяет эффективно отслеживать изменения между снимками.
volume_id: идентификатор тома.
base_snapshot_id: базовый снимок для сравнения.
target_snapshot_id: целевой снимок для сравнения (опционально).
secrets: учётные данные (опционально).
Метод используется для инкрементальных бэкапов, когда нужно получить только изменения между двумя снимками, а не полный снимок. Это позволяет экономить место и время при создании бэкапов.
Пример для NFS: возврат списка изменённых файлов между двумя моментальными снимками директории.
Модифицирует параметры существующего тома. Позволяет изменять свойства тома без его пересоздания.
volume_id: идентификатор тома для модификации.
mutations: список изменений для применения к тому.
secrets: учётные данные (опционально).
Метод позволяет изменять свойства тома, такие как параметры производительности, политики репликации или другие настройки, определённые драйвером. Это полезно для оптимизации работы томов без необходимости их пересоздания.
Пример для NFS: изменение параметров квоты, прав доступа или политик кэширования для директории на NFS-сервере.
Пример для блочного устройства: изменение типа диска (например, с gp2 на gp3 в AWS) или параметров IOPS.
Возвращает детальную информацию о конкретном снимке. Используется для получения статуса снимка и его метаданных.
snapshot_id: идентификатор снимка.
secrets: учётные данные (опционально).
Метод возвращает информацию о снимке, включая его состояние (готов, в процессе создания, ошибка), размер, дату создания и другие метаданные. Может использоваться для проверки статуса операции создания снимка или для получения деталей существующего снимка.
Пример для NFS: возврат информации о моментальном снимке директории: размер, дата создания, статус.
Возвращает информацию о выделенном пространстве для тома. Используется для мониторинга использования и оптимизации размещения данных.
volume_id: идентификатор тома.
secrets: учётные данные (опционально).
Метод возвращает информацию о том, сколько места фактически выделено для тома в хранилище. Это может отличаться от запрошенного размера из-за особенностей выделения места в хранилище (например, округление до ближайшего блока или минимального размера).
Пример для NFS: Возврат информации о фактически выделенном пространстве для директории на NFS-сервере (может отличаться от запрошенного размера из-за квот или округления).
Node Service: локальные операции на нодах
Node Service — это обязательный сервис для всех CSI-драйверов, которые работают с нодами кластера. Он отвечает за локальные операции на каждой ноде: монтирование/размонтирование томов, подготовительное монтирование томов, расширение файловых систем и сбор статистики. Node Service работает на каждой ноде кластера (обычно в DaemonSet) и имеет прямой доступ к файловой системе ноды для выполнения операций монтирования.
Node Service вызывается напрямую kubelet через Unix-сокет. Kubelet регистрирует Node Plugin через sidecar-контейнер node-driver-registrar, который использует NodeGetInfo для получения информации о ноде.
Обязательные методы Node Service
Подготавливает том для использования на ноде. Это первый этап монтирования, который выполняется один раз на ноду, даже если том используется несколькими подами.
volume_id: идентификатор тома.
staging_target_path: путь для подготовительного монтирования (например,
/var/lib/kubelet/plugins/kubernetes.io/csi/pv/volume-id/globalmount).volume_capability: требования к доступу (
block/mount,read-only/read-write).secrets: учётные данные для доступа к хранилищу (опционально).
volume_context: контекст тома из
CreateVolumeResponse.
Для блочных устройств этот метод обычно форматирует том (если нужно) и монтирует его в подготовительный путь. Для файловых систем (например, NFS) этот этап может быть пропущен, если драйвер не сообщает о capability STAGE_UNSTAGE_VOLUME.
Вызывается kubelet перед NodePublishVolume. Позволяет переиспользовать одно подготовительное монтирование для нескольких подов, что повышает эффективность.
Пример для NFS: для NFS подготовительное монтирование может быть пропущено, так как NFS можно монтировать напрямую в под.
Пример для блочного устройства: форматирование диска (mkfs.ext4) и монтирование в подготовительный путь (/var/lib/kubelet/plugins/.../globalmount).
Очищает подготовительное монтирование тома. Вызывается когда том больше не используется ни одним подом на ноде.
volume_id: идентификатор тома.
staging_target_path: путь подготовительного монтирования.
Метод должен быть идемпотентным. Вызывается kubelet после NodeUnpublishVolume, когда все поды, использующие том, удалены с ноды.
Пример для NFS: Cleanup операций (может быть no-op, если подготовительное монтирование не использовалось).
Пример для блочного устройства: размонтирование подготовительного пути (umount /var/lib/kubelet/plugins/.../globalmount).
Монтирует том в файловую систему пода. Это финальный этап, который делает том доступным внутри контейнера.
volume_id: идентификатор тома.
staging_target_path: путь подготовительного монтирования (если использовалось подготовительное монтирование).
target_path: путь монтирования в под (например,
/var/lib/kubelet/pods/pod-id/volumes/kubernetes.io~csi/volume-id/mount).volume_capability: требования к доступу.
readonly: флаг только для чтения.
secrets: учётные данные (опционально).
volume_context: контекст тома.
Для блочных устройств этот метод создаёт bind mount из подготовительного пути в target path. Для файловых систем (NFS, CIFS) метод монтирует том напрямую в target path.
Вызывается kubelet для каждого пода, который использует том. После успешного вызова том становится доступным в контейнере по указанному пути.
Пример для NFS: mount -t nfs server:/path /var/lib/kubelet/pods/.../mount
Пример для блочного устройства: mount --bind /var/lib/kubelet/plugins/.../globalmount /var/lib/kubelet/pods/.../mount
Размонтирует том из файловой системы пода. Вызывается когда под удаляется или том больше не нужен.
volume_id: идентификатор тома.
target_path: путь монтирования в под.
Метод должен быть идемпотентным. Вызывается kubelet при удалении пода или при удалении volumeMount из пода.
Пример для NFS: umount /var/lib/kubelet/pods/.../mount
Пример для блочного устройства: umount /var/lib/kubelet/pods/.../mount (размонтирование bind mount).
Возвращает список возможностей Node Service. Это ключевой метод для обнаружения поддерживаемых операций на ноде. Kubernetes использует эту информацию для принятия решений: какие операции доступны, нужно ли вызывать определенные RPC и т.д.
STAGE_UNSTAGE_VOLUME: поддержка подготовительного монтирования/размонтирования томов. Если отсутствует, Kubernetes будет пропускать этапы
NodeStageVolume/NodeUnstageVolume(например, для NFS это нормально).GET_VOLUME_STATS: поддержка получения статистики использования тома (используется kubelet для метрик).
EXPAND_VOLUME: поддержка расширения тома на ноде (после
ControllerExpandVolume).VOLUME_CONDITION: поддержка условий тома на ноде.
SINGLE_NODE_MULTI_WRITER: поддержка множественной записи с одной ноды (для томов с access mode
ReadWriteMany).
Kubelet использует эту информацию для принятия решений: нужно ли вызывать NodeStageVolume, доступна ли статистика, можно ли расширить том и т.д.
Важно. Если драйвер не сообщает о capability STAGE_UNSTAGE_VOLUME, kubelet будет вызывать только NodePublishVolume и NodeUnpublishVolume, пропуская этапы подготовительного монтирования. Это нормально для файловых систем типа NFS.
Возвращает информацию о ноде, на которой работает Node Plugin. Используется для регистрации драйвера в kubelet и для топологических ограничений.
node_id: уникальный идентификатор ноды (должен совпадать с node_id, используемым в
ControllerPublishVolume).max_volumes_per_node: максимальное количество томов, которые могут быть подключены к ноде (опционально, 0 означает без ограничений).
accessible_topology: топологические ограничения ноды (опционально, например, зона, регион).
Вызывается sidecar-контейнером node-driver-registrar при регистрации драйвера в kubelet. Kubelet использует эту информацию для:
регистрации драйвера в списке доступных драйверов;
проверки топологических ограничений при планировании подов;
ограничения количества томов на ноде.
Важно. Node Service работает на каждой ноде и имеет привилегированный доступ к файловой системе (требуется CAP_SYS_ADMIN на Linux). Это необходимо для выполнения операций монтирования и работы с блочными устройствами.
Опциональные методы Node Service
Возвращает статистику использования тома. Используется kubelet для сбора метрик о доступном и использованном пространстве тома.
volume_id: идентификатор тома.
volume_path: путь монтирования тома на ноде.
Метод должен быть реализован, если драйвер сообщает о capability GET_VOLUME_STATS в NodeGetCapabilities. Возвращает информацию об использовании тома:
available_bytes: доступное пространство.
total_bytes: общий размер тома.
used_bytes: использованное пространство.
inodes: статистика по inode (для файловых систем).
Эта информация используется для метрик в Prometheus (например, kubelet_volume_stats_*), отображения в kubectl describe pvc и принятия решений о расширении томов.
Пример для NFS: возврат статистики использования директории на NFS-сервере через df или statfs.
Пример для блочного устройства: возврат статистики использования блочного устройства через df или statfs.
Расширяет файловую систему тома на ноде. Это второй этап процесса расширения тома, который выполняется после ControllerExpandVolume.
volume_id: идентификатор тома для расширения.
volume_path: путь монтирования тома на ноде.
capacity_range: новый запрошенный размер тома.
staging_target_path: путь подготовительного монтирования (если использовалось).
volume_capability: требования к доступу (опционально).
Метод должен быть реализован, если драйвер сообщает о capability EXPAND_VOLUME в NodeGetCapabilities. Вызывается kubelet после успешного ControllerExpandVolume. Для блочных устройств метод расширяет файловую систему (например, resize2fs для ext4), для файловых систем может быть no-op, если расширение выполняется автоматически.
Пример для NFS: Расширение файловой системы может быть no-op, если расширение выполняется автоматически на стороне сервера.
Пример для блочного устройства: Расширение файловой системы ext4 через resize2fs /dev/sdX после расширения диска.
Опциональные функции: расширение, статистика и снимки
Хотя многие методы CSI являются опциональными, они критически важны для реального использования. Рассмотрим основные сценарии.
Расширение томов (Volume Expansion)
Процесс расширения тома состоит из двух этапов:
ControllerExpandVolume (Controller Service): расширение тома на стороне хранилища. Вызывается sidecar-контейнером
external-resizerпри изменении размера PVC. Драйвер должен сообщить о capabilityEXPAND_VOLUMEвControllerGetCapabilities.NodeExpandVolume (Node Service): расширение файловой системы на ноде (например,
resize2fsдля ext4). Вызывается kubelet после успешногоControllerExpandVolume. Драйвер должен сообщить о capabilityEXPAND_VOLUMEвNodeGetCapabilities.
Статистика томов (NodeGetVolumeStats)
Метод NodeGetVolumeStats возвращает информацию об использовании тома:
available_bytes: доступное пространство.
total_bytes: общий размер тома.
used_bytes: использованное пространство.
inodes: статистика по inode (для файловых систем).
Эта информация используется для:
метрик в Prometheus (например,
kubelet_volume_stats_*);отображения в
kubectl describe pvc;принятия решений о расширении томов.
Драйвер должен сообщить о capability GET_VOLUME_STATS в NodeGetCapabilities.
Снимки (Snapshots)
Полный цикл работы со снимками включает:
CreateSnapshot: создание снимка тома. Вызывается sidecar-контейнером external-snapshotter при создании
VolumeSnapshot. Возвращаетsnapshot_id, который можно использовать для восстановления.ListSnapshots: получение списка снимков (с фильтрацией по source volume или snapshot ID).
DeleteSnapshot: удаление снимка. Вызывается при удалении
VolumeSnapshot.Восстановление из снимка: при создании PVC из
VolumeSnapshot,CreateVolumeполучает source сsnapshot_id, и драйвер создаёт новый том на основе снимка.
Заключение: CSI в действии
CSI — это фундаментальный сдвиг в Kubernetes storage: от монолита к плагинам. С ним можно интегрировать Ceph, AWS EBS, Yandex Cloud или свой S3-совместимый бэкенд без боли, универсально и быстро.
Если же вы хотите максимально погрузиться, можно изучить спецификации:
CSI Specification (spec.md) — полная спецификация Container Storage Interface.
CSI Proto Definition (csi.proto) — определение protobuf-контракта для всех RPC методов.
P. S.
Читайте также в нашем блоге:
