动态存储与 StorageClass 详解
动态存储与 StorageClass 详解
动态存储概述
动态存储自动化了存储卷的创建、配置和销毁过程,无需管理员手动管理 PV。
静态 vs 动态存储
┌─────────────────────────────────────────────────────────┐
│ 静态存储供给 (Static Provisioning) │
└─────────────────────────────────────────────────────────┘
管理员 用户 Kubernetes
│ │ │
│ 1. 创建 PV │ │
├────────────────────────────────────────────────>│
│ │ │
│ │ 2. 创建 PVC │
│ ├─────────────────────────>│
│ │ │
│ │ 3. 绑定 PVC 和 PV │
│ │<─────────────────────────┤
│ │ │
│ │ 4. Pod 使用 PVC │
│ ├─────────────────────────>│
│ │ │
缺点:
- 需要手动创建大量 PV
- 难以预估所需容量
- 容量可能浪费或不足
- 管理开销大
┌─────────────────────────────────────────────────────────┐
│ 动态存储供给 (Dynamic Provisioning) │
└─────────────────────────────────────────────────────────┘
管理员 用户 Kubernetes Provisioner
│ │ │ │
│ 1. 创建 StorageClass│ │ │
├────────────────────────────────────────────────>│ │
│ │ │ │
│ │ 2. 创建 PVC │ │
│ │ (指定 StorageClass) │ │
│ ├─────────────────────────>│ │
│ │ │ │
│ │ │ 3. 调用 Provisioner│
│ │ ├───────────────────>│
│ │ │ │
│ │ │ 4. 创建卷 │
│ │ │<───────────────────┤
│ │ │ │
│ │ │ 5. 创建 PV 并绑定 │
│ │<─────────────────────────┤ │
│ │ │ │
│ │ 6. Pod 使用 PVC │ │
│ ├─────────────────────────>│ │
优点:
✅ 按需自动创建存储
✅ 无需预先分配
✅ 减少管理开销
✅ 提高资源利用率
StorageClass 详解
StorageClass 是动态存储的核心,定义了如何动态创建 PV。
完整配置
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 默认 StorageClass
labels:
tier: gold
performance: high
# Provisioner - 存储提供者
provisioner: ebs.csi.aws.com
# Parameters - 传递给 Provisioner 的参数
parameters:
type: gp3 # AWS EBS 卷类型
iops: "3000" # IOPS
throughput: "125" # 吞吐量 (MiB/s)
encrypted: "true" # 加密
kmsKeyId: "arn:aws:kms:us-east-1:xxx:key/xxx"
fsType: ext4 # 文件系统类型
# VolumeBindingMode - 卷绑定模式
volumeBindingMode: WaitForFirstConsumer # Immediate / WaitForFirstConsumer
# ReclaimPolicy - 回收策略
reclaimPolicy: Delete # Delete / Retain
# AllowVolumeExpansion - 是否允许扩容
allowVolumeExpansion: true
# MountOptions - 挂载选项
mountOptions:
- discard # 支持 TRIM
- noatime # 不更新访问时间
# AllowedTopologies - 允许的拓扑
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- us-east-1a
- us-east-1b
# VolumeLifecycleModes - 卷生命周期模式(CSI)
# - Persistent: 持久卷
# - Ephemeral: 临时卷
Provisioner 类型
内置 Provisioner(In-Tree,逐步废弃):
# AWS EBS
provisioner: kubernetes.io/aws-ebs
# GCE PD
provisioner: kubernetes.io/gce-pd
# Azure Disk
provisioner: kubernetes.io/azure-disk
# Azure File
provisioner: kubernetes.io/azure-file
CSI Provisioner(推荐):
# AWS EBS CSI
provisioner: ebs.csi.aws.com
# GCE PD CSI
provisioner: pd.csi.storage.gke.io
# Azure Disk CSI
provisioner: disk.csi.azure.com
# Ceph RBD CSI
provisioner: rbd.csi.ceph.com
# NFS CSI
provisioner: nfs.csi.k8s.io
VolumeBindingMode
控制何时创建和绑定 PV。
Immediate(立即绑定):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: immediate-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: Immediate # 默认值
parameters:
type: gp3
# 行为:
# 1. 用户创建 PVC
# 2. 立即调用 Provisioner 创建卷
# 3. 立即创建 PV 并绑定
# 4. Pod 创建时,卷已经存在
# 问题:
# - 卷可能创建在错误的可用区
# - Pod 无法调度到能访问该卷的节点
# - 需要重新创建 PVC
WaitForFirstConsumer(延迟绑定,推荐):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: wait-for-consumer-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
type: gp3
# 行为:
# 1. 用户创建 PVC(状态:Pending)
# 2. 用户创建使用该 PVC 的 Pod
# 3. 调度器确定 Pod 运行的节点
# 4. 在该节点所在的拓扑位置创建卷
# 5. 创建 PV 并绑定
# 6. Pod 启动成功
# 优点:
# ✅ 确保卷在正确的位置创建
# ✅ 避免跨可用区访问
# ✅ 提高调度成功率
# ✅ 降低网络延迟和成本
示例对比:
# 场景:三个可用区的集群
# us-east-1a, us-east-1b, us-east-1c
# 使用 Immediate 模式
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: immediate-ebs
provisioner: ebs.csi.aws.com
volumeBindingMode: Immediate
# 问题:
# 1. PVC 创建,卷在 us-east-1a 创建
# 2. Pod 调度到 us-east-1c 的节点
# 3. 结果:Pod 无法启动(EBS 卷不能跨 AZ)
# 使用 WaitForFirstConsumer 模式
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: wait-ebs
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
# 解决:
# 1. PVC 创建,状态 Pending
# 2. Pod 创建,调度器选择 us-east-1c 的节点
# 3. 在 us-east-1c 创建 EBS 卷
# 4. 绑定成功,Pod 启动
Parameters
传递给 Provisioner 的配置参数,不同存储后端参数不同。
AWS EBS Parameters:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: aws-ebs-gp3
provisioner: ebs.csi.aws.com
parameters:
# 卷类型
type: gp3 # gp2, gp3, io1, io2, st1, sc1
# GP3 特定参数
iops: "3000" # IOPS(100-16000)
throughput: "125" # 吞吐量 MiB/s(125-1000)
# IO1/IO2 特定参数
# iopsPerGB: "50" # 每 GB 的 IOPS
# 加密
encrypted: "true"
kmsKeyId: "arn:aws:kms:us-east-1:123456789:key/xxx"
# 文件系统
fsType: ext4 # ext4, xfs
# 标签
tagSpecification_1: "Name=my-volume"
tagSpecification_2: "Environment=production"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
GCE PD Parameters:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gce-pd-ssd
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd # pd-standard, pd-ssd, pd-balanced
replication-type: regional-pd # none, regional-pd
fstype: ext4
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Azure Disk Parameters:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: azure-disk-premium
provisioner: disk.csi.azure.com
parameters:
skuName: Premium_LRS # Standard_LRS, Premium_LRS, StandardSSD_LRS, UltraSSD_LRS
kind: Managed # Managed, Shared
fsType: ext4
cachingMode: ReadOnly # None, ReadOnly, ReadWrite
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
Ceph RBD Parameters:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd
provisioner: rbd.csi.ceph.com
parameters:
clusterID: ceph-cluster-id
pool: kubernetes
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
csi.storage.k8s.io/provisioner-secret-namespace: default
csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret
csi.storage.k8s.io/controller-expand-secret-namespace: default
csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
csi.storage.k8s.io/node-stage-secret-namespace: default
csi.storage.k8s.io/fstype: ext4
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
NFS Parameters:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.example.com
share: /exported/path
mountPermissions: "0755"
# NFS 特定选项
mountOptions:
- hard
- nfsvers=4.1
- timeo=600
- retrans=2
volumeBindingMode: Immediate
reclaimPolicy: Delete
AllowVolumeExpansion
控制是否允许扩容 PVC。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: expandable-sc
provisioner: ebs.csi.aws.com
allowVolumeExpansion: true # 允许扩容
parameters:
type: gp3
---
# 使用该 StorageClass 的 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: expandable-pvc
spec:
storageClassName: expandable-sc
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# 扩容操作
# kubectl edit pvc expandable-pvc
# 将 storage: 10Gi 改为 storage: 20Gi
# 扩容流程:
# 1. 编辑 PVC,增加容量
# 2. External Resizer 检测到变化
# 3. 调用 CSI ControllerExpandVolume(扩容存储后端)
# 4. Kubelet 调用 CSI NodeExpandVolume(扩容文件系统)
# 5. PVC.status.capacity 更新为新容量
支持在线扩容的存储:
- AWS EBS: ✅
- GCE PD: ✅
- Azure Disk: ✅
- Ceph RBD: ✅
- NFS: ❌(网络存储通常不需要)
AllowedTopologies
限制卷可以创建的拓扑位置。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: topology-aware-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- us-east-1a
- us-east-1b
- key: topology.kubernetes.io/region
values:
- us-east-1
# 效果:
# - 卷只能在 us-east-1a 或 us-east-1b 创建
# - Pod 也只能调度到这两个可用区
拓扑标签:
# 查看节点拓扑标签
kubectl get nodes --show-labels
# 常见标签:
topology.kubernetes.io/region=us-east-1
topology.kubernetes.io/zone=us-east-1a
node.kubernetes.io/instance-type=m5.large
默认 StorageClass
设置默认 StorageClass,PVC 不指定 storageClassName 时使用。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: ebs.csi.aws.com
parameters:
type: gp3
---
# PVC 不指定 storageClassName
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: default-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
# storageClassName 留空,使用默认 StorageClass
管理默认 StorageClass:
# 查看默认 StorageClass
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
standard (default) ebs.csi.aws.com Delete WaitForFirstConsumer
# 设置默认
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# 取消默认
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
# 注意:集群中只能有一个默认 StorageClass
动态存储工作流程
完整流程
┌──────────────────────────────────────────────────────────────────┐
│ 1. 管理员创建 StorageClass │
│ kubectl apply -f storageclass.yaml │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 2. 用户创建 PVC │
│ spec: │
│ storageClassName: fast-ssd │
│ resources: │
│ requests: │
│ storage: 10Gi │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 3. PVC 状态变为 Pending │
│ (如果 volumeBindingMode 是 WaitForFirstConsumer) │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 4. 用户创建使用该 PVC 的 Pod │
│ volumes: │
│ - name: data │
│ persistentVolumeClaim: │
│ claimName: my-pvc │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 5. Scheduler 调度 Pod │
│ - 评估节点资源 │
│ - 评估节点拓扑(可用区) │
│ - 选择最优节点 │
│ - 设置 PVC annotation: volume.kubernetes.io/selected-node │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 6. PV Controller 检测到 PVC 需要绑定 │
│ - 检查 selected-node annotation │
│ - 获取节点拓扑信息 │
│ - 调用 External Provisioner │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 7. External Provisioner 工作 │
│ a. 读取 StorageClass parameters │
│ b. 调用 CSI CreateVolume RPC │
│ - volume_name │
│ - capacity_range │
│ - volume_capabilities │
│ - parameters │
│ - accessibility_requirements (拓扑信息) │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 8. CSI Driver Controller Service │
│ a. 连接存储后端 API │
│ b. 创建卷(例如:AWS EBS、Ceph RBD) │
│ c. 返回 volume_id 和 volume_context │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 9. External Provisioner 创建 PV 对象 │
│ apiVersion: v1 │
│ kind: PersistentVolume │
│ spec: │
│ capacity: │
│ storage: 10Gi │
│ csi: │
│ driver: ebs.csi.aws.com │
│ volumeHandle: vol-xxxxx # volume_id │
│ claimRef: │
│ name: my-pvc │
│ namespace: default │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 10. PV Controller 绑定 PVC 和 PV │
│ - PVC.spec.volumeName = PV.name │
│ - PVC.status.phase = Bound │
│ - PV.status.phase = Bound │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 11. Kubelet 挂载卷到 Pod │
│ a. AttachDetach Controller 挂载卷到节点(块存储) │
│ b. Kubelet 调用 CSI NodeStageVolume(格式化、挂载) │
│ c. Kubelet 调用 CSI NodePublishVolume(Bind mount 到 Pod) │
└────────────────┬─────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ 12. Pod 运行,卷可用 │
│ 容器可以读写 /data 目录 │
└──────────────────────────────────────────────────────────────────┘
使用示例
示例 1:AWS EBS 动态存储
# 1. 创建 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
fsType: ext4
---
# 2. 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: webapp-pvc
spec:
storageClassName: ebs-gp3
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
# 3. Deployment 使用 PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: webapp-pvc
示例 2:StatefulSet 使用动态存储
# 1. 创建 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: gp3
iops: "5000"
encrypted: "true"
---
# 2. StatefulSet 使用 volumeClaimTemplates
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
# VolumeClaimTemplates:为每个 Pod 自动创建 PVC
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: fast-ssd
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 50Gi
# 结果:
# - 自动创建 PVC: data-mysql-0, data-mysql-1, data-mysql-2
# - 每个 PVC 都会动态创建一个 PV
# - 每个 Pod 都有独立的持久化存储
示例 3:多层存储策略
# 高性能 SSD 存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: performance
labels:
tier: gold
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: io2
iops: "10000"
encrypted: "true"
reclaimPolicy: Retain # 重要数据,手动删除
---
# 标准 SSD 存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
labels:
tier: silver
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: gp3
iops: "3000"
encrypted: "true"
---
# 经济型 HDD 存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: economy
labels:
tier: bronze
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: sc1 # Cold HDD
encrypted: "false"
---
# 数据库使用高性能存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: database-pvc
spec:
storageClassName: performance
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
---
# 日志使用经济型存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: logs-pvc
spec:
storageClassName: economy
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
监控和管理
查看 StorageClass
# 列出所有 StorageClass
kubectl get storageclass
kubectl get sc # 简写
# 详细信息
kubectl describe storageclass fast-ssd
# YAML 格式
kubectl get sc fast-ssd -o yaml
# 查看默认 StorageClass
kubectl get sc | grep "(default)"
查看动态创建的 PV
# 列出所有 PV
kubectl get pv
# 查看特定 StorageClass 的 PV
kubectl get pv -l storage.kubernetes.io/storageclass=fast-ssd
# 查看 PV 详情
kubectl describe pv pvc-xxxxx
监控动态存储
# 查看 Provisioner Pod
kubectl get pods -n kube-system | grep csi
# 查看 External Provisioner 日志
kubectl logs -n kube-system csi-provisioner-xxxxx
# 查看 CSI Controller 日志
kubectl logs -n kube-system csi-controller-xxxxx
故障排查
PVC 一直 Pending
# 检查 PVC 状态
kubectl describe pvc my-pvc
# 常见原因 1:StorageClass 不存在
Events:
Warning ProvisioningFailed 1m persistentvolume-controller
storageclass.storage.k8s.io "fast-ssd" not found
# 解决:创建 StorageClass
kubectl apply -f storageclass.yaml
# 常见原因 2:Provisioner 未运行
Events:
Warning ProvisioningFailed 1m persistentvolume-controller
Failed to provision volume: timeout waiting for provisioner
# 解决:检查 CSI Driver
kubectl get csidrivers
kubectl get pods -n kube-system | grep csi
# 常见原因 3:资源配额超限
Events:
Warning ProvisioningFailed 1m persistentvolume-controller
exceeded quota: storage-quota
# 解决:增加配额或删除不用的 PVC
kubectl get resourcequota -n default
卷创建失败
# 检查 External Provisioner 日志
kubectl logs -n kube-system -l app=csi-provisioner
# 常见错误:权限不足
# AWS: 检查 IAM 角色权限
# GCP: 检查服务账号权限
# Azure: 检查 Service Principal 权限
# 常见错误:参数错误
# 检查 StorageClass parameters
kubectl get sc my-sc -o yaml
最佳实践
1. 使用延迟绑定
# ✅ 推荐
volumeBindingMode: WaitForFirstConsumer
# ❌ 不推荐(除非特殊需求)
volumeBindingMode: Immediate
2. 设置合理的回收策略
# 生产环境:重要数据使用 Retain
reclaimPolicy: Retain
# 开发/测试环境:使用 Delete
reclaimPolicy: Delete
3. 启用卷扩容
# 允许未来扩容
allowVolumeExpansion: true
4. 使用拓扑感知
# 限制卷创建位置,提高性能和可用性
allowedTopologies:
- matchLabelExpressions:
- key: topology.kubernetes.io/zone
values:
- us-east-1a
- us-east-1b
5. 设置默认 StorageClass
# 为集群设置一个合理的默认 StorageClass
annotations:
storageclass.kubernetes.io/is-default-class: "true"
6. 使用标签分类
# 为不同性能等级的 StorageClass 添加标签
labels:
tier: gold # or silver, bronze
performance: high # or medium, low
7. 监控存储成本
# 定期检查 PV 使用情况
kubectl get pv --sort-by=.spec.capacity.storage
# 删除未使用的 PVC
kubectl get pvc --all-namespaces | grep -v Bound
总结
动态存储通过 StorageClass 实现了存储的自动化管理:
- 自动创建:按需创建 PV,无需手动管理
- 拓扑感知:在正确的位置创建卷
- 灵活配置:通过 parameters 定制存储
- 扩展支持:支持在线扩容
- 多层策略:不同场景使用不同存储等级
掌握 StorageClass 是管理 Kubernetes 存储的关键。