存储管理
Kubernetes 提供了强大的存储抽象,将存储资源的管理与计算资源的管理分离。通过 PersistentVolume、PersistentVolumeClaim 和 StorageClass,Kubernetes 实现了存储资源的灵活配置和动态管理。
为什么需要持久化存储?
容器天生是临时的——当容器重启或迁移时,容器内的数据会丢失。对于无状态应用(如 Web 服务器),这不是问题。但对于有状态应用(如数据库、消息队列),数据持久化至关重要。
Kubernetes 通过以下机制解决数据持久化问题:
- PersistentVolume (PV):集群级别的存储资源,独立于 Pod 生命周期
- PersistentVolumeClaim (PVC):用户对存储资源的请求声明
- StorageClass:存储类的抽象,支持动态创建 PV
存储架构概览
PV 和 PVC 的生命周期
理解 PV 和 PVC 的生命周期对于正确使用存储至关重要。它们的交互遵循以下阶段:
1. Provisioning(供应)
PV 的供应有两种方式:
静态供应(Static Provisioning):集群管理员预先创建 PV,指定存储细节:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
nfs:
server: 192.168.1.100
path: /exports/data
动态供应(Dynamic Provisioning):当没有匹配的静态 PV 时,集群根据 StorageClass 自动创建 PV:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassName: fast-storage # 指定 StorageClass
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
动态供应需要:
- PVC 请求特定的 StorageClass
- 管理员已创建并配置该 StorageClass
- 集群启用了
DefaultStorageClass准入控制器
2. Binding(绑定)
控制平面监视新的 PVC,寻找匹配的 PV 并绑定。绑定规则:
- StorageClass 必须匹配
- 访问模式必须兼容
- 请求的存储大小不能超过 PV 容量
重要特性:
- 绑定是一对一的,一个 PV 只能绑定一个 PVC
- 如果没有匹配的 PV,PVC 会一直处于 Pending 状态
- 绑定后,PV 归该 PVC 用户所有
3. Using(使用)
Pod 通过 PVC 使用存储:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
使用注意事项:
- 对于支持多种访问模式的存储,需在 Pod 中指定访问模式
- PVC 必须与 Pod 在同一命名空间
- 多个 Pod 可以共享同一个 PVC(取决于访问模式)
4. Storage Object in Use Protection(存储对象使用保护)
这是一个重要的保护机制,确保正在使用的 PVC 和绑定的 PV 不会被意外删除,防止数据丢失。
保护行为:
| 场景 | 行为 |
|---|---|
| 删除正在使用的 PVC | 延迟删除,直到没有 Pod 使用 |
| 删除已绑定的 PV | 延迟删除,直到与 PVC 解绑 |
当 PVC 正在被保护时,可以看到:
kubectl describe pvc my-pvc
# Status: Terminating
# Finalizers: [kubernetes.io/pvc-protection]
当 PV 正在被保护时:
kubectl describe pv my-pv
# Status: Terminating
# Finalizers: [kubernetes.io/pv-protection]
5. Reclaiming(回收)
当用户完成存储使用后,可以删除 PVC 触发回收流程。回收策略决定了 PV 释放后的处理方式:
Retain(保留)
手动回收,最安全的方式:
persistentVolumeReclaimPolicy: Retain
回收流程:
- PVC 删除后,PV 变为 Released 状态
- PV 保留数据,但不能被新 PVC 绑定
- 管理员手动处理:
- 清理存储上的数据
- 删除 PV 对象
- 或创建新 PV 复用该存储
适用于:需要保留数据、合规性要求高的场景。
Delete(删除)
自动删除存储资源:
persistentVolumeReclaimPolicy: Delete
行为:
- 删除 Kubernetes 中的 PV 对象
- 删除外部存储系统中的实际存储资产
适用于:动态供应的云存储、不需要保留数据的场景。
PV 删除保护 Finalizer(Kubernetes 1.33+ 稳定版):
为了确保数据安全,Kubernetes 会为 PV 添加 finalizer:
# CSI 卷
Finalizers: [kubernetes.io/pv-protection, external-provisioner.volume.kubernetes.io/finalizer]
# In-tree 卷
Finalizers: [kubernetes.io/pv-protection, kubernetes.io/pv-controller]
这些 finalizer 确保 PV 对象只有在后端存储真正删除后才会从 Kubernetes 中移除。
Recycle(回收,已弃用)
persistentVolumeReclaimPolicy: Recycle
注意:Recycle 策略已被弃用。推荐使用动态供应代替。
PersistentVolume (PV) 详解
完整的 PV 定义
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
# 容量
capacity:
storage: 10Gi
# 卷模式
volumeMode: Filesystem
# 访问模式
accessModes:
- ReadWriteOnce
# 回收策略
persistentVolumeReclaimPolicy: Retain
# 存储类名
storageClassName: standard
# 挂载选项
mountOptions:
- hard
- nfsvers=4.1
# 节点亲和性
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-west-2a
# 存储后端配置
nfs:
server: 192.168.1.100
path: /exports/data
VolumeMode(卷模式)
Kubernetes 支持两种卷模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
Filesystem(默认) | 作为文件系统挂载到 Pod | 大多数应用场景 |
Block | 作为原始块设备挂载 | 数据库、需要直接访问块设备的应用 |
Filesystem 模式:
spec:
volumeMode: Filesystem # 默认值,可省略
卷会被挂载到 Pod 的目录中。如果后端是块设备且为空,Kubernetes 会在首次挂载前创建文件系统。
Block 模式:
apiVersion: v1
kind: PersistentVolume
metadata:
name: block-pv
spec:
capacity:
storage: 10Gi
volumeMode: Block # 块设备模式
accessModes:
- ReadWriteOnce
storageClassName: block-storage
local:
path: /dev/sdb
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
在 Pod 中使用块设备:
apiVersion: v1
kind: Pod
metadata:
name: block-pod
spec:
containers:
- name: app
image: my-app
volumeDevices: # 注意:使用 volumeDevices 而非 volumeMounts
- name: data
devicePath: /dev/xvda # 设备路径
volumes:
- name: data
persistentVolumeClaim:
claimName: block-pvc
AccessModes(访问模式)
| 模式 | 缩写 | 说明 |
|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 |
| ReadOnlyMany | ROX | 多节点只读 |
| ReadWriteMany | RWX | 多节点读写 |
| ReadWriteOncePod | RWOP | 单 Pod 读写(1.29+ 稳定) |
各存储类型支持的访问模式:
| 存储类型 | RWO | ROX | RWX | RWOP |
|---|---|---|---|---|
| NFS | ✓ | ✓ | ✓ | - |
| CSI(取决于驱动) | 取决于驱动 | 取决于驱动 | 取决于驱动 | 取决于驱动 |
| iSCSI | ✓ | ✓ | - | - |
| FC (Fibre Channel) | ✓ | ✓ | - | - |
| Local | ✓ | - | - | - |
| HostPath | ✓ | - | - | - |
ReadWriteOncePod 说明:
这是 Kubernetes 1.29 引入的稳定特性,确保整个集群中只有一个 Pod 可以读写该卷:
accessModes:
- ReadWriteOncePod
适用场景:单实例数据库、需要独占访问的应用。
Node Affinity(节点亲和性)
对于 Local 类型的 PV,必须指定节点亲和性:
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
使用该 PV 的 Pod 只能调度到 node-1 节点。
PV 状态(Phase)
| 状态 | 说明 |
|---|---|
| Available | 可用,未被绑定 |
| Bound | 已绑定到 PVC |
| Released | PVC 已删除,但未回收 |
| Failed | 自动回收失败 |
查看 PV 状态:
kubectl get pv
kubectl describe pv <pv-name>
PersistentVolumeClaim (PVC) 详解
完整的 PVC 定义
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
# 访问模式
accessModes:
- ReadWriteOnce
# 卷模式
volumeMode: Filesystem
# 存储类
storageClassName: fast-storage
# 资源请求
resources:
requests:
storage: 5Gi
limits:
storage: 10Gi # 可选:最大存储限制
# 选择器(可选)
selector:
matchLabels:
environment: production
matchExpressions:
- key: disktype
operator: In
values:
- ssd
# 指定 PV 名称(可选)
volumeName: specific-pv
Selector(选择器)
使用标签选择器筛选 PV:
spec:
selector:
matchLabels:
type: fast
matchExpressions:
- key: environment
operator: In
values:
- production
选择器规则:
matchLabels和matchExpressions是 AND 关系- 所有条件都必须满足才能匹配
指定特定 PV
通过 volumeName 直接绑定到特定 PV:
spec:
storageClassName: "" # 必须为空
volumeName: my-specific-pv
PVC 状态
| 状态 | 说明 |
|---|---|
| Pending | 等待绑定 |
| Bound | 已绑定到 PV |
| Lost | 绑定的 PV 丢失 |
卷扩展(Volume Expansion)
Kubernetes 1.24+ 稳定支持 PVC 扩展,Kubernetes 1.34 将卷扩展失败恢复功能升级为稳定版。
启用卷扩展
StorageClass 必须设置 allowVolumeExpansion: true:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: expandable-storage
provisioner: kubernetes.io/gce-pd
allowVolumeExpansion: true # 关键配置
parameters:
type: pd-ssd
扩展 PVC
直接编辑 PVC 的存储请求:
# 方式一:kubectl patch
kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
# 方式二:kubectl edit
kubectl edit pvc my-pvc
# 方式三:修改 YAML 文件后重新应用
kubectl apply -f pvc.yaml
扩展过程
扩展使用中的 PVC
在线扩展:如果存储后端支持,正在使用的 PVC 也可以扩展:
- 文件系统扩展在 Pod 启动或运行时进行
- 扩展完成后,Pod 自动看到新的存储大小
CSI 卷扩展:需要 CSI 驱动支持 EXPAND_VOLUME 能力。大多数主流 CSI 驱动都支持此功能。
扩展失败恢复(Kubernetes 1.34 GA)
从 Kubernetes 1.34 开始,卷扩展失败恢复功能正式稳定。当扩展请求失败(如超出底层存储容量限制)时,你可以:
- 取消正在进行的扩展:将 PVC 的存储请求改回原始大小
- 使用较小的值重试:使用一个可能成功的较小值重新尝试
恢复步骤:
# 1. 检查 PVC 状态
kubectl describe pvc my-pvc
# 输出示例:
# Status: ResizeInProgress
# Conditions:
# Type Status Reason
# FileSystemResizePending True WaitingForConsumer
# 2. 如果扩展失败,查看事件
kubectl get events --field-selector involvedObject.name=my-pvc
# 3. 恢复到原始大小或使用较小的值
kubectl patch pvc my-pvc -p '{"spec":{"resources":{"requests":{"storage":"15Gi"}}}}'
# 4. 验证恢复状态
kubectl get pvc my-pvc
重要限制:
- 新值必须大于
.status.capacity,Kubernetes 不支持缩小 PVC - 某些存储后端可能有最小扩展增量限制
- 如果文件系统已经扩展,无法恢复到扩展前的大小
VolumeAttributesClass(Kubernetes 1.34 GA)
Kubernetes 1.34 引入了 VolumeAttributesClass 的稳定版,允许在线修改卷的参数(如 IO 配置),支持工作负载动态调整存储性能。
apiVersion: storage.k8s.io/v1
kind: VolumeAttributesClass
metadata:
name: high-performance
driverName: ebs.csi.aws.com
parameters:
iops: "10000"
throughput: "500"
---
apiVersion: storage.k8s.io/v1
kind: VolumeAttributesClass
metadata:
name: standard-performance
driverName: ebs.csi.aws.com
parameters:
iops: "3000"
throughput: "125"
使用 VolumeAttributesClass:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: gp3
volumeAttributesClassName: high-performance # 指定性能级别
resources:
requests:
storage: 100Gi
动态修改性能配置:
# 修改 PVC 的性能级别
kubectl patch pvc my-pvc -p '{"spec":{"volumeAttributesClassName":"standard-performance"}}'
支持的场景:
- 根据负载动态调整存储 IO 性能
- 在非高峰期降低存储成本
- 为特定任务临时提升存储性能
注意:需要 CSI 驱动支持 ModifyVolume API。
StorageClass 详解
StorageClass 提供了一种描述存储"类"的方法,支持动态供应。
StorageClass 结构
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com # 供应器
reclaimPolicy: Delete # 回收策略
allowVolumeExpansion: true # 允许扩展
volumeBindingMode: WaitForFirstConsumer # 绑定模式
allowVolumeExpansion: true
mountOptions:
- discard
parameters:
type: io1
iopsPerGB: "50"
encrypted: "true"
allowedTopologies:
- matchLabelExpressions:
- key: topology.ebs.csi.aws.com/zone
values:
- us-east-2a
- us-east-2b
默认 StorageClass
集群可以有一个默认 StorageClass:
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
当 PVC 不指定 storageClassName 时,使用默认 StorageClass。
重要:应该只有一个默认 StorageClass。如果有多个,Kubernetes 使用最新创建的那个。
Volume Binding Mode(卷绑定模式)
| 模式 | 说明 | 适用场景 |
|---|---|---|
Immediate(默认) | 立即绑定和供应 | 全局可访问的存储 |
WaitForFirstConsumer | 延迟到 Pod 调度时 | 拓扑约束的存储 |
WaitForFirstConsumer 模式:
对于拓扑约束的存储(如 Local、云存储特定区域),应使用此模式:
volumeBindingMode: WaitForFirstConsumer
工作原理:
- PVC 创建后处于 Pending 状态
- 当 Pod 使用该 PVC 时,调度器决定 Pod 运行的节点
- 根据调度约束选择或创建合适的 PV
支持的存储类型:
- CSI 卷(驱动支持)
- Local 卷
注意事项:使用 WaitForFirstConsumer 时,不要在 Pod 中使用 nodeName,应使用 nodeSelector:
spec:
nodeSelector:
kubernetes.io/hostname: node-1
Allowed Topologies(允许的拓扑)
限制动态供应的存储拓扑:
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- us-central-1a
- us-central-1b
常用 Provisioner 配置
AWS EBS CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
csi.storage.k8s.io/fstype: xfs
type: io1
iopsPerGB: "50"
encrypted: "true"
AWS EFS CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-sc
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: fs-92107410
directoryPerms: "700"
GCE PD
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gce-sc
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
volumeBindingMode: WaitForFirstConsumer
Azure Disk CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: azure-disk-sc
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS
location: eastus
volumeBindingMode: WaitForFirstConsumer
NFS(外部供应器)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-sc
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.example.com
path: /share
readOnly: "false"
Local 存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
Local 存储不支持动态供应,但需要 StorageClass 来延迟绑定。
CSI(Container Storage Interface)
CSI 是 Kubernetes 存储的现代标准,替代了传统的 in-tree 存储插件。
CSI 的优势
| 特性 | In-tree 插件 | CSI 驱动 |
|---|---|---|
| 维护方式 | Kubernetes 代码库 | 独立项目 |
| 更新周期 | 跟随 Kubernetes 版本 | 独立发布 |
| 扩展性 | 受限于 Kubernetes | 高度可扩展 |
| 社区支持 | 逐渐弃用 | 官方推荐 |
CSI 驱动安装
大多数云平台和存储厂商提供 CSI 驱动。安装后,可以创建使用 CSI 的 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-storage
provisioner: csi.example.com
parameters:
# CSI 驱动特定参数
常见 CSI 驱动
| 存储系统 | CSI 驱动 |
|---|---|
| AWS EBS | ebs.csi.aws.com |
| AWS EFS | efs.csi.aws.com |
| GCE PD | pd.csi.storage.gke.io |
| Azure Disk | disk.csi.azure.com |
| Azure File | file.csi.azure.com |
| Ceph RBD | rbd.csi.ceph.com |
| Ceph FS | cephfs.csi.ceph.com |
| vSphere | csi.vsphere.vmware.com |
| NFS | nfs.csi.k8s.io |
临时存储
emptyDir
emptyDir 是最简单的临时存储,与 Pod 生命周期绑定:
apiVersion: v1
kind: Pod
metadata:
name: emptydir-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir:
sizeLimit: 100Mi # 可选大小限制
特点:
- Pod 创建时创建,Pod 删除时数据丢失
- 同一 Pod 内容器共享
- 默认存储在节点磁盘,也可配置为内存
内存支持的 emptyDir:
volumes:
- name: tmp
emptyDir:
medium: Memory # 使用内存
sizeLimit: 256Mi
临时卷(Ephemeral Volumes)
临时卷由 CSI 驱动提供,但生命周期与 Pod 绑定:
apiVersion: v1
kind: Pod
metadata:
name: ephemeral-pod
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
csi:
driver: ephemeral.csi.example.com
volumeAttributes:
sizeLimit: "1Gi"
存储最佳实践
1. 选择合适的存储类型
| 场景 | 推荐存储 |
|---|---|
| 生产数据库 | 云 SSD 存储、高性能 CSI |
| 开发测试 | 标准云存储、NFS |
| 缓存数据 | emptyDir(内存) |
| 大文件存储 | NFS、对象存储 |
| 多节点共享 | NFS、CephFS |
2. 使用动态供应
优先使用 StorageClass 动态创建 PV,减少管理负担:
# 只需创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
storageClassName: fast-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
3. 合理配置资源请求
resources:
requests:
storage: 10Gi # 请求
limits:
storage: 20Gi # 上限
4. 使用 ReadWriteOncePod 保护关键数据
accessModes:
- ReadWriteOncePod # 确保独占访问
5. 配置存储类默认值
为命名空间设置 LimitRange,避免忘记配置资源:
apiVersion: v1
kind: LimitRange
metadata:
name: storage-limits
spec:
limits:
- type: PersistentVolumeClaim
max:
storage: 100Gi
min:
storage: 1Gi
存储调试
常用命令
# 查看 PV
kubectl get pv
kubectl describe pv <pv-name>
# 查看 PVC
kubectl get pvc
kubectl describe pvc <pvc-name>
# 查看 StorageClass
kubectl get storageclass
kubectl describe storageclass <sc-name>
# 查看使用 PVC 的 Pod
kubectl get pods -o json | jq '.items[] | select(.spec.volumes[].persistentVolumeClaim.claimName == "my-pvc")'
# 查看存储使用情况
kubectl df-pv # 需要 kubectl 插件
PVC 一直 Pending
排查步骤:
# 1. 查看 PVC 事件
kubectl describe pvc <pvc-name>
# 2. 检查是否有匹配的 PV
kubectl get pv
# 3. 检查 StorageClass 是否存在
kubectl get storageclass
# 4. 检查 Provisioner 日志
kubectl logs -n kube-system <provisioner-pod>
卷无法挂载
# 检查 kubelet 日志
journalctl -u kubelet | grep -i mount
# 检查节点上的挂载
mount | grep <volume-id>
# 检查 CSI 驱动状态
kubectl get pods -n kube-system -l app=<csi-driver>
卷扩展失败
# 查看扩展状态
kubectl describe pvc <pvc-name>
# 查看事件
kubectl get events --field-selector involvedObject.name=<pvc-name>
# 检查底层存储容量
# 根据存储类型使用相应的工具
小结
本章我们学习了 Kubernetes 存储的核心概念:
- PV 和 PVC 生命周期:Provisioning → Binding → Using → Reclaiming
- 存储对象使用保护:防止意外删除正在使用的存储
- 卷扩展:动态扩展 PVC 大小
- 访问模式:RWO、ROX、RWX、RWOP
- VolumeMode:Filesystem 和 Block 两种模式
- StorageClass:动态供应和存储类管理
- CSI 驱动:现代 Kubernetes 存储标准
- Volume Binding Mode:Immediate 和 WaitForFirstConsumer
- 临时存储:emptyDir 和 Ephemeral Volumes
练习
- 创建一个使用动态供应的 PVC
- 配置允许扩展的 StorageClass 并扩展 PVC
- 创建一个使用 Block 模式的 PV 和 PVC
- 配置 WaitForFirstConsumer 模式的 StorageClass
- 测试存储对象使用保护功能