从 Pod 重建不丢数据开始:Kubernetes PV/PVC/StorageClass 落地实践
容器本身是无状态的,Pod重启后容器内的数据全部丢失。数据库、消息队列、文件存储这类有状态服务跑在K8s上,必须解决持久化存储问题。Kubernetes通过PersistentVolume(PV)、PersistentVolumeClaim(PVC)和StorageClass三层抽象来管理存储。实际生产中踩过的坑:开发团队直接在Pod里用hostPath挂载宿主机目录,Pod漂移到其他节点后数据就
一、概述
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 \
--set nfs.server=192.168.1.50 \
--set nfs.path=/data/nfs/k8s \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=false \
--set storageClass.reclaimPolicy=Retain \
--set storageClass.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 -f local-storageclass.yaml
kubectl apply -f local-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 application enable k8s-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 \
--set csiConfig[0].clusterID=<ceph-fsid> \
--set csiConfig[0].monitors[0]=192.168.1.60:6789 \
--set csiConfig[0].monitors[1]=192.168.1.61:6789 \
--set csiConfig[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:<cephauthget-keyclient.k8s-rbd的输出>
---
apiVersion:v1
kind:Secret
metadata:
name:ceph-rbd-admin-secret
namespace:ceph-csi
type:kubernetes.io/rbd
stringData:
userID:admin
userKey:<cephauthget-keyclient.admin的输出>
# 文件: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:<ceph-fsid>
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挂载
kubectl exec -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信息
for node in $(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 | \
whileread ns_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卷
kubectl exec file-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写入(设为只读)
kubectl exec -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
kubectl exec -it mysql-master-xxx -n database -- mysql -e "SHOW DATABASES;"
# 6. 取消只读
kubectl exec -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 config set osd bluestore_cache_size_ssd 4294967296 ceph config set osd 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挂载卷失败,报 |
RWO卷被多个节点的Pod同时挂载 |
确认旧Pod已完全终止;检查是否有残留的VolumeAttachment |
|
NFS挂载超时 |
NFS服务器不可达或防火墙阻断 |
检查NFS服务器状态;确认2049端口可访问;Worker节点安装nfs-common |
|
Ceph RBD挂载报 |
Ceph集群不健康或认证失败 |
检查Ceph集群状态 |
|
PVC扩容后容量没变 |
文件系统未扩展,需要Pod重启 |
删除Pod触发重建,kubelet会自动扩展文件系统 |
|
StatefulSet缩容后PVC残留 |
设计如此,PVC不随Pod删除 |
手动删除不需要的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 <pvc-name> -n <namespace>
# 查看VolumeAttachment
kubectl get volumeattachment
5.1.2 常见问题排查
问题一:PVC Pending,事件显示no persistent volumes available
# 诊断命令
kubectl describe pvc <pvc-name> -n <namespace>
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 <pod-name> -n <namespace> | grep -A 10 "Events"
kubectl get volumeattachment | grep <pv-name>
# 检查节点上的挂载情况
kubectl debug node/<node-name> -it --image=ubuntu:22.04 -- \
mount | grep <pv-name>
解决方案:
-
NFS挂载失败:确认Worker节点安装了nfs-common,NFS服务器可达
-
RBD挂载失败:检查Ceph集群健康状态,确认Secret配置正确
-
Multi-Attach错误:等待旧Pod完全终止,或手动删除残留的VolumeAttachment
问题三:存储性能差,IO延迟高
-
症状:应用响应慢,数据库查询超时
-
排查:
# 在Pod内测试IO性能 kubectl exec -it <pod-name> -- 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 <csi-pod-name> -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/<node-name> -it --image=ubuntu:22.04 -- lsblk
# 查看挂载点
kubectl debug node/<node-name> -it --image=ubuntu:22.04 -- df -h
5.2 性能监控
5.2.1 关键指标监控
# PVC使用率(需要kubelet metrics)
kubectl get --raw "/api/v1/nodes/<node>/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镜像
for image in $(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}' | \
whileread old_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-name> # PV详细信息
kubectl delete pv <pv-name> # 删除PV(Retain策略下数据不丢)
# PVC操作
kubectl get pvc -A # 查看所有namespace的PVC
kubectl get pvc -n <namespace> # 查看指定namespace的PVC
kubectl describe pvc <pvc-name> -n <namespace> # PVC详细信息和事件
kubectl patch pvc <name> -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'# 扩容
# StorageClass操作
kubectl get sc # 查看StorageClass列表
kubectl describe sc <sc-name> # SC详细信息
kubectl patch sc <name> -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
、 |
reclaimPolicy |
回收策略 |
Retain
(保留)、 |
volumeBindingMode |
绑定模式 |
Immediate
(立即)、 |
allowVolumeExpansion |
允许扩容 |
true
/ |
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)