运维知识
悠悠
2026年4月12日

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 storageclass

Pod挂载失败

挂载失败通常是因为:

  • 节点不支持该存储类型
  • 权限问题
  • 存储系统故障

我遇到过一次EBS卷挂载失败,最后发现是AWS的配额限制导致的。

数据丢失

这个问题最严重,预防措施:

  • 使用Retain回收策略
  • 定期备份重要数据
  • 测试恢复流程

最佳实践总结

根据我这些年的经验,总结几个最佳实践:

  1. 生产环境优先使用动态供应,配置合适的StorageClass
  2. 重要数据使用Retain回收策略,手动管理PV生命周期
  3. 合理选择访问模式,大部分情况用RWO就够了
  4. 监控存储使用情况,及时发现问题
  5. 定期备份数据,做好灾难恢复准备

存储选型建议

  • 数据库:高性能SSD + RWO + Retain策略
  • 文件服务:网络存储 + RWX + Delete策略
  • 日志收集:大容量HDD + RWO + Delete策略
  • 临时数据:本地存储 + RWO + Delete策略

如果觉得这篇文章对你有帮助,别忘了点个赞、转发一下,让更多的运维小伙伴看到。你们的支持是我持续输出干货的动力!

关注@运维躬行录,获取更多实战干货,我们一起在运维的道路上躬行实践!


公众号:运维躬行录
个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码