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文章
33浏览量
9624 -
kubernetes
+关注
关注
0文章
273浏览量
9530
原文标题:从 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存储管理功能的落地实践
评论