Kubernetes PersistentVolume
Kubernetes PersistentVolume
什么是 PersistentVolume
在 Kubernetes 中,Pod 的文件系统是临时的。当 Pod 被删除、重启或调度到其他节点时,其容器层中的所有数据都会丢失。对于无状态服务(如 Web 前端、API 网关)来说这不是问题,但对于数据库、消息队列、文件存储等有状态服务,数据持久化是必须的。
Kubernetes 通过 PersistentVolume(PV)和 PersistentVolumeClaim(PVC)两层抽象来解决数据持久化问题:
- PersistentVolume(PV):集群级别的存储资源,由管理员创建或由 StorageClass 动态供给。它代表实际的存储后端(如 AWS EBS、NFS、Ceph RBD 等)
- PersistentVolumeClaim(PVC):用户(开发者)对存储的请求。PVC 声明需要多少存储空间、什么访问模式,Kubernetes 自动将 PVC 绑定到满足条件的 PV
这种双层解耦设计带来了几个关键优势:
- 存储供给与消费分离:应用开发者不需要了解底层存储类型,只需要声明需求
- 跨云平台可移植:PVC 是标准抽象,更换云平台只需要修改 StorageClass
- 动态供给:通过 StorageClass 自动创建 PV,无需管理员手动操作
- 生命周期管理:PV 的回收策略独立于 Pod 和 PVC
存储访问模式
PVC 支持以下访问模式,每种模式决定了 Pod 如何使用存储:
| 访问模式 | 缩写 | 说明 | 典型存储后端 |
|---|---|---|---|
| ReadWriteOnce | RWO | 单个节点读写,最常用 | AWS EBS、GCE PD、Ceph RBD |
| ReadOnlyMany | ROX | 多个节点只读 | NFS、CephFS、EFS |
| ReadWriteMany | RWX | 多个节点读写 | NFS、CephFS、EFS |
| ReadWriteOncePod | RWOP | 单个 Pod 读写(K8s 1.27+) | 任何支持 RWO 的后端 |
# PVC 声明访问模式
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce # 最常用:单个节点读写
# accessModes:
# - ReadOnlyMany # 多节点只读(适合共享静态资源)
# - ReadWriteMany # 多节点读写(需要 NFS/CephFS 支持)重要提示:RWO 模式下,PVC 只能挂载到一个节点上的一个 Pod。如果 Deployment 有多个副本分布在不同节点上,不能共享同一个 RWO PVC。如果需要多副本共享存储,必须使用 RWX 模式或 StatefulSet + volumeClaimTemplates。
回收策略
当 PVC 被删除时,PV 的回收策略决定数据的处理方式:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Retain | 保留数据,PV 变为 Released 状态,需要手动处理 | 生产数据库(防止误删) |
| Delete | 自动删除 PV 和底层存储 | 临时数据、测试环境 |
| Recycle | 已废弃,使用动态供给替代 | - |
# StorageClass 中设置默认回收策略
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-storage
provisioner: ebs.csi.aws.com
reclaimPolicy: Retain # 生产环境推荐 Retain
# reclaimPolicy: Delete # 测试环境可以用 DeleteStorageClass 与动态供给
什么是 StorageClass
StorageClass 定义了存储的"类别"——类似于是存储的"菜单"。管理员预先定义不同性能等级的 StorageClass,用户通过 PVC 引用 StorageClass 名称来选择存储类型:
# 高性能 SSD 存储类(用于数据库)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com
parameters:
type: gp3 # AWS EBS gp3 类型
iopsPerGB: "50" # 每 GB 50 IOPS
throughput: "250" # 250 MB/s 吞吐
encrypted: "true" # 启用加密
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true # 允许在线扩容
---
# 标准 HDD 存储类(用于日志、备份)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: hdd-storage
provisioner: ebs.csi.aws.com
parameters:
type: st1 # AWS EBS st1 类型(吞吐优化 HDD)
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
---
# 低成本归档存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: archive-storage
provisioner: ebs.csi.aws.com
parameters:
type: sc1 # AWS EBS sc1 类型(冷 HDD)
reclaimPolicy: Delete
volumeBindingMode: ImmediatevolumeBindingMode
volumeBindingMode 决定了 PV 的创建和绑定时机:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Immediate | PVC 创建后立即绑定 PV | 不需要考虑节点位置的存储 |
| WaitForFirstConsumer | 等到 Pod 调度后再绑定 | 云盘等有区域限制的存储 |
# WaitForFirstConsumer 的重要性
# AWS EBS 卷只能在同一个可用区(AZ)内挂载
# 如果使用 Immediate 模式,PV 可能在 us-east-1a 创建
# 但 Pod 被调度到 us-east-1b,导致挂载失败
# WaitForFirstConsumer 解决方案:
# 1. PVC 创建后处于 Pending 状态
# 2. 等待使用该 PVC 的 Pod 被调度到某个节点
# 3. 确定节点所在的 AZ 后,在该 AZ 创建 PV
# 4. 绑定 PVC 和 PV多云 StorageClass 示例
# GCP Persistent Disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gcp-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
replication-type: regional-pd
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# Azure Disk
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: azure-ssd
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
cachingMode: ReadOnly
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
---
# NFS(使用 NFS CSI Driver)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.example.com
share: /exports/data
reclaimPolicy: Retain
volumeBindingMode: Immediate
mountOptions:
- hard
- nfsvers=4.1PVC 创建与使用
基础 PVC
# PVC 申请存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
labels:
app: postgres
spec:
accessModes:
- ReadWriteOnce
storageClassName: ssd-storage # 引用 StorageClass
resources:
requests:
storage: 50Gi # 申请 50GB
---
# 在 Deployment 中使用 PVC(注意:Deployment 多副本共享 RWO PVC 会失败)
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: production
spec:
replicas: 1 # RWO PVC 只能挂载到一个 Pod
selector:
matchLabels:
app: postgres
template:
spec:
containers:
- name: postgres
image: postgres:15
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
# subPath 可以挂载卷中的子目录
# subPath: postgres
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-dataStatefulSet + volumeClaimTemplates
StatefulSet 的 volumeClaimTemplates 是管理有状态应用存储的推荐方式。它为每个 Pod 副本自动创建独立的 PVC:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-cluster
namespace: production
spec:
serviceName: postgres-headless # 必须关联一个 Headless Service
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
# volumeClaimTemplates 为每个 Pod 创建独立的 PVC
volumeClaimTemplates:
- metadata:
name: data
labels:
app: postgres
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: ssd-storage
resources:
requests:
storage: 50GiStatefulSet 创建后,会自动生成以下 PVC:
data-postgres-cluster-0 # Pod postgres-cluster-0 的存储
data-postgres-cluster-1 # Pod postgres-cluster-1 的存储
data-postgres-cluster-2 # Pod postgres-cluster-2 的存储关键特性:即使 Pod 被删除重建,StatefulSet 也会将新 Pod 绑定到原来的 PVC,确保数据不丢失。
# 查看 StatefulSet 的 PVC
kubectl get pvc -n production -l app=postgres
# 删除 Pod 后重建,数据保持
kubectl delete pod postgres-cluster-0 -n production
# StatefulSet 会自动重建 Pod,并挂载原来的 PVC
kubectl get pvc -n production -l app=postgres
# 所有 PVC 仍然处于 Bound 状态静态 PV 供给
在某些场景下(如使用 NFS 共享存储),管理员需要手动创建 PV:
# NFS 静态 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-shared-pv
labels:
type: nfs
environment: production
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany # NFS 支持多节点读写
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
mountOptions:
- hard
- nfsvers=4.1
- rsize=1048576
- wsize=1048576
nfs:
server: 10.0.0.50
path: /data/shared
---
# PVC 绑定静态 PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-data
namespace: production
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-storage
volumeName: nfs-shared-pv # 显式指定 PV 名称
resources:
requests:
storage: 100Gi
# 如果不指定 volumeName,Kubernetes 会通过 selector 匹配
# selector:
# matchLabels:
# type: nfs
# environment: productionPV 扩容
# 扩容 PVC(StorageClass 必须设置 allowVolumeExpansion: true)
kubectl patch pvc postgres-data -n production \
-p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
# 查看扩容状态
kubectl describe pvc postgres-data -n production | grep -A5 "Conditions"
# Conditions:
# Type Reason Age From
# ---- ------ ---- ----
# Waiting FileSystemResizePending 5s external-resizer
# 查看 PV 是否已扩容
kubectl describe pv | grep -A5 "Capacity"
# 如果文件系统需要在线扩容
# ext4 和 XFS 支持 online resize
# Kubernetes 1.15+ CSI Driver 支持自动文件系统扩容本地持久化卷(Local PV)
对于高性能数据库场景(如 MySQL、Redis),本地存储通常比网络存储性能更好。Local PV 使用节点上的本地磁盘或目录:
# Local PV 使用节点上的本地存储
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-ssd-pv
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-ssd
local:
path: /mnt/ssd/data # 节点上的本地路径
nodeAffinity: # 必须指定节点亲和性
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1 # 只能在这个节点上使用# Local StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-ssd
provisioner: kubernetes.io/no-provisioner # Local PV 不支持动态供给
volumeBindingMode: WaitForFirstConsumerLocal PV 的局限性:节点故障时数据可能丢失。建议使用 Local PV + StatefulSet + Pod 反亲和性来降低风险,并配合应用层的数据复制(如 MySQL 主从复制)。
PV 管理与运维
日常运维命令
# 查看 PV/PVC 状态
kubectl get pv
kubectl get pvc -A
# 查看详细信息
kubectl describe pv <pv-name>
kubectl describe pvc <pvc-name> -n <namespace>
# 查看 PVC 的绑定状态和存储使用量
kubectl get pvc -n production -o wide
# 查看哪些 Pod 使用了某个 PVC
kubectl get pods -n production -o json | \
jq -r '.items[].spec.volumes[]? | select(.persistentVolumeClaim.claimName=="postgres-data") | .name'处理卡在 Terminating 的 PVC
# PVC 卡在 Terminating 状态
kubectl get pvc -n production
# NAME STATUS VOLUME CAPACITY ...
# postgres-data Terminating pvc-xxx 50Gi ...
# 原因:通常是因为 PV 的 finalizer 未清理
# 解决方案 1:检查是否有 Pod 仍在使用该 PVC
kubectl get pods -n production -o json | \
jq -r '.items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName=="postgres-data") | .metadata.name'
# 解决方案 2:强制移除 finalizer
kubectl patch pvc postgres-data -n production \
-p '{"metadata":{"finalizers":null}}'
# 解决方案 3:编辑 PVC 删除 finalizer
kubectl edit pvc postgres-data -n production
# 找到 finalizers 字段,删除后保存存储备份
# 使用 Velero 备份 PV 数据
# 安装 Velero
velero install --provider aws --plugins velero/velero-plugin-for-aws:v1.7.0 \
--bucket velero-backups --secret-file ./credentials-velero \
--use-volume-snapshots=true
# 创建备份
velero backup create myapp-backup --include-namespaces production
# 恢复备份
velero restore create --from-backup myapp-backup
# 定时备份
velero schedule create daily-backup --schedule="0 2 * * *" \
--include-namespaces production存储安全
# 1. PVC 的安全上下文
apiVersion: apps/v1
kind: StatefulSet
spec:
template:
spec:
securityContext:
fsGroup: 1000 # 设置文件系统组,确保容器进程可以读写
containers:
- name: app
securityContext:
runAsUser: 1000
runAsGroup: 1000
volumeMounts:
- name: data
mountPath: /app/data
---
# 2. 加密存储卷(AWS EBS 加密)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: encrypted-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true" # 启用 EBS 加密
kmsKeyId: "arn:aws:kms:..." # 使用自定义 KMS 密钥优点
缺点
总结
PV/PVC 是 Kubernetes 有状态应用存储管理的基础。生产环境推荐使用动态供给和 StorageClass 分层,为有状态服务使用 StatefulSet 的 volumeClaimTemplates 管理存储生命周期。
核心选择指南:
- 有状态服务(数据库):StatefulSet + volumeClaimTemplates + SSD StorageClass
- 共享文件存储:NFS StorageClass + RWX PVC
- 高性能本地存储:Local PV + StatefulSet + 应用层复制
- 临时数据:emptyDir(不需要 PV/PVC)
关键知识点
- PVC 和 PV 是一对一绑定关系,通过 storageClassName 和容量匹配
- WaitForFirstConsumer 延迟绑定,确保 PV 创建在 Pod 调度的节点所在区域
- StatefulSet 的 volumeClaimTemplates 为每个副本创建独立的 PVC
- PV 的 reclaimPolicy 决定 PVC 删除后数据的保留策略
- 扩容需要 StorageClass 的 allowVolumeExpansion: true
- Local PV 不支持动态供给,使用 kubernetes.io/no-provisioner
项目落地视角
- 按性能需求定义 2-3 个 StorageClass(SSD 高性能、HDD 归档、NFS 共享)
- 数据库类服务使用 StatefulSet + volumeClaimTemplates,每个副本独立存储
- 建立存储配额(ResourceQuota)限制每个命名空间的 PVC 总量
- 为生产数据启用存储加密(如 EBS encryption)
常见误区
- 以为 PVC 删除后数据还在(取决于 reclaimPolicy,Delete 策略会清空数据)
- 在 Deployment 中使用 PVC,多个副本会争抢同一个 PVC 的 ReadWriteOnce 锁
- 忘记设置 allowVolumeExpansion,导致无法在线扩容
- 忽略 volumeBindingMode,导致跨可用区挂载失败
- Local PV 节点故障后以为数据还在(需要应用层复制保障)
进阶路线
- 学习 CSI(Container Storage Interface)规范和自定义 CSI Driver 开发
- 掌握 Velero 等工具实现 PV 的跨集群备份与迁移
- 了解本地持久化卷(Local Persistent Volume)在高性能场景下的应用
- 学习 Portworx、Longhorn 等云原生存储解决方案
适用场景
- 数据库(MySQL、PostgreSQL、MongoDB)在 K8s 中运行需要持久化数据目录
- 共享文件存储场景(日志归档、静态资源、配置文件共享)
- 消息队列(Kafka、RabbitMQ)的数据目录需要持久化
- 机器学习训练数据的持久化存储
落地建议
- 为生产环境使用 Retain 回收策略,防止误删 PVC 导致数据永久丢失
- 监控 PV 使用率,设置容量告警(80% 警告、90% 紧急)
- 定期备份关键 PV 数据到异地存储,并验证恢复流程
- 使用 ResourceQuota 限制命名空间的存储使用量
排错清单
- PVC 一直 Pending:检查 StorageClass 是否存在、是否有可用 PV、容量是否匹配
- PV 挂载失败:检查节点上的 CSI Driver 是否正常、存储后端是否可达
- 扩容失败:确认 StorageClass 的 allowVolumeExpansion 是否为 true
- 跨可用区挂载失败:检查 volumeBindingMode 是否为 WaitForFirstConsumer
- PVC 卡在 Terminating:检查是否有 Pod 仍在使用,清理 finalizer
复盘问题
- 当前集群的 StorageClass 分层是否合理?是否有服务使用了不合适的存储等级?
- PV 的备份频率和保留策略是否满足业务 RPO/RTO 要求?
- 是否存在未被任何 PVC 绑定的孤立 PV?存储成本是否需要优化?
- StatefulSet 的 PVC 是否设置了正确的回收策略?
