Зачем абстракция PV/PVC
Pod в k8s может умереть и переехать на другую ноду. Если данные пишутся в локальный диск pod'а, они теряются. Нужен persistent volume: storage, который переживает pod и доступен новым pod'ам где бы они не запустились (или хотя бы на тех же нодах).
Причём application-team не должна знать, какой именно storage под капотом, EBS, GCE PD, Ceph, NFS, vSAN. Отсюда два слоя:
- PersistentVolume (PV), кусок реального хранилища, описан в kube
- PersistentVolumeClaim (PVC), заявка pod'а: «дай мне 10 Gi с режимом RWO». kube-scheduler сматчит её с подходящим PV.
app dev пишет PVC ──► kube-controller-manager ──► binding к PV
│
└─► pod монтирует
Static vs Dynamic provisioning
Static, администратор заранее создаёт PV, потом приходят PVC и матчатся. Подход 2014 года, сейчас почти нигде. Используется только для legacy-storage без CSI-драйвера.
Dynamic, есть StorageClass (шаблон с параметрами provisioner'а).
Когда приходит PVC c storageClassName, controller вызывает
CSI-driver, тот идёт в облако/SAN, аллоцирует диск, возвращает
имя/handle, controller создаёт PV и биндит к PVC. Всё автоматически.
CSI, Container Storage Interface
Стандартизированный gRPC-интерфейс между k8s и storage-vendor.
До CSI каждый driver был встроен в kubelet (in-tree). Это создавало
жуткую coupling, релиз k8s блокировал обновления драйверов.
CSI-driver, это два DaemonSet'а:
- Node plugin, на каждой ноде, делает Mount/Unmount
- Controller plugin, обычно на одном pod'е, делает CreateVolume/DeleteVolume/AttachVolume в облаке
Популярные:
| CSI driver | Где работает |
|---|---|
ebs.csi.aws.com | AWS EBS (RWO, block) |
pd.csi.storage.gke.io | GCE Persistent Disk |
disk.csi.azure.com | Azure managed disks |
rook-ceph.rbd.csi.ceph.com | Ceph RBD (RWO block) |
cephfs.csi.ceph.com | Ceph FS (RWX) |
nfs.csi.k8s.io | любой [[nfs |
local.csi.k8s.io | локальный диск ноды |
longhorn.io | distributed block-storage от Rancher |
StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3 # тип диска EBS
encrypted: "true"
iops: "3000"
throughput: "125"
fsType: ext4 # как будет отформатирован
reclaimPolicy: Delete # что с PV при удалении PVC
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete, удалить PV (и реальный диск) при удалении PVC.Retain, оставить, размечать как Released, чтобы админ вручную почистил (production-friendly).volumeBindingMode: WaitForFirstConsumer, отложить provisioning пока pod не запланирован, чтобы зону диска подобрать под зону ноды.Immediate, создать сразу, может попасть «не в ту зону».allowVolumeExpansion: true, можно увеличить PVC живьём (online resize, подерживается XFS/ext4 поверх EBS). Уменьшать нельзя.
PVC, заявка от приложения
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests: { storage: 50Gi }В pod'е:
spec:
containers:
- name: app
volumeMounts:
- name: data
mountPath: /var/lib/myapp
volumes:
- name: data
persistentVolumeClaim:
claimName: app-data
Access Modes
| Mode | Аббр | Что значит | Где работает |
|---|---|---|---|
| ReadWriteOnce | RWO | один node может монтировать read-write | EBS, GCE PD, RBD - типичный block |
| ReadOnlyMany | ROX | много node могут монтировать read-only | редко, configMap-style |
| ReadWriteMany | RWX | много node могут писать одновременно | NFS, CephFS, Azure Files, filesystem-уровень |
| ReadWriteOncePod | RWOP | один pod (k8s 1.27+) | strict-locking БД |
RWO, на блок-устройство. Нельзя смонтировать на 2 ноды (FS не consistent). Если pod переехал, PV отвязывается от старой ноды и привязывается к новой (через CSI ControllerUnpublish/Publish, занимает 10-60 сек).
RWX, нужен shared filesystem: NFS, CephFS, GlusterFS, Azure Files. Block-storage не умеет.
Ephemeral volumes, данные на жизнь pod'а
Не всегда нужен persistent storage. Для scratch-данных:
emptyDir, пустой dir на ноде, удаляется с pod'ом. Backed by node-disk или RAM (medium: Memoryчерез [[tmpfs-overlayfs|tmpfs]]).configMap/secret, readonly mount контента из ConfigMap/Secret.downwardAPI, pod metadata как файлы.projected, несколько источников в один mount (часто дляserviceAccountToken+ ca-cert).generic ephemeral volume, inline PVC, удаляется с pod'ом. Удобно для temporary workloads, не надо отдельный PVC.
volumes:
- name: cache
emptyDir:
sizeLimit: 1Gi
- name: tls-certs
secret:
secretName: api-tls
StatefulSet + volumeClaimTemplates
Для БД, очередей, KV-store нужны:
- Стабильное имя pod'а (
mongo-0,mongo-1), даёт StatefulSet - Свой PVC на каждый pod, переживающий рескейлинг
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: mongo }spec:
serviceName: mongo
replicas: 3
selector: { matchLabels: { app: mongo } }template:
spec:
containers:
- name: mongo
image: mongo:7
volumeMounts:
- { name: data, mountPath: /data/db }volumeClaimTemplates:
- metadata: { name: data }spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources: { requests: { storage: 100Gi } }StatefulSet создаст data-mongo-0, data-mongo-1, data-mongo-2
каждый со своим PV. При delete StatefulSet PVC не удаляется
(специально, чтобы не потерять данные). Чистить руками
(kubectl delete pvc data-mongo-{0,1,2}).
Snapshot и clone
CSI поддерживает VolumeSnapshot (если у driver'а есть capability).
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata: { name: app-data-2026-05-03 }spec:
volumeSnapshotClassName: ebs-snapshot
source: { persistentVolumeClaimName: app-data }Из snapshot'а можно создать новый PVC (dataSource: kind: VolumeSnapshot, name: ...). Это облачный snapshot, не файловый
делается через AWS/GCE API, очень быстро (CoW).
Performance, на что смотреть
- IOPS/throughput провизионируются через StorageClass parameters
(для EBS gp3,
iops,throughput). gp2, IOPS привязан к размеру. - Latency, local SSD < cloud SSD < network filesystem. RWX storage всегда заметно медленнее RWO (NFS overhead).
- Resize, увеличение PVC требует поддержки online-resize в FS. ext4 и xfs оба умеют. ZFS, нет (через k8s).
- Backup, k8s не делает бэкап автоматически. Velero, Stash, Kasten K10, внешние инструменты.
Когда что-то пошло не так
- PVC
Pendingнавсегда,kubectl describe pvc. Часто: no StorageClass с таким именем; CSI driver не работает (kubectl logs -n kube-system <csi-controller>); WaitForFirstConsumer но pod не scheduled (no nodes). - Pod
ContainerCreatingбесконечно, проблема mount.kubectl describe podпокажет «MountVolume.SetUp failed». Часто: zone mismatch (PV в зоне A, pod в зоне B), CSI node-plugin crashed. Multi-Attach error, RWO PV пытается смонтироваться на 2 ноды (например при rolling update без правильного strategy). Решение:strategy: Recreateдля StatefulSet илиpodManagementPolicy: OrderedReady.- Disk full в pod'е, но PVC показывает много free, забыли
allowVolumeExpansion, или FS не resized после увеличения PVC.kubectl get pvc→ новый размер; внутри pod'аdf -hстарый нужноresize2fs/xfs_growfsвручную (новые CSI делают сами). Persistent volume claim is forbidden: storage size cannot be decreased, уменьшение размера запрещено. Создавай новый PVC, мигрируй данные.- Reclaimed PV не освобождается,
reclaimPolicy: Retain, PV вReleased. Удалять руками:kubectl delete pv ..., почистить spec.claimRef и привязывать к новому PVC.