Kubernetes存儲管理:PV、PVC與StorageClass
一、概述
1.1 背景介紹
容器本身是無狀態的,Pod重啟后容器內的數據全部丟失。數據庫、消息隊列、文件存儲這類有狀態服務跑在K8s上,必須解決持久化存儲問題。Kubernetes通過PersistentVolume(PV)、PersistentVolumeClaim(PVC)和StorageClass三層抽象來管理存儲。
實際生產中踩過的坑:開發團隊直接在Pod里用hostPath掛載宿主機目錄,Pod漂移到其他節點后數據就丟了。還有團隊手動創建了100個PV,每次擴容都要運維手動操作,效率極低。StorageClass的動態供給機制就是解決這個問題的。
本文覆蓋靜態供給、動態供給、本地存儲、NFS、Ceph RBD等常見存儲方案,基于Kubernetes 1.28.x版本。
1.2 技術特點
存儲與計算解耦:PV是集群級別的存儲資源,PVC是用戶對存儲的申請,兩者通過綁定關系關聯,Pod只需要引用PVC
動態供給:StorageClass配合Provisioner自動創建PV,無需運維手動干預
訪問模式控制:ReadWriteOnce(單節點讀寫)、ReadOnlyMany(多節點只讀)、ReadWriteMany(多節點讀寫),不同存儲后端支持的模式不同
回收策略:Retain(保留數據)、Delete(刪除數據)、Recycle(已廢棄),控制PVC刪除后PV的處理方式
1.3 適用場景
場景一:數據庫(MySQL、PostgreSQL)持久化存儲,要求高IOPS和數據安全
場景二:日志、文件上傳等共享存儲,多個Pod需要同時讀寫同一份數據
場景三:AI訓練數據集存儲,大容量、高吞吐量讀取
場景四:StatefulSet有狀態應用,每個Pod需要獨立的持久化卷
1.4 環境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| Kubernetes | 1.24+ | CSI驅動在1.13 GA,本文使用1.28 |
| NFS Server | NFSv4 | 共享存儲方案,需要獨立NFS服務器 |
| Ceph | 16.x+ (Pacific) | 分布式存儲方案,生產推薦 |
| nfs-subdir-external-provisioner | 4.0+ | NFS動態供給控制器 |
| ceph-csi | 3.9+ | Ceph CSI驅動 |
| 存儲節點磁盤 | SSD/HDD按需 | 數據庫用SSD,歸檔用HDD |
二、詳細步驟
2.1 準備工作
2.1.1 存儲基礎概念
PV、PVC、StorageClass的關系:
用戶視角:Pod → PVC(我需要10Gi存儲)
↓ 綁定
管理員視角: PV(這塊10Gi的存儲可以用)
↑ 創建
自動化視角: StorageClass + Provisioner(自動創建PV)
# 查看集群現有的StorageClass kubectl get sc # 查看PV和PVC kubectl get pv kubectl get pvc -A # 查看CSI驅動 kubectl get csidrivers
2.1.2 存儲方案選型
| 存儲方案 | 訪問模式 | 性能 | 適用場景 | 運維復雜度 |
|---|---|---|---|---|
| hostPath | RWO | 高(本地磁盤) | 測試環境,單節點 | 低 |
| Local PV | RWO | 高(本地SSD) | 數據庫,對IO敏感 | 中 |
| NFS | RWO/ROX/RWX | 中 | 共享文件存儲 | 低 |
| Ceph RBD | RWO | 高 | 數據庫,塊存儲 | 高 |
| CephFS | RWO/ROX/RWX | 中高 | 共享文件存儲 | 高 |
| 云廠商EBS/云盤 | RWO | 高 | 云環境數據庫 | 低 |
| Longhorn | RWO/RWX | 中 | 輕量級分布式存儲 | 中 |
2.1.3 搭建NFS服務器
NFS是最簡單的共享存儲方案,適合中小規模集群:
# NFS服務器(192.168.1.50)上執行 apt-get install -y nfs-kernel-server # 創建共享目錄 mkdir -p /data/nfs/k8s chown nobody:nogroup /data/nfs/k8s chmod 755 /data/nfs/k8s # 配置NFS導出 cat > /etc/exports <'EOF' /data/nfs/k8s ?192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash) EOF # 生效配置 exportfs -ra # 啟動NFS服務 systemctl restart nfs-kernel-server systemctl?enable?nfs-kernel-server # 驗證 showmount -e localhost
K8s所有Worker節點安裝NFS客戶端:
# Ubuntu apt-get install -y nfs-common # CentOS yum install -y nfs-utils # 驗證能掛載 mount -t nfs 192.168.1.50:/data/nfs/k8s /mnt ls /mnt umount /mnt
注意:no_root_squash允許客戶端以root身份寫入,生產環境如果安全要求高,改為root_squash并通過initContainer設置目錄權限。
2.2 核心配置
2.2.1 靜態PV/PVC(手動創建)
適合存儲資源固定、數量少的場景:
# 文件:nfs-pv-static.yaml apiVersion:v1 kind:PersistentVolume metadata: name:nfs-pv-data-01 labels: type:nfs env:production spec: capacity: storage:50Gi accessModes: -ReadWriteMany persistentVolumeReclaimPolicy:Retain storageClassName:"" nfs: server:192.168.1.50 path:/data/nfs/k8s/data-01 --- apiVersion:v1 kind:PersistentVolumeClaim metadata: name:app-data-pvc namespace:production spec: accessModes: -ReadWriteMany resources: requests: storage:50Gi storageClassName:"" selector: matchLabels: type:nfs env:production
# 先在NFS服務器上創建目錄 mkdir -p /data/nfs/k8s/data-01 # 創建PV和PVC kubectl apply -f nfs-pv-static.yaml # 驗證綁定狀態 kubectl get pv nfs-pv-data-01 # STATUS應該是Bound kubectl get pvc app-data-pvc -n production # STATUS應該是Bound
注意:靜態PV的storageClassName設為空字符串"",PVC也要設為空字符串,這樣PVC只會綁定沒有StorageClass的PV。如果不設置這個字段,PVC會嘗試使用默認StorageClass進行動態供給。
2.2.2 NFS動態供給(StorageClass)
安裝nfs-subdir-external-provisioner,實現NFS的動態PV創建:
# 使用Helm安裝 helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --namespace kube-system --setnfs.server=192.168.1.50 --setnfs.path=/data/nfs/k8s --setstorageClass.name=nfs-client --setstorageClass.defaultClass=false --setstorageClass.reclaimPolicy=Retain --setstorageClass.archiveOnDelete=true # 驗證StorageClass創建成功 kubectl get sc nfs-client
手動創建StorageClass(不用Helm的方式):
# 文件:nfs-storageclass.yaml apiVersion:storage.k8s.io/v1 kind:StorageClass metadata: name:nfs-client annotations: storageclass.kubernetes.io/is-default-class:"false" provisioner:cluster.local/nfs-provisioner parameters: archiveOnDelete:"true" reclaimPolicy:Retain volumeBindingMode:Immediate allowVolumeExpansion:true mountOptions: -hard -nfsvers=4.1 -timeo=600 -retrans=3
參數說明:
archiveOnDelete: "true":PVC刪除時不刪除NFS上的數據,而是重命名目錄加archived-前綴,防止誤刪
reclaimPolicy: Retain:PVC刪除后PV保留,需要手動清理。生產環境必須用Retain,用Delete會直接刪數據
volumeBindingMode: Immediate:PVC創建后立即綁定PV。Local PV要用WaitForFirstConsumer
allowVolumeExpansion: true:允許PVC擴容,NFS支持在線擴容
2.2.3 Local PV(本地持久卷)
適合數據庫等對IO性能敏感的場景,數據存儲在節點本地SSD上:
# 文件:local-storageclass.yaml apiVersion:storage.k8s.io/v1 kind:StorageClass metadata: name:local-ssd provisioner:kubernetes.io/no-provisioner volumeBindingMode:WaitForFirstConsumer reclaimPolicy:Retain
# 文件:local-pv.yaml apiVersion:v1 kind:PersistentVolume metadata: name:local-pv-worker01-ssd01 spec: capacity: storage:100Gi accessModes: -ReadWriteOnce persistentVolumeReclaimPolicy:Retain storageClassName:local-ssd local: path:/data/local-volumes/ssd01 nodeAffinity: required: nodeSelectorTerms: -matchExpressions: -key:kubernetes.io/hostname operator:In values: -k8s-worker-01
# 在對應節點上創建目錄 mkdir -p /data/local-volumes/ssd01 # 如果是獨立SSD,掛載到該目錄 # mkfs.xfs /dev/sdb # mount /dev/sdb /data/local-volumes/ssd01 # echo '/dev/sdb /data/local-volumes/ssd01 xfs defaults 0 0' >> /etc/fstab kubectl apply -flocal-storageclass.yaml kubectl apply -flocal-pv.yaml
警告:Local PV的數據和節點綁定,節點故障數據就丟了。生產環境用Local PV必須配合應用層的數據復制(如MySQL主從、Redis Sentinel)來保證數據安全。
2.2.4 Ceph RBD存儲(生產推薦)
Ceph提供塊存儲(RBD)和文件存儲(CephFS),通過ceph-csi驅動接入K8s:
# 前提:已有Ceph集群,獲取以下信息 # - Ceph Monitor地址:192.168.1.60:6789,192.168.1.61:6789,192.168.1.62:6789 # - Ceph集群ID:通過 ceph fsid 獲取 # - 管理員密鑰:通過 ceph auth get-key client.admin 獲取 # 在Ceph集群上創建K8s專用pool和用戶 ceph osd pool create k8s-rbd 128 128 ceph osd pool applicationenablek8s-rbd rbd rbd pool init k8s-rbd ceph auth get-or-create client.k8s-rbd mon'profile rbd' osd'profile rbd pool=k8s-rbd' -o /etc/ceph/ceph.client.k8s-rbd.keyring
在K8s中配置ceph-csi:
# 安裝ceph-csi(使用Helm) helm repo add ceph-csi https://ceph.github.io/csi-charts helm install ceph-csi-rbd ceph-csi/ceph-csi-rbd --namespace ceph-csi --create-namespace --setcsiConfig[0].clusterID=--setcsiConfig[0].monitors[0]=192.168.1.60:6789 --setcsiConfig[0].monitors[1]=192.168.1.61:6789 --setcsiConfig[0].monitors[2]=192.168.1.62:6789
創建Secret和StorageClass:
# 文件:ceph-rbd-secret.yaml apiVersion:v1 kind:Secret metadata: name:ceph-rbd-secret namespace:ceph-csi type:kubernetes.io/rbd stringData: userID:k8s-rbd userKey:--- apiVersion:v1 kind:Secret metadata: name:ceph-rbd-admin-secret namespace:ceph-csi type:kubernetes.io/rbd stringData: userID:admin userKey:
# 文件:ceph-rbd-storageclass.yaml apiVersion:storage.k8s.io/v1 kind:StorageClass metadata: name:ceph-rbd annotations: storageclass.kubernetes.io/is-default-class:"true" provisioner:rbd.csi.ceph.com parameters: clusterID:pool:k8s-rbd imageFormat:"2" imageFeatures:layering csi.storage.k8s.io/provisioner-secret-name:ceph-rbd-secret csi.storage.k8s.io/provisioner-secret-namespace:ceph-csi csi.storage.k8s.io/controller-expand-secret-name:ceph-rbd-secret csi.storage.k8s.io/controller-expand-secret-namespace:ceph-csi csi.storage.k8s.io/node-stage-secret-name:ceph-rbd-secret csi.storage.k8s.io/node-stage-secret-namespace:ceph-csi reclaimPolicy:Retain allowVolumeExpansion:true volumeBindingMode:Immediate mountOptions: -discard
kubectl apply -f ceph-rbd-secret.yaml kubectl apply -f ceph-rbd-storageclass.yaml # 驗證 kubectl get sc ceph-rbd
2.2.5 PVC擴容
# 確認StorageClass支持擴容
kubectl get sc ceph-rbd -o jsonpath='{.allowVolumeExpansion}'
# 輸出:true
# 編輯PVC,修改storage大小
kubectl patch pvc mysql-data -n database -p'{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'
# 查看擴容狀態
kubectl get pvc mysql-data -n database
# 如果顯示 FileSystemResizePending,需要Pod重啟后才能完成文件系統擴容
# Ceph RBD支持在線擴容,不需要重啟Pod
kubectl get events -n database --field-selector involvedObject.name=mysql-data
注意:PVC只能擴容不能縮容。NFS支持在線擴容,Ceph RBD支持在線擴容,Local PV不支持擴容。
2.3 啟動和驗證
2.3.1 使用PVC的Pod
# 文件:app-with-pvc.yaml
apiVersion:apps/v1
kind:Deployment
metadata:
name:app-with-storage
namespace:default
spec:
replicas:3
selector:
matchLabels:
app:app-storage
template:
metadata:
labels:
app:app-storage
spec:
containers:
-name:app
image:nginx:1.24
volumeMounts:
-name:shared-data
mountPath:/usr/share/nginx/html
-name:logs
mountPath:/var/log/nginx
resources:
requests:
cpu:100m
memory:128Mi
volumes:
-name:shared-data
persistentVolumeClaim:
claimName:app-shared-pvc
-name:logs
persistentVolumeClaim:
claimName:app-logs-pvc
---
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:app-shared-pvc
namespace:default
spec:
accessModes:
-ReadWriteMany
storageClassName:nfs-client
resources:
requests:
storage:10Gi
---
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:app-logs-pvc
namespace:default
spec:
accessModes:
-ReadWriteMany
storageClassName:nfs-client
resources:
requests:
storage:20Gi
kubectl apply -f app-with-pvc.yaml
# 驗證PVC綁定
kubectl get pvc -n default
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
# app-shared-pvc Bound pvc-xxxx-xxxx 10Gi RWX nfs-client
# app-logs-pvc Bound pvc-yyyy-yyyy 20Gi RWX nfs-client
# 驗證Pod掛載
kubectlexec-it $(kubectl get pod -l app=app-storage -o jsonpath='{.items[0].metadata.name}') -- df -h /usr/share/nginx/html
2.3.2 StatefulSet的volumeClaimTemplates
StatefulSet的每個Pod自動創建獨立的PVC:
# 文件:redis-statefulset.yaml
apiVersion:apps/v1
kind:StatefulSet
metadata:
name:redis
namespace:default
spec:
serviceName:redis
replicas:3
selector:
matchLabels:
app:redis
template:
metadata:
labels:
app:redis
spec:
containers:
-name:redis
image:redis:7.2
ports:
-containerPort:6379
volumeMounts:
-name:redis-data
mountPath:/data
resources:
requests:
cpu:200m
memory:256Mi
volumeClaimTemplates:
-metadata:
name:redis-data
spec:
accessModes:["ReadWriteOnce"]
storageClassName:ceph-rbd
resources:
requests:
storage:10Gi
kubectl apply -f redis-statefulset.yaml # 驗證:每個Pod有獨立的PVC kubectl get pvc -l app=redis # redis-data-redis-0 Bound pvc-aaa 10Gi RWO ceph-rbd # redis-data-redis-1 Bound pvc-bbb 10Gi RWO ceph-rbd # redis-data-redis-2 Bound pvc-ccc 10Gi RWO ceph-rbd
注意:StatefulSet刪除后PVC不會自動刪除,需要手動清理。這是設計如此,防止誤刪數據。
三、示例代碼和配置
3.1 完整配置示例
3.1.1 MySQL主從 + Ceph RBD完整存儲方案
# 文件:mysql-storage-complete.yaml
# MySQL主庫 - 使用Ceph RBD高性能塊存儲
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:mysql-master-data
namespace:database
spec:
accessModes:
-ReadWriteOnce
storageClassName:ceph-rbd
resources:
requests:
storage:100Gi
---
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:mysql-master-logs
namespace:database
spec:
accessModes:
-ReadWriteOnce
storageClassName:ceph-rbd
resources:
requests:
storage:50Gi
---
apiVersion:apps/v1
kind:Deployment
metadata:
name:mysql-master
namespace:database
spec:
replicas:1
strategy:
type:Recreate
selector:
matchLabels:
app:mysql
role:master
template:
metadata:
labels:
app:mysql
role:master
spec:
containers:
-name:mysql
image:mysql:8.0.35
ports:
-containerPort:3306
env:
-name:MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name:mysql-secret
key:root-password
args:
---innodb-buffer-pool-size=2G
---innodb-log-file-size=512M
---innodb-flush-method=O_DIRECT
---innodb-io-capacity=2000
---innodb-io-capacity-max=4000
resources:
requests:
cpu:"2"
memory:4Gi
limits:
cpu:"4"
memory:8Gi
volumeMounts:
-name:mysql-data
mountPath:/var/lib/mysql
-name:mysql-logs
mountPath:/var/log/mysql
-name:mysql-config
mountPath:/etc/mysql/conf.d
livenessProbe:
exec:
command:
-mysqladmin
-ping
--h
-localhost
initialDelaySeconds:30
periodSeconds:10
readinessProbe:
exec:
command:
-mysql
--h
-localhost
--e
-"SELECT 1"
initialDelaySeconds:10
periodSeconds:5
volumes:
-name:mysql-data
persistentVolumeClaim:
claimName:mysql-master-data
-name:mysql-logs
persistentVolumeClaim:
claimName:mysql-master-logs
-name:mysql-config
configMap:
name:mysql-config
注意:MySQL的Deployment用strategy: Recreate而不是RollingUpdate,因為RBD卷是RWO模式,不能同時被兩個Pod掛載。RollingUpdate會先創建新Pod再刪舊Pod,新Pod掛載會失敗。
3.1.2 存儲容量監控腳本
#!/bin/bash
# PVC使用率監控腳本
# 文件:/opt/scripts/pvc-usage-monitor.sh
# 通過kubelet的metrics獲取PVC使用率
set-euo pipefail
ALERT_THRESHOLD=85
LOG_FILE="/var/log/pvc-monitor.log"
echo"[$(date)] Starting PVC usage check...">>"${LOG_FILE}"
# 獲取所有節點的kubelet metrics中的volume信息
fornodein$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}');do
# 通過API proxy訪問kubelet metrics
kubectl get --raw"/api/v1/nodes/${node}/proxy/stats/summary"2>/dev/null |
jq -r'.pods[]? | select(.volume != null) | .podRef.namespace + "/" + .podRef.name + " " + (.volume[]? | select(.pvcRef != null) | .pvcRef.name + " " + (.usedBytes|tostring) + " " + (.capacityBytes|tostring))'2>/dev/null |
whilereadns_pod pvc used capacity;do
if[ -n"${capacity}"] && ["${capacity}"!="0"];then
usage_pct=$((used * 100 / capacity))
used_gi=$((used / 1073741824))
cap_gi=$((capacity / 1073741824))
if["${usage_pct}"-ge"${ALERT_THRESHOLD}"];then
echo"[ALERT]${ns_pod}PVC:${pvc}Usage:${usage_pct}% (${used_gi}Gi/${cap_gi}Gi)">>"${LOG_FILE}"
fi
fi
done
done
echo"[$(date)] PVC usage check completed.">>"${LOG_FILE}"
3.2 實際應用案例
案例一:多Pod共享NFS存儲實現文件上傳
場景描述:Web應用有3個副本,用戶上傳的文件需要所有副本都能訪問。用NFS的RWX模式實現共享存儲。
實現代碼:
# 文件:file-upload-app.yaml
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:upload-files-pvc
namespace:production
spec:
accessModes:
-ReadWriteMany
storageClassName:nfs-client
resources:
requests:
storage:100Gi
---
apiVersion:apps/v1
kind:Deployment
metadata:
name:file-upload-api
namespace:production
spec:
replicas:3
selector:
matchLabels:
app:file-upload-api
template:
metadata:
labels:
app:file-upload-api
spec:
containers:
-name:api
image:file-upload-api:v1.2.0
ports:
-containerPort:8080
volumeMounts:
-name:uploads
mountPath:/app/uploads
resources:
requests:
cpu:200m
memory:256Mi
volumes:
-name:uploads
persistentVolumeClaim:
claimName:upload-files-pvc
運行結果:
# 3個Pod都掛載了同一個NFS卷 kubectlexecfile-upload-api-xxx -- ls /app/uploads # 在任一Pod中上傳的文件,其他Pod都能看到
案例二:PV數據遷移(從NFS遷移到Ceph RBD)
場景描述:業務初期用NFS存儲MySQL數據,隨著數據量增長NFS的IO性能成為瓶頸,需要遷移到Ceph RBD。
實現步驟:
創建新的Ceph RBD PVC
啟動數據遷移Job
切換應用到新PVC
驗證后清理舊PVC
# 文件:data-migration-job.yaml
apiVersion:batch/v1
kind:Job
metadata:
name:pvc-data-migration
namespace:database
spec:
template:
spec:
containers:
-name:migrator
image:ubuntu:22.04
command:
-/bin/bash
--c
-|
apt-get update && apt-get install -y rsync
echo "Starting data migration..."
rsync -avz --progress /source/ /destination/
echo "Migration completed. Verifying..."
diff -r /source/ /destination/ && echo "Verification passed" || echo "Verification FAILED"
volumeMounts:
-name:source-vol
mountPath:/source
readOnly:true
-name:dest-vol
mountPath:/destination
resources:
requests:
cpu:"1"
memory:1Gi
volumes:
-name:source-vol
persistentVolumeClaim:
claimName:mysql-data-nfs
-name:dest-vol
persistentVolumeClaim:
claimName:mysql-data-ceph
restartPolicy:Never
backoffLimit:3
# 1. 停止MySQL寫入(設為只讀)
kubectlexec-it mysql-master-xxx -n database -- mysql -e"SET GLOBAL read_only=1;"
# 2. 創建目標PVC
kubectl apply -f mysql-data-ceph-pvc.yaml
# 3. 執行遷移Job
kubectl apply -f data-migration-job.yaml
kubectl logs -f job/pvc-data-migration -n database
# 4. 修改MySQL Deployment引用新PVC
kubectl patch deployment mysql-master -n database --type='json'
-p='[{"op":"replace","path":"/spec/template/spec/volumes/0/persistentVolumeClaim/claimName","value":"mysql-data-ceph"}]'
# 5. 驗證MySQL正常啟動
kubectl get pods -n database -l app=mysql,role=master
kubectlexec-it mysql-master-xxx -n database -- mysql -e"SHOW DATABASES;"
# 6. 取消只讀
kubectlexec-it mysql-master-xxx -n database -- mysql -e"SET GLOBAL read_only=0;"
四、最佳實踐和注意事項
4.1 最佳實踐
4.1.1 性能優化
NFS掛載參數調優:默認NFS掛載參數在高并發寫入時性能差,生產環境必須調整。hard模式保證數據一致性,nfsvers=4.1支持并行IO,timeo=600避免網絡抖動導致超時。
# StorageClass中設置mountOptions mountOptions: -hard -nfsvers=4.1 -timeo=600 -retrans=3 -rsize=1048576 -wsize=1048576
實測調整rsize/wsize從默認的32KB到1MB后,大文件順序寫入吞吐量從80MB/s提升到350MB/s。
Ceph RBD性能調優:RBD鏡像默認特性包含exclusive-lock、object-map、fast-diff,生產環境建議全部開啟。Ceph OSD的bluestore_cache_size默認1GB,SSD節點建議調到4GB。
# 查看RBD鏡像特性 rbd info k8s-rbd/csi-vol-xxx # Ceph OSD調優(在Ceph節點執行) ceph configsetosd bluestore_cache_size_ssd 4294967296 ceph configsetosd osd_op_num_threads_per_shard_ssd 2
Local PV使用XFS文件系統:相比ext4,XFS在大文件和高并發場景下性能更好,MySQL的InnoDB引擎在XFS上的隨機寫性能提升約15%。
mkfs.xfs -f /dev/sdb mount -o noatime,nodiratime /dev/sdb /data/local-volumes/ssd01
4.1.2 安全加固
PV訪問權限控制:通過SecurityContext設置Pod的fsGroup,確保掛載的卷只有指定用戶組能訪問。
spec: securityContext: runAsUser:1000 runAsGroup:1000 fsGroup:1000 containers: -name:app volumeMounts: -name:data mountPath:/data
加密存儲:Ceph RBD支持LUKS加密,數據在磁盤上是加密的,即使磁盤被盜也無法讀取數據。
# StorageClass中啟用加密 parameters: encrypted:"true" encryptionKMSID:vault-kms
限制PVC大小:通過LimitRange限制namespace中PVC的最大容量,防止用戶申請過大的存儲。
apiVersion:v1 kind:LimitRange metadata: name:storage-limits namespace:production spec: limits: -type:PersistentVolumeClaim max: storage:500Gi min: storage:1Gi
4.1.3 高可用配置
HA方案一:Ceph RBD三副本存儲,任一OSD節點故障數據不丟失,RBD卷自動恢復
HA方案二:NFS服務器用DRBD + Pacemaker做主備高可用,VIP自動漂移
備份策略:Ceph RBD使用快照功能定期備份,NFS使用rsync同步到備份服務器。VolumeSnapshot是K8s原生的快照機制:
apiVersion:snapshot.storage.k8s.io/v1 kind:VolumeSnapshot metadata: name:mysql-data-snapshot namespace:database spec: volumeSnapshotClassName:ceph-rbd-snapclass source: persistentVolumeClaimName:mysql-master-data
4.2 注意事項
4.2.1 配置注意事項
警告:存儲配置錯誤可能導致數據丟失,修改前務必備份數據。
注意reclaimPolicy: Delete會在PVC刪除時同時刪除PV和后端存儲數據。生產環境必須用Retain,寧可手動清理也不要自動刪除。
注意Local PV的volumeBindingMode必須設為WaitForFirstConsumer,否則PVC可能綁定到一個節點的PV上,但Pod被調度到另一個節點,導致掛載失敗。
注意NFS的no_root_squash選項允許客戶端以root身份操作文件,安全風險高。生產環境用root_squash,通過initContainer以root身份設置目錄權限后,主容器以非root用戶運行。
4.2.2 常見錯誤
| 錯誤現象 | 原因分析 | 解決方案 |
|---|---|---|
| PVC一直Pending | 沒有匹配的PV或StorageClass的Provisioner未運行 | kubectl describe pvc 查看事件;檢查Provisioner Pod狀態 |
| Pod掛載卷失敗,報Multi-Attach error | RWO卷被多個節點的Pod同時掛載 | 確認舊Pod已完全終止;檢查是否有殘留的VolumeAttachment |
| NFS掛載超時 | NFS服務器不可達或防火墻阻斷 | 檢查NFS服務器狀態;確認2049端口可訪問;Worker節點安裝nfs-common |
| Ceph RBD掛載報rbd: map failed | Ceph集群不健康或認證失敗 | 檢查Ceph集群狀態ceph -s;驗證Secret中的key是否正確 |
| PVC擴容后容量沒變 | 文件系統未擴展,需要Pod重啟 | 刪除Pod觸發重建,kubelet會自動擴展文件系統 |
| StatefulSet縮容后PVC殘留 | 設計如此,PVC不隨Pod刪除 |
手動刪除不需要的PVC:kubectl delete pvc |
4.2.3 兼容性問題
版本兼容:CSI驅動版本需要和K8s版本匹配,ceph-csi 3.9.x支持K8s 1.24-1.28
平臺兼容:NFS在所有Linux發行版上都支持;Ceph RBD需要內核4.x+;CephFS需要內核4.17+
組件依賴:VolumeSnapshot需要安裝snapshot-controller和對應的CSI驅動支持
五、故障排查和監控
5.1 故障排查
5.1.1 日志查看
# 查看CSI驅動日志 kubectl logs -n ceph-csi -l app=ceph-csi-rbd-nodeplugin --tail=50 kubectl logs -n ceph-csi -l app=ceph-csi-rbd-provisioner --tail=50 # 查看NFS Provisioner日志 kubectl logs -n kube-system -l app=nfs-subdir-external-provisioner --tail=50 # 查看kubelet的卷掛載日志 journalctl -u kubelet | grep -i"volume|mount|pvc"| tail -30 # 查看PVC事件 kubectl describe pvc-n # 查看VolumeAttachment kubectl get volumeattachment
5.1.2 常見問題排查
問題一:PVC Pending,事件顯示no persistent volumes available
# 診斷命令 kubectl describe pvc-n kubectl get pv --show-labels kubectl get sc
解決方案:
靜態供給:檢查是否有可用的PV,PV的容量、accessModes、storageClassName是否和PVC匹配
動態供給:檢查StorageClass是否存在,Provisioner Pod是否Running
檢查PV的狀態是否為Available(已綁定的PV不能再綁定其他PVC)
問題二:Pod卡在ContainerCreating,報FailedMount
# 診斷命令 kubectl describe pod-n | grep -A 10"Events" kubectl get volumeattachment | grep # 檢查節點上的掛載情況 kubectl debug node/ -it --image=ubuntu:22.04 -- mount | grep
解決方案:
NFS掛載失敗:確認Worker節點安裝了nfs-common,NFS服務器可達
RBD掛載失敗:檢查Ceph集群健康狀態,確認Secret配置正確
Multi-Attach錯誤:等待舊Pod完全終止,或手動刪除殘留的VolumeAttachment
問題三:存儲性能差,IO延遲高
癥狀:應用響應慢,數據庫查詢超時
排查:
# 在Pod內測試IO性能 kubectlexec-it-- bash # 安裝fio apt-get update && apt-get install -y fio # 隨機讀寫測試 fio --name=randwrite --ioengine=libaio --iodepth=32 --rw=randwrite --bs=4k --direct=1 --size=1G --numjobs=4 --runtime=60 --filename=/data/fio-test --group_reporting # 順序寫測試 fio --name=seqwrite --ioengine=libaio --iodepth=32 --rw=write --bs=1M --direct=1 --size=1G --numjobs=1 --runtime=60 --filename=/data/fio-test --group_reporting
解決:
NFS性能差:調整rsize/wsize到1MB,升級到NFSv4.1
Ceph RBD性能差:檢查OSD磁盤是否為SSD,調整bluestore_cache_size
考慮遷移到Local PV獲取本地磁盤性能
5.1.3 調試模式
# 查看CSI驅動詳細日志 kubectl logs -n ceph-csi-c csi-rbdplugin --tail=200 # 手動測試Ceph連接 kubectl run ceph-test --image=quay.io/ceph/ceph:v18 --rm -it -- bash ceph -s --conf /etc/ceph/ceph.conf # 查看節點上的塊設備 kubectl debug node/ -it --image=ubuntu:22.04 -- lsblk # 查看掛載點 kubectl debug node/ -it --image=ubuntu:22.04 -- df -h
5.2 性能監控
5.2.1 關鍵指標監控
# PVC使用率(需要kubelet metrics) kubectl get --raw"/api/v1/nodes//proxy/stats/summary"| jq'.pods[].volume[]? | select(.pvcRef != null) | {pvc: .pvcRef.name, used: .usedBytes, capacity: .capacityBytes}' # Ceph集群狀態 ceph -s ceph osd pool stats k8s-rbd # NFS服務器IO nfsstat -s iostat -x 1 5
5.2.2 監控指標說明
| 指標名稱 | 正常范圍 | 告警閾值 | 說明 |
|---|---|---|---|
| PVC使用率 | <70% | >85% | 超過85%需要擴容或清理數據 |
| Ceph集群健康狀態 | HEALTH_OK | HEALTH_WARN | WARN需要排查,ERR需要立即處理 |
| Ceph OSD延遲 | <10ms | >50ms | OSD延遲高影響所有RBD卷性能 |
| NFS服務器IOPS | 按硬件 | 接近硬件上限 | IOPS飽和需要擴容NFS或遷移到分布式存儲 |
| 卷掛載失敗次數 | 0 | >0 | 任何掛載失敗都需要排查 |
| PV Available數量 | >5 | <2 | 靜態供給時可用PV不足需要提前創建 |
5.2.3 Prometheus監控規則
# 文件:storage-alerts.yaml
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:storage-alerts
namespace:monitoring
spec:
groups:
-name:kubernetes-storage
rules:
-alert:PVCUsageHigh
expr:|
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85
for:10m
labels:
severity:warning
annotations:
summary:"PVC{{ $labels.persistentvolumeclaim }}usage exceeds 85%"
description:"Namespace:{{ $labels.namespace }}"
-alert:PVCUsageCritical
expr:|
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.95
for:5m
labels:
severity:critical
annotations:
summary:"PVC{{ $labels.persistentvolumeclaim }}usage exceeds 95%"
-alert:PVCPending
expr:|
kube_persistentvolumeclaim_status_phase{phase="Pending"} == 1
for:15m
labels:
severity:warning
annotations:
summary:"PVC{{ $labels.persistentvolumeclaim }}has been Pending for 15 minutes"
-alert:PVAvailableLow
expr:|
count(kube_persistentvolume_status_phase{phase="Available"}) < 2
? ? ? ? ??for:10m
? ? ? ? ??labels:
? ? ? ? ? ??severity:warning
? ? ? ? ??annotations:
? ? ? ? ? ??summary:"Less than 2 PVs available in the cluster"
? ? ? ??-alert:VolumeAttachmentFailed
? ? ? ? ??expr:|
? ? ? ? ? ? increase(storage_operation_errors_total[5m]) > 0
for:5m
labels:
severity:warning
annotations:
summary:"Volume operation errors detected"
5.3 備份與恢復
5.3.1 備份策略
#!/bin/bash
# Ceph RBD快照備份腳本
# 文件:/opt/scripts/rbd-snapshot-backup.sh
set-euo pipefail
POOL="k8s-rbd"
BACKUP_PREFIX="scheduled-backup"
KEEP_SNAPSHOTS=24
# 獲取pool中所有RBD鏡像
forimagein$(rbd ls${POOL});do
SNAP_NAME="${BACKUP_PREFIX}-$(date +%Y%m%d-%H%M%S)"
# 創建快照
rbd snap create"${POOL}/${image}@${SNAP_NAME}"
echo"[$(date)] Created snapshot:${POOL}/${image}@${SNAP_NAME}"
# 清理過期快照(保留最近24個)
rbd snap ls"${POOL}/${image}"| grep"${BACKUP_PREFIX}"|
head -n -${KEEP_SNAPSHOTS}| awk'{print $2}'|
whilereadold_snap;do
rbd snap rm"${POOL}/${image}@${old_snap}"
echo"[$(date)] Removed old snapshot:${POOL}/${image}@${old_snap}"
done
done
5.3.2 恢復流程
停止服務:縮容使用該PVC的Deployment/StatefulSet到0副本
恢復數據:從快照恢復RBD鏡像或從備份rsync數據
驗證完整性:掛載卷檢查數據完整性
重啟服務:恢復Deployment/StatefulSet副本數
六、總結
6.1 技術要點回顧
要點一:生產環境reclaimPolicy必須設為Retain,Delete策略會在PVC刪除時直接刪除后端存儲數據
要點二:Local PV的volumeBindingMode必須用WaitForFirstConsumer,避免PV和Pod調度到不同節點
要點三:NFS適合共享文件存儲(RWX),Ceph RBD適合數據庫塊存儲(RWO),根據業務需求選型
要點四:StorageClass + Provisioner實現動態供給,消除手動創建PV的運維負擔
要點五:PVC只能擴容不能縮容,容量規劃時預留30%余量,配合監控在85%使用率時告警
6.2 進階學習方向
CSI驅動開發:理解CSI接口規范,為自研存儲系統開發K8s CSI驅動
學習資源:CSI規范
實踐建議:從ceph-csi源碼入手,理解Provisioner和NodePlugin的工作流程
Rook-Ceph Operator:用Rook在K8s中自動化部署和管理Ceph集群,降低Ceph運維復雜度
學習資源:Rook官方文檔
實踐建議:測試環境用Rook部署3節點Ceph集群,體驗自動化運維
VolumeSnapshot和數據保護:基于CSI的快照機制實現應用一致性備份
6.3 參考資料
Kubernetes存儲官方文檔- PV/PVC/SC完整說明
Ceph CSI項目- Ceph CSI驅動
NFS Provisioner- NFS動態供給
Longhorn項目- 輕量級分布式存儲
附錄
A. 命令速查表
# PV操作 kubectl get pv # 查看所有PV kubectl get pv -o wide # 查看PV詳情(含StorageClass) kubectl describe pv# PV詳細信息 kubectl delete pv # 刪除PV(Retain策略下數據不丟) # PVC操作 kubectl get pvc -A # 查看所有namespace的PVC kubectl get pvc -n # 查看指定namespace的PVC kubectl describe pvc -n # PVC詳細信息和事件 kubectl patch pvc -p'{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'# 擴容 # StorageClass操作 kubectl get sc # 查看StorageClass列表 kubectl describe sc # SC詳細信息 kubectl patch sc -p'{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'# 設為默認SC # 排查命令 kubectl get volumeattachment # 查看卷掛載關系 kubectl get events -A --field-selector reason=FailedMount # 掛載失敗事件
B. 配置參數詳解
StorageClass關鍵參數:
| 參數 | 說明 | 可選值 |
|---|---|---|
| provisioner | 存儲供給器 | rbd.csi.ceph.com 、nfs.csi.k8s.io等 |
| reclaimPolicy | 回收策略 | Retain (保留)、Delete(刪除) |
| volumeBindingMode | 綁定模式 | Immediate (立即)、WaitForFirstConsumer(延遲) |
| allowVolumeExpansion | 允許擴容 | true /false |
| mountOptions | 掛載選項 | 依存儲類型而定 |
PV訪問模式:
| 模式 | 縮寫 | 說明 | 支持的存儲 |
|---|---|---|---|
| ReadWriteOnce | RWO | 單節點讀寫 | 所有存儲類型 |
| ReadOnlyMany | ROX | 多節點只讀 | NFS、CephFS、云盤 |
| ReadWriteMany | RWX | 多節點讀寫 | NFS、CephFS、GlusterFS |
| ReadWriteOncePod | RWOP | 單Pod讀寫(1.27 GA) | CSI驅動支持 |
C. 術語表
| 術語 | 英文 | 解釋 |
|---|---|---|
| PV | PersistentVolume | 集群級別的存儲資源,由管理員創建或動態供給 |
| PVC | PersistentVolumeClaim | 用戶對存儲的申請,綁定到PV后供Pod使用 |
| SC | StorageClass | 存儲類,定義動態供給的參數和策略 |
| CSI | Container Storage Interface | 容器存儲接口標準,存儲廠商實現CSI驅動接入K8s |
| RBD | RADOS Block Device | Ceph的塊存儲設備,提供高性能塊級IO |
| Provisioner | 供給器 | 負責根據PVC自動創建PV的控制器 |
| VolumeSnapshot | 卷快照 | 基于CSI的存儲快照機制,用于數據備份 |
| fsGroup | 文件系統組 | Pod SecurityContext中設置,控制掛載卷的文件組權限 |
-
存儲管理
+關注
關注
0文章
32瀏覽量
9608 -
kubernetes
+關注
關注
0文章
263瀏覽量
9494
原文標題:從 Pod 重建不丟數據開始:Kubernetes PV/PVC/StorageClass 落地實踐
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
KubePi:開源Kubernetes可視化管理面板,讓集群管理如此簡單
Kubernetes Ingress 高可靠部署最佳實踐
不吹不黑,今天我們來聊一聊 Kubernetes 落地的三種方式
在Kubernetes上運行Kubernetes
Kubernetes API詳解
阿里巴巴 Kubernetes 應用管理實踐中的經驗與教訓
Kubernetes是什么,一文了解Kubernetes
Kubernetes上Java應用的最佳實踐
Awesome 工具如何更好地管理Kubernetes
戴爾科技再次榮獲Kubernetes數據存儲領導者
龍智出席2024零跑智能汽車技術論壇,分享功能安全、需求管理、版本管理、代碼掃描等DevSecOps落地實踐
Kubernetes存儲管理功能的落地實踐
評論