引言

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-provisionerlocal-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-provisionernfs-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. 常见问题与注意事项
  1. PVC 一直 Pending?

    • 检查 Provisioner Pod 是否正常运行(kubectl logs -n dev <pod-name> 查看日志);
    • 确认 RBAC 权限是否配置完整(漏绑 ClusterRole 会导致无权限创建 PV);
    • 验证 NFS 服务器与 K8s 节点的网络连通性(防火墙是否开放 2049 端口)。
  2. 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. 常见问题与注意事项
  1. 指定 nodeName 后 Pod 无法调度?

    • 原因:volumeBindingMode: WaitForFirstConsumer 需等待 Pod 调度后生成 PV,但 nodeName 强制指定节点时,PVC 未绑定 PV 导致 Pod 阻塞;
    • 解决方案:改用 节点亲和性 替代 nodeName(如 nodeSelectoraffinity),允许 PVC 先绑定 PV。
  2. PVC 申请 ReadWriteMany 后一直 Pending?

    • 原因:Local 存储本质是节点本地目录,无法跨节点共享,仅支持 ReadWriteOnce
    • 解决方案:将 accessModes 改为 ReadWriteOnce,若需多节点共享,需切换至 NFS 等共享存储。
  3. Pod 迁移后无法访问数据?

    • 原因:Local PV 包含 nodeAffinity(绑定创建时的节点),Pod 迁移到其他节点后无法挂载;
    • 解决方案:核心数据场景避免使用 Local-Path,改用分布式存储(如 Ceph);非核心数据可删除旧 PVC 重新申请。

三、场景对比与选择建议

对比维度 NFS 共享存储 Local-Path 本地存储
适用场景 跨节点数据共享(如多副本 Web 应用) 单节点高性能存储(如数据库、缓存)
访问模式 支持 ReadWriteMany/ReadOnlyMany/ReadWriteOnce 仅支持 ReadWriteOnce
数据迁移 支持(跨节点挂载) 不支持(PV 绑定节点,节点故障后数据不可用)
IO 性能 依赖网络(性能中等,受带宽限制) 本地磁盘性能(高 IO,适合性能敏感场景)
运维复杂度 需维护 NFS 服务器(高可用、备份) 节点本地目录管理(复杂度低)

选择建议

  • 若业务需跨节点共享数据(如分布式应用日志、静态资源),选择 NFS 共享存储
  • 若业务对 IO 性能要求高且无需跨节点迁移(如单副本数据库、本地缓存),选择 Local-Path 本地存储
  • 核心业务数据(如生产数据库)建议使用分布式存储(如 Ceph、GlusterFS),兼顾共享性与高可用。

四、总结

StorageClass 通过声明式配置实现存储自动化管理,结合不同 Provisioner 可适配从云存储到本地盘的多样化场景。实际部署中需根据业务需求选择合适的存储类型,并注意回收策略、访问模式等关键参数的配置。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐