跳到主要内容

存储管理

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

动态供应需要:

  1. PVC 请求特定的 StorageClass
  2. 管理员已创建并配置该 StorageClass
  3. 集群启用了 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

回收流程

  1. PVC 删除后,PV 变为 Released 状态
  2. PV 保留数据,但不能被新 PVC 绑定
  3. 管理员手动处理:
    • 清理存储上的数据
    • 删除 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(访问模式)

模式缩写说明
ReadWriteOnceRWO单节点读写
ReadOnlyManyROX多节点只读
ReadWriteManyRWX多节点读写
ReadWriteOncePodRWOP单 Pod 读写(1.29+ 稳定)

各存储类型支持的访问模式

存储类型RWOROXRWXRWOP
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
ReleasedPVC 已删除,但未回收
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

选择器规则:

  • matchLabelsmatchExpressions 是 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 也可以扩展:

  1. 文件系统扩展在 Pod 启动或运行时进行
  2. 扩展完成后,Pod 自动看到新的存储大小

CSI 卷扩展:需要 CSI 驱动支持 EXPAND_VOLUME 能力。大多数主流 CSI 驱动都支持此功能。

扩展失败恢复(Kubernetes 1.34 GA)

从 Kubernetes 1.34 开始,卷扩展失败恢复功能正式稳定。当扩展请求失败(如超出底层存储容量限制)时,你可以:

  1. 取消正在进行的扩展:将 PVC 的存储请求改回原始大小
  2. 使用较小的值重试:使用一个可能成功的较小值重新尝试

恢复步骤

# 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

工作原理:

  1. PVC 创建后处于 Pending 状态
  2. 当 Pod 使用该 PVC 时,调度器决定 Pod 运行的节点
  3. 根据调度约束选择或创建合适的 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 EBSebs.csi.aws.com
AWS EFSefs.csi.aws.com
GCE PDpd.csi.storage.gke.io
Azure Diskdisk.csi.azure.com
Azure Filefile.csi.azure.com
Ceph RBDrbd.csi.ceph.com
Ceph FScephfs.csi.ceph.com
vSpherecsi.vsphere.vmware.com
NFSnfs.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

练习

  1. 创建一个使用动态供应的 PVC
  2. 配置允许扩展的 StorageClass 并扩展 PVC
  3. 创建一个使用 Block 模式的 PV 和 PVC
  4. 配置 WaitForFirstConsumer 模式的 StorageClass
  5. 测试存储对象使用保护功能

参考资源