上一章节学习了K8S中PV/PVC,这种创建卷的方式给我们带来了不少好处,例如持久化,共享卷等,但是最大的问题就是它是静态的。什么意思?就是我们每次想使用一个卷的时候,先要创建pv,然后声明pvc,最后才能在pod里面去使用这个卷。少的情况下还好,万一生产环境里面有成1000个pod都需要用到卷存储,那岂不是要先创建1000个PV?删除pod后不是还要删除1000次PV?能不能省略掉创建和删除的步骤?
这种情况下我们就需要用到动态 PV,也就是我们今天要讲解的 StorageClass。
一、概念与作用
StorageClass(存储类) 是 Kubernetes 中用于动态创建和管理 PersistentVolume(PV) 的核心资源对象。它解决了传统静态存储分配的低效问题,通过定义存储策略(如存储类型、性能等级、回收规则等),实现按需自动化创建 PV,并与 PersistentVolumeClaim(PVC) 绑定。
核心作用:
动态供应存储:根据 PVC 请求自动创建匹配的 PV,无需管理员手动干预。
抽象存储细节:用户无需关心底层存储实现(如 AWS EBS、NFS、Ceph 等),只需声明存储需求。
支持多存储后端:通过不同的 provisioner 字段对接多种存储系统,如云存储、分布式存储等。
一个完整的创建过程(NFS作为后端)
前提所有的服务器安装NFS服务端,这个上一章节之前有操作过,这里不再复述。
1. NFS Provisioner 部署(含 RBAC 权限)
# rbac.yaml
# ServiceAccount 权限
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- 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"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
上面是一个RBAC的创建过程,后面会有具体章节进行讲解,这里只做简单的说明。总共分为3部分。
1.创建一个SA,名为nfs-provisioner
2.创建一个ClusterRole,名为nfs-provisioner-role,他有以下权限
对resources: ["endpoints"] 拥有["get", "list", "watch", "create", "update", "patch"]的权限
对resources: ["persistentvolumes"] 拥有["get", "list", "watch", "create", "delete"]的权限
对resources: ["persistentvolumeclaims"]用有["get", "list", "watch", "update"]的权限
对resources: ["storageclasses"]拥有["get", "list", "watch"]的权限
3.创建一个ClusterRoleBinding,名为nfs-provisioner-binding,它将nfs-provisioner这个SA绑定到nfs-provisioner-role的权限上。
2. NFS Provisioner 控制器
由于原生 NFS 不支持动态卷分配,需使用 nfs-subdir-external-provisioner
实现动态 PV 创建。
# nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-provisioner
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: k8s.m.daocloud.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 注意这里的值要和StorageClass里面的provisioner: nfs-provisioner保持一致
- name: NFS_SERVER
value: 172.21.176.4 # 替换为你的NFS服务器IP
- name: NFS_PATH
value: /data/nfs_share # 替换为NFS共享路径
volumes:
- name: nfs-client-root
nfs:
server: 172.21.176.4 # 同上
path: /data/nfs_share # 同上
3. StorageClass 定义
# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 注意这里的值要和上面的里面的name: PROVISIONER_NAME 的值保持一致
parameters:
archiveOnDelete: "false" # 删除PVC时是否保留数据目录
reclaimPolicy: Delete # 或 Retain
4. PVC 申请存储
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: nfs-storage # 必须与StorageClass名称匹配
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
5. Deployment 挂载 PVC
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-web-app
spec:
replicas: 2
selector:
matchLabels:
app: nfs-web
template:
metadata:
labels:
app: nfs-web
spec:
containers:
- name: web
image: m.daocloud.io/docker.io/nginx:alpine
volumeMounts:
- name: nfs-volume
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc
二、部署流程说明
1. 部署 NFS 服务器
确保 /data/nfs
目录已通过 rw,sync,no_subtree_check
参数导出
上一章节已经在k8s-node01的服务器上面设置好了
2.应用 RBAC 和 Provisioner
kubectl apply -f rbac.yaml
kubectl apply -f nfs-provisioner.yaml
# 通过 # kubectl get pod 查看pod,如果有报错可以通过describe或者log来查看报错内容
NAME READY STATUS RESTARTS AGE
nfs-provisioner-d7cc848df-xspjz 1/1 Running 0 15m
3.创建 StorageClass
kubectl apply -f storageclass.yaml
# 通过下面命令查看是否创建成功
# kubectl get storageclasses.storage.k8s.io
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 47m
4.申请 PVC
kubectl apply -f pvc.yaml
# 通过命令得到pvc,此时应该是Bound 的状态,pv也自动创建成功
# kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/nfs-pvc Bound pvc-fd38b271-06b1-497b-a2dc-dafe82d45d72 10Gi RWX nfs-storage <unset> 52m
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-fd38b271-06b1-497b-a2dc-dafe82d45d72 10Gi RWX Delete Bound default/nfs-pvc nfs-storage <unset> 16m
5.部署应用
kubectl apply -f deployment.yaml
# 通过命令查看新创建的pod
# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-provisioner-d7cc848df-xspjz 1/1 Running 0 17m
nfs-web-app-7b7cf4454-bhlrn 1/1 Running 0 41m
nfs-web-app-7b7cf4454-dktj8 1/1 Running 0 41m
三、验证数据持久化
1.进入任意 Pod 写入测试文件:
kubectl exec -it <pod-name> -- sh -c "echo 'Hello NFS' > /usr/share/nginx/html/test.txt"
# kubectl exec -it nfs-web-app-7b7cf4454-bhlrn -- sh -c "echo 'Hello NFS' > /usr/share/nginx/html/test.txt"
2.删除 Pod 后重新进入另外一个 Pod查看:
kubectl exec -it <new-pod-name> -- cat /usr/share/nginx/html/test.txt # 应看到文件内容
# kubectl exec -it nfs-web-app-7b7cf4454-dktj8 -- cat /usr/share/nginx/html/test.txt
Hello NFS
四、典型应用场景对比
场景 | 静态 PV | 动态 PV |
---|---|---|
开发测试环境 | 适合小型环境,存储需求稳定 | 不适用(可能资源浪费) |
生产环境 | 需配合人工监控和扩容 | 适合弹性伸缩、自动化运维 |
多存储类型混合架构 | 需为每种存储单独配置 PV | 通过 StorageClass 统一管理 |
云原生与微服务 | 兼容性差,扩展成本高 | 天然适配,支持跨云存储 |