K8S存储体系全解-从PV-PVC-SC到StatefulSet持久化实战

K8S存储体系全解-从PV-PVC-SC到StatefulSet持久化实战 K8S存储体系全解从PV/PVC/SC到StatefulSet持久化实战导读容器是阅后即焚的——Pod 重启后内部数据全部丢失。对于数据库、文件服务等有状态应用数据持久化是不可逾越的门槛。K8S 通过PV持久卷→ PVC持久卷声明→ SC存储类三层抽象以及StatefulSet VolumeClaimTemplate机制构建了一套优雅的存储管理体系。本文将通过 NFS 存储后端从手动创建 PV 到动态供给 SC从 Deployment 共享存储到 StatefulSet 独享存储带你彻底搞懂 K8S 的存储体系。一、为什么需要持久化存储容器的文件系统是临时的——容器停止或重建后数据即丢失。但在生产环境中很多应用需要数据持久化场景数据类型丢失后果MySQL / Redis数据文件业务数据全部丢失文件服务用户上传的文件用户内容丢失日志采集采集的日志数据日志断档配置中心持久化配置服务配置丢失K8S 存储抽象层的核心价值┌─────────────────────────────────────────────────────────┐ │ K8S 存储抽象体系 │ │ │ │ Pod ──→ PVC声明需要多少存储──→ PV/SC提供存储 │ │ │ │ 开发者只需关心我要 5Gi 存储PVC │ │ 运维者只需关心后端存储是什么PV/SC │ │ 两者通过标签和存储类解耦互不干扰 │ └─────────────────────────────────────────────────────────┘二、存储卷类型全景K8S 支持的存储卷类型多达 30 种按层次可以分为三类┌──────────────────────────────────────────────────────────┐ │ K8S 存储卷分类 │ ├──────────────┬──────────────┬────────────────────────────┤ │ 临时存储 │ 本地存储 │ 持久存储网络存储 │ ├──────────────┼──────────────┼────────────────────────────┤ │ emptyDir │ hostPath │ NFS │ │ (Pod内临时) │ (节点目录) │ Ceph RBD / CephFS │ │ │ │ iSCSI │ │ │ │ AWS EBS │ │ │ │ 阿里云 OSS / NAS │ │ │ │ GlusterFS │ └──────────────┴──────────────┴────────────────────────────┘类型生命周期数据持久性适用场景emptyDir与 Pod 同生共死不持久化Pod 内容器间共享临时数据hostPath与节点同生共死节点级持久化开发测试、DaemonSetNFS独立于 Pod 和节点持久化文件共享、日志存储Ceph独立于 Pod 和节点持久化高可用块存储、对象存储PV/PVC管理员控制持久化生产环境标准方式三、PV/PVC/SC 核心概念3.1 三个核心资源的关系┌─────────────────────────────────────────────────────────┐ │ Pod ←──引用── PVC ←──绑定── PV ←──对接── 后端存储 │ │ ↑ ↑ │ │ │ 或由 SC 动态创建 │ │ │ ↑ │ │ └── 指定 SC ──→ SC │ └─────────────────────────────────────────────────────────┘资源全称角色由谁管理PVPersistentVolume一块具体的存储资源集群管理员PVCPersistentVolumeClaim对存储的申请单开发者/用户SCStorageClass存储的类别模板支持动态创建 PV集群管理员类比理解PV 是房子PVC 是租房合同SC 是房产中介。开发者租客只需要签合同PVC不用关心房子在哪PV房产中介SC会自动帮忙找房子。3.2 PV 的三种状态Available可用──→ Bound绑定──→ Released已释放──→ Failed失败 ↑ │ │ └──── 回收后 ←──────┘ │ │ │ └── 手动清理后 ←────────┘状态说明AvailablePV 已创建等待 PVC 绑定BoundPV 已被 PVC 绑定正在使用ReleasedPVC 被删除PV 已释放但数据保留取决于回收策略Failed自动回收失败3.3 PV 访问模式访问模式简称说明ReadWriteOnceRWO单节点读写最常用ReadOnlyManyROX多节点只读ReadWriteManyRWX多节点读写NFS/CephFS 支持ReadWriteOncePodRWOP单 Pod 读写K8S 1.22仅 CSI 卷3.4 PV 回收策略策略行为适用场景Retain保留 PV 和数据需手动清理后重新使用生产环境最安全Delete删除 PV 和对应的后端存储数据测试环境、动态供给Recycle执行rm -rf清理后重新可用已废弃旧版兼容四、实战一手动创建 PV PVC Pod4.1 NFS 后端准备在 NFS 服务器上创建存储目录# 在 NFS 服务器10.0.0.250上创建 PV 对应的目录mkdir-pv/data/nfs-server/pv/linux/pv00{1,2,3}# 确认 NFS 共享配置exportfs# /data/nfs-server world4.2 创建 PV# manual-pv.yamlapiVersion:v1kind:PersistentVolumemetadata:name:pv01spec:accessModes:-ReadWriteMany# NFS 支持多节点读写nfs:path:/data/nfs-server/pv/linux/pv001server:10.0.0.250persistentVolumeReclaimPolicy:Retain# 保留回收策略capacity:storage:2Gi---apiVersion:v1kind:PersistentVolumemetadata:name:pv02spec:accessModes:-ReadWriteManynfs:path:/data/nfs-server/pv/linux/pv002server:10.0.0.250persistentVolumeReclaimPolicy:Retaincapacity:storage:5Gi---apiVersion:v1kind:PersistentVolumemetadata:name:pv03spec:accessModes:-ReadWriteManynfs:path:/data/nfs-server/pv/linux/pv003server:10.0.0.250persistentVolumeReclaimPolicy:Retaincapacity:storage:10Gikubectl apply-fmanual-pv.yaml# 查看所有 PV状态为 Available等待绑定kubectl getpv# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE# pv01 2Gi RWX Retain Available 4s# pv02 5Gi RWX Retain Available 4s# pv03 10Gi RWX Retain Available 4s4.3 创建 PVCPVC 会根据accessModes和storage请求自动匹配最合适的 PV# manual-pvc.yamlapiVersion:v1kind:PersistentVolumeClaimmetadata:name:pvc001spec:accessModes:-ReadWriteManyresources:limits:storage:4Gi# 最大不超过 4Girequests:storage:3Gi# 至少需要 3Gikubectl apply-fmanual-pvc.yaml# PVC 自动绑定了 pv025Gi 3Gi 且 4Gikubectl get pvc,pv# NAME STATUS VOLUME CAPACITY ACCESS MODES# persistentvolumeclaim/pvc001 Bound pv02 5Gi RWX## NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM# pv01 2Gi RWX Retain Available# pv02 5Gi RWX Retain Bound default/pvc001 ← 已绑定# pv03 10Gi RWX Retain AvailablePV 绑定规则PVC 按accessModes过滤匹配的 PV再按storage请求大小筛选PV 容量 PVC 请求最后选择容量最小的 PV 进行绑定。4.4 Pod 引用 PVC# deploy-pvc.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:deploy-pvcspec:replicas:3selector:matchLabels:apps:v1template:metadata:labels:apps:v1spec:volumes:-name:datapersistentVolumeClaim:claimName:pvc001# 引用 PVCcontainers:-name:c1image:nginx:1.20volumeMounts:-name:datamountPath:/usr/share/nginx/html# 挂载到 Nginx 目录kubectl apply-fdeploy-pvc.yaml# 多个 Pod 跨节点共享同一块 NFS 存储kubectl get pods-owide# NAME READY IP NODE# deploy-pvc-xxx-f2b69 1/1 10.100.2.106 worker233# deploy-pvc-xxx-q56hp 1/1 10.100.2.107 worker233# deploy-pvc-xxx-sjct4 1/1 10.100.1.228 worker232# 跨节点访问验证所有 Pod 返回相同数据curl10.100.2.106# 同一份数据curl10.100.1.228# 同一份数据五、实战二StorageClass 动态存储手动创建 PV 的问题很明显每块存储都需要管理员手动创建和维护。当 Pod 数量很多时管理成本极高。StorageClassSC通过动态供给解决了这个问题。5.1 手动 vs 动态对比手动模式PV 预创建 管理员创建 PV(1Gi) PV(2Gi) PV(5Gi) PV(10Gi) ... PVC 按 size 匹配可用的 PV 问题PV 大小不一定精确匹配可能浪费存储 动态模式SC 自动创建 管理员创建一个 StorageClass关联 NFS 后端 PVC 只需指定 storageClassName 请求大小 SC 自动创建精确大小的 PV 优势按需分配零浪费5.2 部署 NFS CSI Driver# 克隆 NFS CSI Drivergitclone https://github.com/kubernetes-csi/csi-driver-nfs.gitcdcsi-driver-nfs-4.9.0# 安装 NFS CSI Driver所有节点需导入镜像./deploy/install-driver.sh v4.9.0local# 验证组件部署kubectl-nkube-system get pod-owide-lapp# csi-nfs-controller-xxx 4/4 Running worker232 (控制器1个)# csi-nfs-node-xxx 3/3 Running master231 (节点插件每个节点1个)# csi-nfs-node-xxx 3/3 Running worker232# csi-nfs-node-xxx 3/3 Running worker2335.3 创建 StorageClass# storageclass.yaml来自 csi-driver-nfs-4.9.0/deploy/v4.9.0/apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:nfs-csiprovisioner:nfs.csi.k8s.ioparameters:server:10.0.0.250share:/data/nfs-server/sc/reclaimPolicy:DeletevolumeBindingMode:Immediatekubectl apply-fstorageclass.yaml kubectl get sc# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE# nfs-csi nfs.csi.k8s.io Delete Immediate5.4 PVC 使用动态存储类# pvc-sc.yamlapiVersion:v1kind:PersistentVolumeClaimmetadata:name:pvc-sc-dynamicspec:storageClassName:nfs-csi# 指定存储类accessModes:-ReadWriteManyresources:requests:storage:1Gi# 自动创建精确 1Gi 的 PVkubectl apply-fpvc-sc.yaml# PV 自动创建名称为 UUID 格式由 SC 动态生成kubectl get pvc,sc,pv# NAME STATUS VOLUME CAPACITY STORAGECLASS# pvc-sc-dynamic Bound pvc-91a29925-xxxx 1Mi nfs-csi## NAME PROVISIONER RECLAIMPOLICY# nfs-csi nfs.csi.k8s.io Delete## NAME CAPACITY RECLAIM POLICY STATUS# pvc-91a29925-26ae-448f-9748-28d0e54fe97c 1Mi Delete Bound default/pvc-sc-dynamic nfs-csi动态供给的关键PVC 创建时指定storageClassNameSC 会自动创建一个容量精确匹配的 PV 并完成绑定。PVC 删除时如果回收策略是 DeletePV 和后端数据也会自动清理。5.5 配置默认存储类K8S 支持设置一个默认存储类PVC 不指定storageClassName时自动使用# 设置默认存储类kubectl patch sc nfs-csi-p\{metadata: {annotations:{storageclass.kubernetes.io/is-default-class:true}}}# 验证出现 default 标记kubectl get sc# NAME PROVISIONER RECLAIMPOLICY DEFAULT# nfs-csi (default) nfs.csi.k8s.io Delete ✓# 取消默认kubectl patch sc nfs-csi-p\{metadata: {annotations:{storageclass.kubernetes.io/is-default-class:false}}}声明式定义多个存储类# sc-multiple.yamlapiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:sc-ssdannotations:storageclass.kubernetes.io/is-default-class:falseprovisioner:nfs.csi.k8s.ioparameters:server:10.0.0.250share:/data/nfs-server/sc-ssd/reclaimPolicy:DeletevolumeBindingMode:ImmediatemountOptions:-nfsvers4.1---apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:sc-hddannotations:storageclass.kubernetes.io/is-default-class:trueprovisioner:nfs.csi.k8s.ioparameters:server:10.0.0.250share:/data/nfs-server/sc-hdd/生产建议不同性能的存储SSD/HDD定义不同的 SCPVC 按需选择实现存储分级管理。六、实战三StatefulSet 独享存储6.1 有状态服务 vs 无状态服务无状态服务Nginx、Tomcat - 所有 Pod 完全等价 - Pod 重启后无需恢复状态 - Deployment PVC 即可多 Pod 共享存储 有状态服务MySQL、Redis、Kafka - Pod 之间有主从/分片关系 - 每个 Pod 需要独立的存储 - Pod 需要稳定的网络标识名称/IP - 启动/停止有严格的顺序要求StatefulSet 解决的三大难题难题StatefulSet 的解决方案启动/停止顺序按序号 0→N 启动N→0 终止独立存储VolumeClaimTemplate 为每个 Pod 创建独立 PVC稳定网络标识Headless Service 固定编号sts-name-0, sts-name-1…6.2 Headless Service稳定网络标识Headless Service无头服务的核心特征是clusterIP: None——不分配虚拟 IPDNS 直接解析到 Pod IP# Headless ServiceapiVersion:v1kind:Servicemetadata:name:svc-headlessspec:ports:-port:80name:webclusterIP:None# 关键设为 None 即为 Headless Serviceselector:app:nginx# StatefulSetapiVersion:apps/v1kind:StatefulSetmetadata:name:stsspec:selector:matchLabels:app:nginxserviceName:svc-headless# 关键关联 Headless Servicereplicas:3template:metadata:labels:app:nginxspec:containers:-name:nginximage:nginx:1.20kubectl apply-fsts-headless.yaml# Pod 按序号创建0 → 1 → 2kubectl get sts,svc,pods-owide# NAME READY AGE# statefulset.apps/sts 3/3 15s## NAME TYPE CLUSTER-IP PORT(S)# service/svc-headless ClusterIP None 80/TCP ← 无头服务## NAME READY IP NODE# pod/sts-0 1/1 10.100.2.118 worker233 ← 按序创建# pod/sts-1 1/1 10.100.1.235 worker232# pod/sts-2 1/1 10.100.2.119 worker233DNS 解析验证# 每个 Pod 都有独立的 DNS 记录dig10.200.0.10 sts-0.svc-headless.default.svc.oldboyedu.com short# 10.100.2.151dig10.200.0.10 sts-1.svc-headless.default.svc.oldboyedu.com short# 10.100.1.9# Pod 内部可以通过名称互相访问kubectlexec-itsts-0 --sh/# ping sts-1.svc-headless -c 3# PING sts-1.svc-headless (10.100.1.10): 56 data bytes# 3 packets transmitted, 3 packets received, 0% packet loss6.3 Pod 删除后网络标识保持稳定# 删除所有 Podkubectl delete pods-lappnginx# pod sts-0 deleted# pod sts-1 deleted# pod sts-2 deleted# StatefulSet 自动重建名称不变、DNS 不变kubectl get pods-owide# NAME READY IP NODE AGE# pod/sts-0 1/1 10.100.2.153 worker233 5s ← 名称不变# pod/sts-1 1/1 10.100.1.10 worker232 3s ← 名称不变# pod/sts-2 1/1 10.100.2.154 worker233 2s ← 名称不变# DNS 仍然有效IP 可能变化但 DNS 名称不变kubectlexec-itsts-0 --pingsts-1.svc-headless-c3# PING sts-1.svc-headless (10.100.1.10): 56 data bytes# 0% packet loss与 Deployment 的关键区别Deployment 的 Pod 名称为随机哈希重建后名称和 DNS 完全改变StatefulSet 的 Pod 名称固定为sts-{0,1,2,...}重建后名称和 DNS 保持不变。6.4 VolumeClaimTemplate独享存储StatefulSet 通过volumeClaimTemplates为每个 Pod 自动创建独立的 PVCapiVersion:apps/v1kind:StatefulSetmetadata:name:stsspec:selector:matchLabels:app:nginxserviceName:svc-headlessreplicas:3volumeClaimTemplates:# 关键存储卷申请模板-metadata:name:dataspec:accessModes:[ReadWriteOnce]storageClassName:nfs-csi# 使用动态存储类resources:requests:storage:2Gi# 每个 Pod 独享 2Gitemplate:metadata:labels:app:nginxspec:containers:-name:nginximage:nginx:1.20volumeMounts:-name:datamountPath:/usr/share/nginx/htmlkubectl apply-fsts-volumeclaimtemplate.yaml# 每个 Pod 自动创建独立的 PVCkubectl get pvc# NAME STATUS VOLUME CAPACITY#>#>#>七、CSI、CRI、CNI 三大接口区分在面试和实际工作中经常需要区分 K8S 的三大插件接口接口全称职责典型插件CSIContainer Storage Interface存储管理创建/挂载/删除卷NFS CSI、Ceph CSI、AWS EBS CSICRIContainer Runtime Interface容器运行时管理Docker已废弃、containerd、CRI-OCNIContainer Network Interface网络管理Pod 间通信、网络策略Flannel、Calico、Cilium一句话记忆CSI 管存、CRI 管跑、CNI 管通。八、生产环境最佳实践8.1 存储选型建议场景推荐方案原因开发/测试NFS SC部署简单、成本低高可用 Web 服务Ceph RBD SC性能高、支持快照文件共享服务CephFS / NFS支持 RWX 多节点读写日志存储CephFS PVC容量大、可扩展数据库Ceph RBD StatefulSet低延迟、块设备级性能8.2 生产检查清单- [ ] PVC 设置了合理的 storage requests避免浪费 - [ ] 生产环境 PV 回收策略使用 Retain防止误删数据 - [ ] StatefulSet 使用 Headless Service 确保稳定网络标识 - [ ] 动态存储类已配置默认 SC简化 PVC 创建 - [ ] NFS/Ceph 后端已做高可用和备份 - [ ] PVC 设置 storage limits 防止无限使用 - [ ] 定期检查 Released 状态的 PV 并清理8.3 PV/PVC 不绑定的排查思路# 1. 检查 PVC 状态kubectl get pvc# Pending → 未绑定成功# 2. 查看 PVC 事件kubectl describe pvcpvc-name# 常见原因# - 没有匹配的 PV手动模式# - 没有匹配的 SC动态模式# - accessModes 不匹配# - storage 大小超过所有可用 PV# 3. 检查 SC 是否存在kubectl get sc# 4. 检查可用 PVkubectl getpv十、总结K8S 存储体系的核心知识链路emptyDir/hostPath临时/本地存储 ↓ PV手动创建持久卷← PVC声明式申请→ Pod ↓ SC动态供给存储类← PVC → Pod自动创建 PV ↓ StatefulSet VolumeClaimTemplate每个 Pod 独享存储 ↓ CSI标准存储接口→ Ceph / NFS / 云存储后端存储掌握 K8S 存储的三个层次基础层理解 PV/PVC/SC 的关系和绑定机制进阶层掌握动态存储供给和默认存储类配置实战层StatefulSet Headless Service VolumeClaimTemplate 构建完整的有状态服务存储方案