Kubernetes StorageClass 存储类详解与实践指南
引言
StorageClass 是 Kubernetes 动态存储供给的核心组件,通过定义存储策略实现 PVC 到 PV 的自动绑定,显著降低运维复杂度。本文将解析其核心机制,并通过 NFS 和 Local-Path-Provisioner 两个典型场景演示部署实践。
一、核心概念解析
1. 动态 Provisioner 机制
- 内置 Provisioner:由 Kubernetes 官方维护,名称前缀统一为
kubernetes.io/,无需额外部署程序即可对接云厂商或主流分布式存储,如:kubernetes.io/aws-ebs(AWS 弹性块存储)kubernetes.io/gce-pd(GCP 持久磁盘)kubernetes.io/ceph-rbd(Ceph RBD 块存储)
- 外部 Provisioner:由第三方或社区实现,需通过 Deployment/DaemonSet 部署独立程序(如
nfs-subdir-external-provisioner、local-path-provisioner),支持 NFS、Local PV、GlusterFS 等非云原生存储。
2. 关键参数说明
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 标注为默认存储类,PVC 未指定时自动使用
provisioner: kubernetes.io/aws-ebs # 对接的 Provisioner(内置/外部)
parameters: # 存储介质专属配置,由 Provisioner 定义(不同存储参数不同)
type: gp2 # AWS EBS 存储类型(gp2=通用型 SSD)
encrypted: "true" # 开启数据加密(可选,部分 Provisioner 支持)
reclaimPolicy: Retain # 核心:PV 回收策略
allowVolumeExpansion: true # 支持 PVC 容量动态扩展(需 Provisioner 兼容)
volumeBindingMode: Immediate # 核心:PV 与 PVC 的绑定时机
mountOptions: # 挂载参数(如 NFS 需配置权限、缓存策略)
- hard
- nfsvers=4.1
3. 绑定模式(volumeBindingMode)
定义:控制 PV 与 PVC 的匹配时机,直接影响存储资源的“节点亲和性适配”。
3.1 两种核心绑定模式对比
| 绑定模式 | 定义与工作流程 | 适用场景 | 注意事项 |
|---|---|---|---|
| Immediate(默认) | 1. PVC 创建后立即触发 Provisioner 生成 PV; 2. PV 生成后立即与 PVC 绑定(不等待 Pod 调度)。 |
通用存储场景:无节点亲和性要求的存储(如 AWS EBS、Ceph RBD)、非节点绑定的共享存储(如 NFS)。 | 不支持 Local PV(Local PV 依赖 Pod 调度到特定节点,若先绑定 PV,Pod 可能调度到其他节点导致挂载失败)。 |
| WaitForFirstConsumer | 1. PVC 创建后,Provisioner 暂不生成 PV; 2. 等待第一个使用该 PVC 的 Pod 被调度到具体节点; 3. 根据 Pod 所在节点的亲和性(如节点标签、存储拓扑),生成匹配的 PV 并绑定。 |
节点专属存储场景: 1. Local PV(依赖节点本地磁盘); 2. 有拓扑约束的存储(如仅某类节点可访问的存储); 3. 需要根据 Pod 调度结果动态匹配存储的场景。 |
1. 需确保 Provisioner 支持拓扑感知(如 local-path-provisioner、nfs-subdir-external-provisioner v4.0+);2. PVC 若未被 Pod 使用,PV 会一直处于“待绑定”状态。 |
4. 回收策略(reclaimPolicy)
定义:定义 PV 生命周期管理规则,控制 PVC 删除后 PV 及其关联数据的处理方式。
4.1 四种核心回收策略(含扩展行为)
| 回收策略参数 | 核心行为 | 数据安全性 | 资源复用性 | 适用场景 |
|---|---|---|---|---|
reclaimPolicy: Delete |
1. PVC 删除后,Provisioner 自动删除 PV; 2. 同时删除 PV 关联的后端存储数据(如 EBS 磁盘、NFS 目录)。 |
低 | 高 | 临时数据场景:如测试环境、日志存储、无持久化需求的业务(避免手动清理资源) |
reclaimPolicy: Retain |
1. PVC 删除后,PV 状态变为 Released(释放),但不删除 PV 及后端数据; 2. 需手动执行两步操作: - 清理 PV 关联的后端数据(如删除 NFS 目录、格式化 EBS 磁盘) - 删除 PV 对象( kubectl delete pv <pv-name>) |
高 | 低 | 核心数据场景:如数据库存储、业务核心数据(避免误删导致数据丢失,需人工确认) |
archiveOnDelete: true |
(仅 NFS Provisioner 支持,需配合 reclaimPolicy: Delete 使用)1. PVC 删除后,不删除数据,而是将 NFS 目录重命名(如 archived-<pv-name>-<timestamp>)2. 自动删除 PV 对象 |
中 | 高 | NFS 存储场景:需保留历史数据但自动释放 PV 资源(如定期归档的业务日志、临时备份数据) |
reclaimPolicy: Recycle |
(已废弃,Kubernetes 1.16+ 移除) 原行为:PVC 删除后,自动清理 PV 数据(如执行 rm -rf /path/*),并将 PV 状态改为 Available 供复用 |
低 | 高 | 已不推荐使用,替代方案:Delete(临时数据)或 Retain(需复用资源时手动清理) |
二、典型存储场景实践
在 Kubernetes 集群中,不同业务对存储的需求差异显著:部分场景需要跨节点共享存储(如多副本应用的数据共享),部分场景则依赖节点本地存储(如对 IO 性能要求高的单节点应用)。以下基于 NFS 共享存储 和 Local-Path 本地存储 两大核心场景,结合实操步骤完成部署与验证,覆盖动态供给、权限配置、问题排查等关键环节。
场景一:NFS 共享存储实践(基于 NFS-Subdir-External-Provisioner)
1. 场景说明
NFS(Network File System)是经典的网络共享存储方案,适用于 跨节点数据共享场景(如多副本 Web 应用共享静态资源、分布式日志存储等)。通过 NFS-Subdir-External-Provisioner 可实现 NFS 存储的动态供给,自动创建 PV 并与 PVC 绑定,无需手动管理存储资源。
2. 前置准备:搭建 NFS 服务器
2.1 服务器端配置(以 CentOS 为例,节点 IP:10.211.55.11)
# 1. 安装 NFS 依赖包
yum install nfs-utils rpcbind -y
# 2. 创建 NFS 共享目录(根据实际需求调整路径)
mkdir -p /root/data/nfs
# 3. 配置 NFS 访问权限(允许集群网段 192.168.154.0/24 读写,不压缩 root 权限)
echo "/root/data/nfs 192.168.154.0/24(rw,no_root_squash,sync)" >> /etc/exports
# 4. 重载 NFS 配置并启动服务
exportfs -r # 使配置生效
systemctl enable --now nfs-server rpcbind # 开机自启
# 5. 验证 NFS 共享(服务器端自查)
exportfs # 输出应包含共享目录及允许的网段
2.2 客户端配置(所有 K8s 节点)
# 1. 安装 NFS 客户端依赖
yum install nfs-utils rpcbind -y
# 2. 验证 NFS 服务器连通性(能看到共享目录即为正常)
showmount -e 10.211.55.11
# 预期输出:Export list for 10.211.55.11: /root/data/nfs 192.168.154.0/24
3. 部署步骤:NFS-Subdir-External-Provisioner
3.1 步骤 1:创建命名空间与 RBAC 权限(必须配置)
K8s 基于 RBAC 控制权限,需为 Provisioner 创建专属 ServiceAccount 并绑定权限,否则无法动态生成 PV。
创建 nfs-rbac.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: dev # 统一使用 dev 命名空间管理 NFS 相关资源
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: dev
---
# 集群级权限:管理 PV、PVC、StorageClass 等资源
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: dev
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
# 命名空间级权限:管理 endpoints(用于 Leader 选举,避免多 Provisioner 冲突)
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: dev
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: dev
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: dev
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
应用配置:
kubectl apply -f nfs-rbac.yaml
3.2 步骤 2:部署 Provisioner 核心组件
创建 nfs-provisioner-deploy.yaml,配置 NFS 服务器地址、共享目录及 Provisioner 名称(需与后续 StorageClass 一致):
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
namespace: dev
spec:
replicas: 1 # Provisioner 单副本即可,避免资源竞争
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate # 升级策略:删除旧 Pod 再创建新 Pod(避免多 Provisioner 冲突)
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 绑定步骤 1 创建的 ServiceAccount
containers:
- name: nfs-client-provisioner
# 使用国内镜像(避免国外镜像拉取超时)
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
# 将 NFS 共享目录挂载到 Provisioner 容器内
- name: nfs-client-root
mountPath: /persistentvolumes
env:
# Provisioner 名称:后续 StorageClass 需引用此名称
- name: PROVISIONER_NAME
value: storage-nfs
# NFS 服务器 IP(需与前置准备的服务器 IP 一致)
- name: NFS_SERVER
value: 10.211.55.11
# NFS 共享目录(需与前置准备的目录一致)
- name: NFS_PATH
value: /root/data/nfs
# 启用 Leader 选举:确保多 Provisioner 场景下仅 1 个生效
- name: ENABLE_LEADER_ELECTION
value: "true"
volumes:
# 定义 NFS 卷:关联服务器与共享目录
- name: nfs-client-root
nfs:
server: 10.211.55.11
path: /root/data/nfs
应用配置并验证:
kubectl apply -f nfs-provisioner-deploy.yaml
# 验证 Provisioner Pod 状态(需为 Running)
kubectl get pods -n dev -l app=nfs-client-provisioner
3.3 步骤 3:创建 NFS 专属 StorageClass
创建 nfs-storageclass.yaml,定义存储策略(如数据回收、挂载参数):
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage # StorageClass 名称:PVC 需引用此名称
namespace: dev
annotations:
# 不设为默认 StorageClass(避免其他应用误使用)
storageclass.kubernetes.io/is-default-class: "false"
provisioner: storage-nfs # 必须与 Provisioner 的 PROVISIONER_NAME 一致
parameters:
# PVC 删除后保留数据(重命名为 archived-xxx,避免误删)
archiveOnDelete: "true"
mountOptions:
- hard # 硬挂载:客户端与服务器断开时阻塞请求(保证数据一致性)
- nfsvers=4 # 指定 NFS 版本(需与服务器版本匹配,通过 nfsstat -v 查看)
reclaimPolicy: Delete # PVC 删除后自动删除 PV(配合 archiveOnDelete 保留数据)
volumeBindingMode: Immediate # 立即绑定:PVC 创建后立即生成 PV(NFS 为共享存储,无需等 Pod 调度)
应用配置并验证:
kubectl apply -f nfs-storageclass.yaml
# 验证 StorageClass 状态
kubectl get sc nfs-storage
4. 测试验证:动态生成 PV 与 PVC 绑定
创建 storage-pvc.yaml,通过 PVC 申请 NFS 存储:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-pvc
namespace: dev
spec:
storageClassName: nfs-storage # 引用 NFS 专属 StorageClass
accessModes:
- ReadWriteMany # NFS 支持多节点读写(适合共享场景)
resources:
requests:
storage: 1Mi # 申请 1Mi 存储(测试用,可根据需求调整)
应用配置并验证全流程:
kubectl apply -f storage-pvc.yaml
# 1. 验证 PVC 状态(需为 Bound:表示已与 PV 绑定)
kubectl get pvc -n dev storage-pvc
# 2. 验证 PV 状态(会自动生成 PV,名称格式为 pvc-xxx)
kubectl get pv -l storage.k8s.io/sc-name=nfs-storage
# 3. 验证 NFS 服务器目录(会生成 dev-storage-pvc-xxx 目录,存储 PVC 数据)
ls -l /root/data/nfs/ # 在 NFS 服务器执行
5. 常见问题与注意事项
-
PVC 一直 Pending?
- 检查 Provisioner Pod 是否正常运行(
kubectl logs -n dev <pod-name>查看日志); - 确认 RBAC 权限是否配置完整(漏绑 ClusterRole 会导致无权限创建 PV);
- 验证 NFS 服务器与 K8s 节点的网络连通性(防火墙是否开放 2049 端口)。
- 检查 Provisioner Pod 是否正常运行(
-
archiveOnDelete 不生效?
- 需确保 Provisioner 版本 ≥ v4.0.0(旧版本不支持此参数);
- PVC 删除后,NFS 目录会重命名为
archived-{namespace}-{pvcName}-{pvName},需手动清理历史归档。
场景二:Local-Path 本地存储实践(基于 Rancher 版 Provisioner)
1. 场景说明
Local-Path 适用于 单节点本地存储场景(如数据库、缓存应用等对 IO 性能要求高,且无需跨节点共享的业务)。通过 Rancher 版 local-path-provisioner 可实现 Local/HostPath 存储的动态供给,解决手动创建 Local PV 的繁琐问题(K8s SIGS 版不支持动态供给,推荐使用 Rancher 版)。
2. 前置准备:选择 Rancher 版 Provisioner
- 版本差异:K8s SIGS 版仅支持静态 PV,Rancher 版支持动态供给,且支持配置节点存储路径、自动清理等功能;
- 官方地址:Rancher 版 Provisioner 仓库:https://github.com/rancher/local-path-provisioner;
- 依赖要求:所有 K8s 节点需提前安装
busybox(用于执行目录创建/清理脚本)。
3. 部署步骤:Local-Path-Provisioner
3.1 步骤 1:下载并修改部署文件
# 下载 Rancher 版 Provisioner 部署文件(v0.0.24 为稳定版)
wget https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.24/deploy/local-path-storage.yaml
修改 local-path-storage.yaml 中的核心配置(重点调整 ConfigMap 和 StorageClass):
# 1. 调整 StorageClass 配置:支持扩容、设置回收策略
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
annotations:
# 默认创建 HostPath 类型卷(可选 local,需节点提前准备磁盘)
defaultVolumeType: hostPath
provisioner: rancher.io/local-path # Provisioner 名称(固定)
volumeBindingMode: WaitForFirstConsumer # 等待 Pod 调度后再生成 PV(Local 存储必须,避免节点不匹配)
reclaimPolicy: Retain # PVC 删除后保留 PV 和数据(避免误删核心数据)
allowVolumeExpansion: true # 支持 PVC 容量动态扩容(需后端目录支持)
---
# 2. 调整 ConfigMap:定义节点存储路径、目录脚本
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage # Provisioner 专属命名空间
data:
# 节点存储路径映射:指定不同节点的存储目录
config.json: |-
{
"nodePathMap":[
{
# 未指定的节点默认使用此路径
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/data/local-path-provisioner"]
},
{
# 指定 master 节点使用单独路径(根据实际节点名称调整)
"node":"master",
"paths":["/opt/local-path-provisioner"]
},
{
# 禁止某节点作为存储节点(paths 设为空数组)
"node":"node-01",
"paths":[]
}
]
}
# 目录创建脚本:PVC 绑定后在节点创建存储目录(权限 777,确保应用可读写)
setup: |-
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
# 目录清理脚本:PVC 删除后清理节点目录(仅 Retain 策略下需手动触发)
teardown: |-
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
# 辅助 Pod 模板:用于执行 setup/teardown 脚本(使用 busybox 轻量镜像)
helperPod.yaml: |-
apiVersion: v1
kind: Pod
metadata:
name: helper-pod
spec:
containers:
- name: helper-pod
image: busybox
3.2 步骤 2:部署 Provisioner 并验证
# 应用部署文件
kubectl apply -f local-path-storage.yaml
# 1. 验证 Provisioner Pod 状态(需为 Running,命名空间为 local-path-storage)
kubectl get pods -n local-path-storage
# 2. 验证 StorageClass 状态
kubectl get sc local-path
4. 测试验证:动态生成 Local PV 并挂载应用
创建 local-path-test.yaml,包含 PVC 和 Nginx Deployment(验证存储挂载):
# 1. PVC:申请 Local-Path 存储
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
namespace: default
spec:
storageClassName: local-path # 引用 Local-Path 专属 StorageClass
accessModes:
- ReadWriteOnce # Local 存储仅支持单节点读写(PV 绑定节点后无法迁移)
resources:
requests:
storage: 10Gi # 申请 10Gi 存储
# 2. Deployment:Nginx 应用挂载 PVC(存储静态页面)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-local-path
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx-local-path
template:
metadata:
labels:
app: nginx-local-path
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
volumeMounts:
# 将 PVC 挂载到 Nginx 静态页面目录
- name: nginx-data
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-data
persistentVolumeClaim:
claimName: local-path-pvc # 引用上述 PVC
应用配置并验证全流程:
kubectl apply -f local-path-test.yaml
# 1. 验证 PVC 状态(需为 Bound)
kubectl get pvc local-path-pvc
# 2. 验证 PV 状态(自动生成,名称格式为 pvc-xxx,且包含节点亲和性)
kubectl get pv -l storage.k8s.io/sc-name=local-path
# 查看 PV 详情:确认 nodeAffinity 绑定的节点(如 master)
kubectl describe pv <pv-name>
# 3. 验证应用挂载:在节点查看存储目录(以 master 节点为例)
ls -l /opt/local-path-provisioner/ # 会生成 pvc-xxx_default_local-path-pvc 目录
# 写入测试文件,验证挂载生效
kubectl exec -it <nginx-pod-name> -- echo "Local-Path Test" > /usr/share/nginx/html/index.html
curl <nginx-pod-ip> # 预期输出 "Local-Path Test"
5. 常见问题与注意事项
-
指定 nodeName 后 Pod 无法调度?
- 原因:
volumeBindingMode: WaitForFirstConsumer需等待 Pod 调度后生成 PV,但nodeName强制指定节点时,PVC 未绑定 PV 导致 Pod 阻塞; - 解决方案:改用 节点亲和性 替代
nodeName(如nodeSelector或affinity),允许 PVC 先绑定 PV。
- 原因:
-
PVC 申请 ReadWriteMany 后一直 Pending?
- 原因:Local 存储本质是节点本地目录,无法跨节点共享,仅支持
ReadWriteOnce; - 解决方案:将
accessModes改为ReadWriteOnce,若需多节点共享,需切换至 NFS 等共享存储。
- 原因:Local 存储本质是节点本地目录,无法跨节点共享,仅支持
-
Pod 迁移后无法访问数据?
- 原因:Local PV 包含
nodeAffinity(绑定创建时的节点),Pod 迁移到其他节点后无法挂载; - 解决方案:核心数据场景避免使用 Local-Path,改用分布式存储(如 Ceph);非核心数据可删除旧 PVC 重新申请。
- 原因:Local PV 包含
三、场景对比与选择建议
| 对比维度 | NFS 共享存储 | Local-Path 本地存储 |
|---|---|---|
| 适用场景 | 跨节点数据共享(如多副本 Web 应用) | 单节点高性能存储(如数据库、缓存) |
| 访问模式 | 支持 ReadWriteMany/ReadOnlyMany/ReadWriteOnce | 仅支持 ReadWriteOnce |
| 数据迁移 | 支持(跨节点挂载) | 不支持(PV 绑定节点,节点故障后数据不可用) |
| IO 性能 | 依赖网络(性能中等,受带宽限制) | 本地磁盘性能(高 IO,适合性能敏感场景) |
| 运维复杂度 | 需维护 NFS 服务器(高可用、备份) | 节点本地目录管理(复杂度低) |
选择建议:
- 若业务需跨节点共享数据(如分布式应用日志、静态资源),选择 NFS 共享存储;
- 若业务对 IO 性能要求高且无需跨节点迁移(如单副本数据库、本地缓存),选择 Local-Path 本地存储;
- 核心业务数据(如生产数据库)建议使用分布式存储(如 Ceph、GlusterFS),兼顾共享性与高可用。
四、总结
StorageClass 通过声明式配置实现存储自动化管理,结合不同 Provisioner 可适配从云存储到本地盘的多样化场景。实际部署中需根据业务需求选择合适的存储类型,并注意回收策略、访问模式等关键参数的配置。
更多推荐



所有评论(0)