K8s持久化存储深度解析:PV、PVC、StorageClass三剑客的生产实战
做了这么多年运维,K8s的存储这块一直是个让人又爱又恨的东西。说它复杂吧,确实概念挺多的,PV、PVC、StorageClass这些名词听起来就头大;说它简单吧,一旦搞明白了原理,用起来还是很顺手的。
今天我就把自己这些年在生产环境中踩过的坑、总结的经验分享给大家,让你们少走点弯路。
为什么需要持久化存储
容器本身是无状态的,这个大家都知道。Pod一重启,里面的数据就没了,这对于数据库、文件存储这些有状态应用来说简直是灾难。
我记得刚开始用K8s的时候,有次MySQL的Pod重启了,结果所有数据都丢了,那个心情...简直想死的心都有。从那以后我就深刻认识到,持久化存储在K8s中有多重要。
K8s的持久化存储主要解决这么几个问题:
- 数据持久性:Pod重启、迁移后数据不丢失
- 数据共享:多个Pod可以访问同一份数据
- 存储抽象:屏蔽底层存储实现细节
PV、PVC、StorageClass的关系
这三个概念的关系,我用一个比较接地气的比喻来解释:
PV(PersistentVolume)就像是一个仓库,管理员提前准备好的存储空间。
PVC(PersistentVolumeClaim)就像是租仓库的申请单,用户说我要多大的空间、什么类型的。
StorageClass就像是仓库的分类标准,比如高性能仓库、普通仓库、便宜仓库等。
具体来说:
PV(持久卷)
PV是集群级别的资源,由管理员创建,代表集群中的一块存储。它包含存储实现的具体细节,比如NFS、iSCSI、云存储等。
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.1.100
path: /data/nfs这个配置我在生产环境用过很多次,需要注意几个点:
capacity定义存储大小accessModes定义访问模式,RWO(ReadWriteOnce)表示只能被一个节点挂载persistentVolumeReclaimPolicy定义回收策略
PVC(持久卷声明)
PVC是用户对存储的请求,它会去寻找合适的PV进行绑定。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi这里有个坑要注意,PVC请求的存储大小必须小于等于PV提供的大小,而且访问模式也要匹配。
StorageClass(存储类)
StorageClass提供了动态创建PV的方式,不需要管理员手动创建PV。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
zone: us-west-2a静态供应 vs 动态供应
静态供应
静态供应就是管理员提前创建好PV,然后用户创建PVC去绑定。这种方式比较传统,适合小规模或者对存储有特殊要求的场景。
我在一个项目中就是用的静态供应,因为客户要求数据必须存储在指定的NFS服务器上,所以只能手动创建PV。
动态供应
动态供应通过StorageClass自动创建PV,用户只需要在PVC中指定StorageClass即可。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi这种方式更灵活,特别适合云环境。我现在基本都用动态供应,省事多了。
生命周期管理
PV和PVC的生命周期有几个关键阶段:
Available(可用)
PV已创建,但还没有被PVC绑定。
Bound(已绑定)
PV已经被PVC绑定,正在使用中。
Released(已释放)
PVC被删除,但PV还没有被回收。
Failed(失败)
自动回收失败。
这里要特别说一下回收策略,有三种:
- Retain:保留数据,需要手动清理
- Delete:自动删除PV和底层存储
- Recycle:已废弃,不建议使用
我在生产环境中一般用Retain策略,因为数据安全第一,宁可手动清理也不能误删数据。
访问模式详解
K8s支持三种访问模式:
ReadWriteOnce (RWO)
只能被一个节点以读写方式挂载。这是最常用的模式,适合大部分应用。
ReadOnlyMany (ROX)
可以被多个节点以只读方式挂载。适合配置文件、静态资源等场景。
ReadWriteMany (RWX)
可以被多个节点以读写方式挂载。这个模式对存储系统要求比较高,不是所有存储都支持。
我遇到过一个坑,就是用了不支持RWX的存储系统,结果Pod一直处于Pending状态。后来查了半天才发现是访问模式的问题。
实际生产案例
案例1:MySQL数据库持久化
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysql-storage
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
encrypted: "true"
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: mysql-storage
resources:
requests:
storage: 100Gi
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "password123"
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc这个配置我用了好几年,非常稳定。关键点是:
- 使用加密的EBS卷保证数据安全
- 100Gi的存储空间足够大部分应用使用
- 挂载到MySQL的数据目录
案例2:多Pod共享存储
有时候需要多个Pod共享同一份数据,比如Web应用的静态文件。
# 使用NFS的PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: shared-pv
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteMany
nfs:
server: nfs-server.example.com
path: /shared/data
---
# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 50Gi这种场景下必须使用支持RWX的存储系统,NFS是个不错的选择。
性能优化技巧
选择合适的存储类型
不同的存储类型性能差异很大:
- SSD:IOPS高,延迟低,适合数据库
- HDD:容量大,成本低,适合日志、备份
- 网络存储:可共享,但性能一般
我的经验是,数据库一定要用SSD,日志可以用HDD,配置文件用网络存储。
合理设置存储大小
存储大小不是越大越好,要根据实际需求来:
- 预留20-30%的空间用于扩展
- 考虑备份和快照的空间需求
- 定期清理无用数据
监控存储使用情况
我一般会用Prometheus监控存储使用率:
# 存储使用率告警规则
groups:
- name: storage.rules
rules:
- alert: PVCStorageUsageHigh
expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "PVC storage usage is high"常见问题排查
PVC一直Pending
这是最常见的问题,可能的原因:
- 没有合适的PV可以绑定
- StorageClass配置错误
- 存储资源不足
排查方法:
kubectl describe pvc my-pvc
kubectl get pv
kubectl get storageclassPod挂载失败
挂载失败通常是因为:
- 节点不支持该存储类型
- 权限问题
- 存储系统故障
我遇到过一次EBS卷挂载失败,最后发现是AWS的配额限制导致的。
数据丢失
这个问题最严重,预防措施:
- 使用Retain回收策略
- 定期备份重要数据
- 测试恢复流程
最佳实践总结
根据我这些年的经验,总结几个最佳实践:
- 生产环境优先使用动态供应,配置合适的StorageClass
- 重要数据使用Retain回收策略,手动管理PV生命周期
- 合理选择访问模式,大部分情况用RWO就够了
- 监控存储使用情况,及时发现问题
- 定期备份数据,做好灾难恢复准备
存储选型建议
- 数据库:高性能SSD + RWO + Retain策略
- 文件服务:网络存储 + RWX + Delete策略
- 日志收集:大容量HDD + RWO + Delete策略
- 临时数据:本地存储 + RWO + Delete策略
如果觉得这篇文章对你有帮助,别忘了点个赞、转发一下,让更多的运维小伙伴看到。你们的支持是我持续输出干货的动力!
关注@运维躬行录,获取更多实战干货,我们一起在运维的道路上躬行实践!
公众号:运维躬行录
个人博客:躬行笔记