Pamięć trwała w Kubernetes: CSI, PV, StorageClass i StatefulSet
Kubernetes narodził się jako platforma dla obciążeń bezstanowych, ale rzeczywistość aplikacji korporacyjne i bardzo różne: bazy danych, kolejki komunikatów, systemy trwałej pamięci podręcznej, współdzielone systemy plików. Wszystkie wymagają pamięci, która przetrwa cykl życia kapsuły. Zarządzaj tą pamięcią masową w niezawodny, wydajny i przenośny sposób pomiędzy dostawcami usług w chmurze odmienne i stanowią jedno z najbardziej konkretnych wyzwań codziennej produkcji.
W tym artykule przyjrzymy się całej warstwie pamięci masowej Kubernetes: począwszy od Container Storage Interfejs (CSI), który standaryzuje integrację z dostawcami, PersistentVolume i StorageClass do dynamicznego udostępniania, do Zestaw stanowy dla zarządzaj bazami danych takimi jak PostgreSQL, Cassandra i Redis na Kubernetesie.
Czego się nauczysz
- Model pamięci masowej Kubernetes: Volume, PersistentVolume, PersistentVolumeClaim
- Jak działa interfejs Container Storage Interface (CSI) i najczęściej używane sterowniki
- StorageClass i dynamiczna konfiguracja: konfiguracja dla AWS EBS, GCE PD, Azure Disk
- Tryb dostępu: ReadWriteOnce, ReadOnlyMany, ReadWriteMany – kiedy którego używać
- StatefulSet: stabilna tożsamość, automatyczne PVC, uporządkowana aktualizacja krocząca
- Jak uruchomić PostgreSQL na Kubernetesie za pomocą StatefulSet
- Tworzenie kopii zapasowych i przywracanie PersistentVolumes za pomocą Velero
Model pamięci masowej Kubernetes
Kubernetes definiuje hierarchię abstrakcji dla pamięci masowej, która oddziela „to, co jest potrzebne” (PersistentVolumeClaim) z „jak i pod warunkiem” (PersistentVolume i StorageClass). To umożliwia programistom żądanie miejsca na dysku bez znajomości szczegółów dostawcy podstawowa chmura.
Prymitywy przechowywania
| Ratunek | Miotły | Kto tym zarządza | Opis |
|---|---|---|---|
| Tom | Strąk | Wywoływacz | Magazyn efemeryczny powiązany z cyklem życia kapsuły |
| Stała objętość (PV) | Klastry | Administrator / Dostawca | Element pamięci w klastrze, cykl życia niezależny od Podów |
| Trwałe żądanie objętościowe (PVC) | Przestrzeń nazw | Wywoływacz | Żądanie miejsca na dysku z Poda |
| Klasa przechowywania | Klastry | Administrator | Definiuje „typ” magazynu i dostawcę usług |
Cykl życia trwałego woluminu
Cykl życia fotowoltaiki przechodzi przez różne stany. Ich zrozumienie jest niezbędne rozwiązywanie problemów:
- Dostępny: PV istnieje i jest bezpłatne i nie jest powiązane z żadnym PCV
- Zobowiązany: Panel fotowoltaiczny został połączony z PVC spełniającym wymagania
- Wydany: PVC zostało wyeliminowane, ale PV nie jest jeszcze dostępne (dane nadal tam są)
- Przegrany: Automatyczne odzyskiwanie fotowoltaiki nie powiodło się
# Verifica lo stato dei PersistentVolume nel cluster
kubectl get pv -o wide
# Output tipico:
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
# pv-db-001 100Gi RWO Retain Bound production/postgres fast-ssd
# pv-db-002 100Gi RWO Retain Available fast-ssd
# Descrizione dettagliata di un PV
kubectl describe pv pv-db-001
# Verifica i PVC in un namespace
kubectl get pvc -n production
kubectl describe pvc postgres-data -n production
Interfejs przechowywania kontenerów (CSI)
Przed wprowadzeniem CSI każdy dostawca pamięci masowej musiał utrzymywać wtyczki wbudowane w swój kod Źródło Kubernetes (wtyczki w drzewie). Stworzyło to mocne i wydajne sprzęgło trudne do aktualizacji wtyczki niezależnie od Kubernetes. CSI rozwiązuje ten problem ze standardowym interfejsem gRPC, który umożliwia stronom trzecim tworzenie sterowników pamięci masowej jako niezależne Kubernetes Pody.
Główne sterowniki CSI
| Kierowcy CSI | Dostawcy | Typ przechowywania | CzytajNapiszWiele |
|---|---|---|---|
| sterownik aws-ebs-csi | AWS | Blok (gp3, io2) | No |
| sterownik aws-efs-csi | AWS | NFS (EFS) | Si |
| sterownik gce-pd-csi | GCP | Blok (pd-ssd, pd-zbalansowany) | Nie (tylko RWX FileStore) |
| sterownik azuredisk-csi | Lazur | Blok (dysk SSD klasy premium) | No |
| sterownik Azurefile-csi | Lazur | NFS (pliki Azure) | Si |
| csi-rook-ceph | Rook/Cef | Blok/FS/Obiekt | Tak (CephFS) |
| długorogi | Ranczerowie | Blok rozprowadzony | Tak (z NFS) |
Instalacja sterownika CSI EBS na EKS
# Installa il driver CSI EBS su Amazon EKS
# Prima, crea un IAM Role con le policy necessarie
aws eks create-addon \
--cluster-name my-cluster \
--addon-name aws-ebs-csi-driver \
--service-account-role-arn arn:aws:iam::ACCOUNT_ID:role/EBSCSIRole
# Verifica il daemonset del driver CSI
kubectl get daemonset -n kube-system ebs-csi-node
kubectl get deployment -n kube-system ebs-csi-controller
StorageClass: dynamiczne udostępnianie
Udostępnianie statyczne (ręczne tworzenie PV) jest niepraktyczne w produkcji. Z dynamiczne udostępnianie, Kubernetes automatycznie tworzy PV, gdy przychodzi PVC utworzony przy użyciu sterownika CSI skonfigurowanego w klasie StorageClass.
StorageClass dla AWS EBS
# storage-classes-aws.yaml
# StorageClass per dischi gp3 (performance ottimale)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "false"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # IMPORTANTE: evita cross-AZ mounting
reclaimPolicy: Retain # Protegge i dati in produzione
allowVolumeExpansion: true
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
kmsKeyId: "arn:aws:kms:eu-west-1:ACCOUNT:key/KEY_ID"
---
# StorageClass per io2 (database ad alto IOPS)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ultra-fast-ssd
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
parameters:
type: io2
iops: "32000"
encrypted: "true"
---
# StorageClass per EFS (ReadWriteMany, NFS)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: shared-storage
provisioner: efs.csi.aws.com
reclaimPolicy: Retain
parameters:
provisioningMode: efs-ap
fileSystemId: fs-XXXXXXXX
directoryPerms: "700"
StorageClass dla GKE (Google Kubernetes Engine)
# storage-classes-gke.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd-gke
provisioner: pd.csi.storage.gke.io
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: true
parameters:
type: pd-ssd
replication-type: regional-pd # replica su 2 zone
availability-class: regional-hard-failover
VolumeBindingMode: Dlaczego WaitForFirstConsumer
Zawsze używaj WaitForFirstConsumer zamiast Immediate kiedy
klaster ma wiele stref dostępności. Z Immediate, tworzony jest PV
w strefie, w której zaplanowany jest dostawca usług, która może różnić się od strefy
gdzie kapsuła zostanie zaplanowana. Wynik: kapsuła nie może zamontować woluminu.
WaitForFirstConsumer tworzy PV w tym samym obszarze co Pod.
PersistentVolumeClaim w praktyce
PVC i sposób, w jaki kapsuła wymaga przechowywania. PVC określa rozmiar i tryb dostępu i StorageClass. Kubernetes znajduje lub tworzy kompatybilny PV i wiąże go z PVC.
# pvc-database.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
labels:
app: postgres
tier: database
annotations:
# Snapshot policy (con alcuni storage providers)
storageclass.kubernetes.io/is-default-class: "false"
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
# Espandi a 200Gi in futuro con:
# kubectl patch pvc postgres-data -p '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
Tryb dostępu: wybierz właściwy
| Tryb dostępu | Skrót | Co to znaczy | Typowe zastosowanie |
|---|---|---|---|
| PrzeczytajNapisz raz | RWO | Tylko jeden węzeł odczytu/zapisu | Bazy danych, aplikacje jednoinstancyjne |
| PrzeczytajTylko wiele | ROX | Wiele węzłów tylko do odczytu | Udostępnione dane statyczne, konfiguracje |
| CzytajNapiszWiele | RWX | Wiele węzłów odczytu/zapisu | Udostępniony NFS, moduł obsługi przesyłania |
| PrzeczytajZapiszRazPod | RWOP | Tylko jeden moduł odczytu/zapisu | Ekskluzywne miejsce na pojedynczy Pod (K8s 1.29+) |
StatefulSet: obciążenie ze stabilną tożsamością
Wdrożenia doskonale sprawdzają się w przypadku obciążeń bezstanowych, ale w przypadku baz danych i aplikacji, które wymagają stabilnej tożsamości (przewidywalna nazwa hosta, dedykowany wolumin, kolejność rozruchu) służy Zestaw stanowy. Kluczowe różnice w porównaniu do wdrożeń:
- Stabilna tożsamość: Pody mają przewidywalne nazwy:
myapp-0,myapp-1,myapp-2 - Kolejność rozruchu: Pody są uruchamiane w kolejności (0, następnie 1, następnie 2) i zamykane w odwrotnej kolejności
- Dedykowane PCV: Każda kapsuła otrzymuje własne łącze PVC
volumeClaimTemplates - Usługa bezgłowa: Każdy Pod ma stabilny wpis DNS:
myapp-0.myapp.namespace.svc.cluster.local
PostgreSQL na Kubernetesie z StatefulSet
Oto kompletna i gotowa do produkcji konfiguracja PostgreSQL z StatefulSet, w tym ConfigMap do konfiguracji, Secret do poświadczeń i usługa bezgłowa:
# postgres-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: production
labels:
app: postgres
spec:
ports:
- port: 5432
name: postgres
clusterIP: None # Headless Service - abilita il DNS per Pod individuali
selector:
app: postgres
---
# Service per accesso al master (read/write)
apiVersion: v1
kind: Service
metadata:
name: postgres-master
namespace: production
spec:
ports:
- port: 5432
selector:
app: postgres
role: master
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: production
spec:
serviceName: postgres # deve corrispondere al nome del headless Service
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
# Anti-affinity: distribuisce le repliche su nodi diversi
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: postgres
topologyKey: kubernetes.io/hostname
initContainers:
# init container per configurare i permessi del volume
- name: init-postgres
image: postgres:16
command:
- bash
- "-c"
- |
chown -R 999:999 /var/lib/postgresql/data
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgres-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: postgres-config
mountPath: /etc/postgresql/postgresql.conf
subPath: postgresql.conf
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
- -d
- $(POSTGRES_DB)
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- $(POSTGRES_USER)
- -d
- $(POSTGRES_DB)
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-config
configMap:
name: postgres-config
# PVC template: ogni Pod ottiene il proprio volume da 100Gi
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
ConfigMap dla konfiguracji PostgreSQL
# postgres-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
namespace: production
data:
postgresql.conf: |
# Performance tuning per 4GB RAM
shared_buffers = 1GB
work_mem = 64MB
maintenance_work_mem = 256MB
effective_cache_size = 3GB
# WAL settings
wal_level = replica
max_wal_senders = 5
wal_keep_size = 1GB
# Checkpoint
checkpoint_completion_target = 0.9
max_wal_size = 4GB
min_wal_size = 1GB
# Connection settings
max_connections = 200
# Logging
log_min_duration_statement = 1000 # log query lente >1s
log_checkpoints = on
log_connections = on
log_disconnections = on
Migawka woluminu i kopia zapasowa
Migawki woluminów umożliwiają tworzenie kopii zapasowych woluminów PersistentVolumes w określonym momencie korzystanie z natywnych możliwości dostawcy chmury (migawka EBS, migawka GCE PD itp.).
# Installa le CRD per Volume Snapshot (se non presenti)
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
# VolumeSnapshotClass
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ebs-snapshot-class
driver: ebs.csi.aws.com
deletionPolicy: Retain
---
# Crea uno snapshot del volume di PostgreSQL
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: postgres-snapshot-20260801
namespace: production
spec:
volumeSnapshotClassName: ebs-snapshot-class
source:
persistentVolumeClaimName: postgres-data-postgres-0
---
# Verifica lo stato dello snapshot
kubectl get volumesnapshot -n production
# Restore da snapshot: crea un nuovo PVC da snapshot
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data-restored
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
dataSource:
name: postgres-snapshot-20260801
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
Velero do kompletnej kopii zapasowej klastra
Velero to referencyjne narzędzie do tworzenia kopii zapasowych i przywracania całych klastrów Kubernetes, w tym PersistentVolumes:
# Installa Velero con il plugin EBS
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.8.0 \
--bucket my-velero-backups \
--backup-location-config region=eu-west-1 \
--snapshot-location-config region=eu-west-1 \
--secret-file ./credentials-velero
# Crea un backup del namespace production con i volume
velero backup create production-backup-20260801 \
--include-namespaces production \
--snapshot-volumes \
--wait
# Verifica il backup
velero backup describe production-backup-20260801
velero backup logs production-backup-20260801
# Schedule: backup giornaliero a mezzanotte
velero schedule create daily-production \
--schedule="0 0 * * *" \
--include-namespaces production \
--snapshot-volumes \
--ttl 720h # mantieni per 30 giorni
# Restore in un nuovo cluster
velero restore create --from-backup production-backup-20260801 \
--namespace-mappings production:production-restored
Miejsce na obciążenia AI/ML
Obciążenia szkoleniowe ML mają specjalne wymagania dotyczące magazynu: dostęp równoległy wysoka przepustowość dla dużych zbiorów danych, często od wielu pracowników GPU jednocześnie.
# PVC con ReadWriteMany per training distribuito
# Usa EFS (AWS) o CephFS (on-premise) per RWX
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ml-dataset-storage
namespace: ml-training
spec:
accessModes:
- ReadWriteMany
storageClassName: shared-storage # EFS o CephFS
resources:
requests:
storage: 10Ti # 10TB per dataset ImageNet, etc.
---
# Job di training che accede ai dati in parallelo
apiVersion: batch/v1
kind: Job
metadata:
name: distributed-training
namespace: ml-training
spec:
parallelism: 8 # 8 worker GPU in parallelo
completions: 8
template:
spec:
containers:
- name: trainer
image: pytorch/pytorch:2.3.0-cuda12.1-cudnn8-runtime
resources:
limits:
nvidia.com/gpu: "1"
volumeMounts:
- name: dataset
mountPath: /data
readOnly: true # tutti i worker leggono, nessuno scrive
- name: checkpoints
mountPath: /checkpoints
volumes:
- name: dataset
persistentVolumeClaim:
claimName: ml-dataset-storage
readOnly: true
- name: checkpoints
persistentVolumeClaim:
claimName: ml-checkpoints-rwx # RWX per checkpoint condivisi
Najlepsze praktyki dotyczące pamięci masowej Kubernetes
Lista kontrolna przechowywania produkcji
- Zawsze używaj ReclaimPolicy: Zachowaj dla danych produkcyjnych.
Deleteusuwa dane automatycznie po usunięciu PVC - woluminBindingMode: WaitForFirstConsumer: Unikaj problemów z powiązaniami między strefami AZ w klastrach wielostrefowych
- zezwolenie na rozwinięcie wolumenu: true: Skonfiguruj StorageClasses, aby umożliwić rozszerzanie woluminów bez przestojów
- Monitoruj użycie dysku: Skonfiguruj alerty w Prometheusie, gdy pojemność PVC przekracza 80%.
- Automatyczne migawki: Skonfiguruj klasę VolumeSnapshotClass i zaplanowane kopie zapasowe
- Przetestuj przywracanie: Nieprzetestowana i bezużyteczna kopia zapasowa. Wykonuj comiesięczne testy przywracania
- Oddziel PVC według roli: Jeden PVC na dane, jeden na logi, jeden na tymczasowe kopie zapasowe
- StatefulSet z anty-powinowactwem: Rozdzielaj repliki w różnych węzłach i strefach
Anty-wzór: nie rób tego
- Nie używaj hostPath w produkcji: Wiąże kapsułę z określonym węzłem i nie jest przenośna
- Nie używaj pustyDir dla trwałych danych: Znika po ponownym uruchomieniu kapsuły
- Nie używaj reclaimPolicy: Usuń dla danych produkcyjnych: Przez pomyłkę możesz stracić wszystko
- Nie montuj tego samego PVC (RWO) na wielu modułach: Powoduje uszkodzenie danych
Monitorowanie pamięci masowej za pomocą Prometheusa
# Metriche chiave da monitorare con kube-state-metrics
# Aggiungi alert a Prometheus
# Alert: PVC vicino alla capacita massima
groups:
- name: kubernetes-storage
rules:
- alert: PVCStorageUsageHigh
expr: |
kubelet_volume_stats_used_bytes /
kubelet_volume_stats_capacity_bytes > 0.80
for: 5m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} e all'80% della capacita"
description: "Namespace: {{ $labels.namespace }}"
- alert: PVCStorageFull
expr: |
kubelet_volume_stats_used_bytes /
kubelet_volume_stats_capacity_bytes > 0.95
for: 2m
labels:
severity: critical
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} quasi piena!"
- alert: PVCNotBound
expr: |
kube_persistentvolumeclaim_status_phase{phase="Pending"} == 1
for: 10m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} in stato Pending da 10 minuti"
Wnioski i dalsze kroki
Pamięć Kubernetes to jedna z najważniejszych warstw aplikacji korporacyjnych produkcja. Interfejs Container Storage standaryzuje integrację z dowolnym dostawcy, dynamiczne udostępnianie za pomocą StorageClass eliminuje pracę ręczną, np StatefulSets zapewniają elementy podstawowe niezbędne do zarządzania bazami danych o stabilnych tożsamościach.
Kluczem do solidnego przechowywania w produkcji jest połączenie wyborów architektonicznych poprawne (reclaimPolicy Retain, WaitForFirstConsumer, anty-powinowactwo), proaktywne monitorowanie z Prometheusem i regularnie testowaną strategią tworzenia kopii zapasowych z Velero lub VolumeSnapshot.
Nadchodzące artykuły z serii Kubernetes at Scale
Powiązane serie
- Kubernetes Networking: CNI, Cilium z eBPF i polityką sieciową
- MLOps i uczenie maszynowe w produkcji — przechowywanie zbiorów danych ML
- PostgreSQL i wyszukiwanie wektorowe AI







