容器文件系统通常跟随容器生命周期变化。应用重建、Pod 漂移或节点故障时,如果数据只保存在容器内部,就可能丢失。Kubernetes 存储体系的目标,是把“应用需要什么存储”与“集群如何提供存储”解耦。
一、先区分临时卷与持久化存储
emptyDir 等卷适合缓存、临时文件和同一 Pod 内多个容器共享数据。Pod 被删除后,这类数据通常也会消失。
需要跨 Pod 重建保留的数据,应使用持久化存储。Kubernetes 通过 PersistentVolume(PV)、PersistentVolumeClaim(PVC)、StorageClass 和 CSI 驱动组织这套流程。
二、PV、PVC 与 StorageClass 各自负责什么
PersistentVolume(PV)代表集群中的一块可用存储资源。它可能由管理员提前创建,也可能由系统动态创建。
PersistentVolumeClaim(PVC)代表应用对存储的申请,例如需要 10Gi 容量、ReadWriteOnce 访问模式,并指定某个 StorageClass。
StorageClass 描述一种存储服务,包括使用哪个 provisioner、后端参数、回收策略和绑定时机。它类似一份存储“套餐”,例如高性能云盘、普通磁盘或共享文件存储。
CSI 驱动负责让 Kubernetes 与实际存储系统交互。云盘、Ceph、NFS 或其他后端通常需要相应的 CSI 驱动或外部 provisioner。
三、PV 与 PVC 如何匹配
PVC 会根据以下条件寻找或创建合适的 PV:
1. storageClassName 是否一致。
2. PV 容量是否不少于 PVC 请求容量。
3. accessModes 是否满足要求。
4. volumeMode 是 Filesystem 还是 Block。
5. 标签选择器等额外条件是否匹配。
容量通常是“向上匹配”:PVC 申请 8Gi,可以绑定满足其他条件的 10Gi PV,但不会绑定 5Gi PV。
PV 与 PVC 的绑定是一对一关系。一个 PVC 可以被一个或多个 Pod 引用,但同一时刻能否跨节点挂载,取决于访问模式和后端能力。
四、正确理解访问模式
ReadWriteOnce(RWO):卷可以由一个节点以读写方式挂载。它并不严格等于“只能给一个 Pod 使用”;同一节点上的多个 Pod 是否可用还要看驱动和应用场景。
ReadOnlyMany(ROX):多个节点可以只读挂载。
ReadWriteMany(RWX):多个节点可以读写挂载,常见于支持共享文件系统的后端。
ReadWriteOncePod(RWOP):限制为集群中的单个 Pod 读写,是否可用取决于 CSI 驱动及集群版本支持。
访问模式是存储能力声明,不等同于文件锁、数据库一致性或应用层并发控制。
五、静态供给与动态供给
静态供给:管理员先创建实际存储和 PV,PVC 再与之绑定。它适合需要精确控制既有存储的场景,但运维成本较高。
动态供给:管理员配置 StorageClass,用户创建 PVC 后,provisioner 自动创建对应存储和 PV。这样应用团队不必直接了解云盘或存储阵列的具体创建流程。
如果 PVC 一直处于 Pending,常见原因包括:没有默认 StorageClass、指定的 StorageClass 不存在、CSI 驱动异常、访问模式不受支持、容量不足或拓扑条件无法满足。
六、PVC 与 Deployment 的示例
先创建 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: web-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 10Gi
再在 Deployment 中按名称引用:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:stable-alpine
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: web-data
Deployment 和 PVC 是通过 claimName 关联的。扩容 Deployment 副本前,要确认后端是否支持所需的并发挂载方式;不要假设一个 RWO 卷能安全地跨多个节点供所有副本共同写入。
七、StatefulSet 为什么常与存储一起出现
无状态应用的副本通常可以互换,而数据库、消息队列等有状态应用往往需要稳定身份、固定启动顺序和独立存储。
StatefulSet 可以为每个 Pod 提供稳定名称,并通过 volumeClaimTemplates 为每个副本创建独立 PVC。例如 db-0、db-1、db-2 会分别拥有自己的存储,而不是三个副本共享同一个数据目录。
需要特别注意:删除 StatefulSet 或缩容时,PVC 的处理策略取决于配置和操作方式。不要在没有备份和确认回收策略的情况下批量删除资源。
八、StorageClass 的两个关键策略
reclaimPolicy 决定 PVC 释放后动态创建的 PV 如何处理:
Delete:通常删除 PV 及后端存储,适合可重建数据,但误删风险较高。
Retain:保留底层数据,等待管理员人工处理,更适合重要数据。
volumeBindingMode 决定何时绑定或创建卷:
Immediate:PVC 创建后立即绑定或供给。
WaitForFirstConsumer:等使用 PVC 的 Pod 参与调度后再供给存储。对于受可用区、节点或拓扑限制的存储,这种方式通常能避免“卷建在一个区域,Pod 却被调度到另一个区域”的问题。
九、块存储、文件存储和对象存储
块存储常见于云硬盘和数据库数据盘,通常提供较低延迟,但跨节点共享能力受限。
文件存储通过文件系统协议提供目录共享,常用于多个 Pod 共同读取或写入文件,但性能和一致性要结合后端评估。
对象存储通常通过 HTTP API 或 SDK 访问,不一定以普通文件系统卷的形式挂载。应用直接使用对象存储接口,往往比强行把它模拟成本地磁盘更符合其设计。
十、排查与运维检查
kubectl get pvc,pv
kubectl describe pvc web-data
kubectl get storageclass
kubectl get pod -o wide
kubectl get events --sort-by=.metadata.creationTimestamp
还应检查 CSI Controller、CSI Node Pod、云账号权限、节点拓扑、挂载日志和后端容量。
容量扩展是否真正生效,取决于 StorageClass 的 allowVolumeExpansion、CSI 驱动能力、卷类型以及文件系统是否支持扩容。YAML 中把 10Gi 改成 20Gi,并不保证所有后端都能立即在线扩容。
总结
PVC 表达应用需求,PV 表达实际存储资源,StorageClass 定义供给策略,CSI 驱动连接真实后端。选择存储时,不仅要看容量,还要同时考虑访问模式、拓扑、回收策略、备份、故障恢复和应用一致性。
参考资料:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
https://kubernetes.io/docs/concepts/storage/storage-classes/
https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/