k8s-动态卷供应
Kubernetes 动态卷供应
动态卷介绍
静态 PV/PVC(传统手动方式):必须管理员提前手动写 YAML 创建 PV,用户再创建 PVC 去匹配 PV;没有 PV 就会一直 Pending。
动态供给(StorageClass + PVC):不用手动创建 PV,只需要创建 PVC,集群自动生成 PV 并绑定,也就是动态分配 PV。
这就是动态卷供应的能力。它彻底省去了集群管理员提前手动配置存储的工作,用户只要提出存储需求,系统就会自动创建对应的存储卷。
动态卷供应流程
- 管理员先创建 “存储模板”(StorageClass),指定用哪个 “造卷工具”(Provisioner,制备器)来创建卷;
- 用户创建 “存储申请单”(PVC)时,指定要用哪个 “存储模板”;
- 系统收到申请后,会让模板绑定的 “造卷工具” 自动创建一个 PV,并且把 PV 和用户的 PVC 绑定,用户直接用 PVC 就行。
StorageClass
StorageClass 就像管理员定义的 “存储套餐”—— 不同套餐对应不同的存储类型、服务质量(比如读写速度)、备份策略等。Kubernetes 不关心套餐叫什么,只负责按套餐规则造卷。
- 命名很重要:用户创建 PVC 时,要通过这个名字选择对应的存储套餐;
- 创建后不能改:StorageClass 一旦创建,名字和参数就没法修改了,要改只能重新建。
核心配置
每个 “存储套餐” 都包含以下核心信息,用来指导系统创建 PV:
provisioner:造卷工具(制备器),指定用哪个工具创建 PV,必须设置。
Kubernetes 自带一些 Provisioner,名字以 “kubernetes.io” 开头(比如 AWS EBS、Azure Disk);也可以使用第三方提供的(比如 NFS 没有内置工人,需要自己装)Provisioner,按 Kubernetes 规则运行即可。
存储类型 是否内置 说明 AWSElasticBlockStore ✅ 是 亚马逊云硬盘 AzureFile/AzureDisk ✅ 是 微软云文件 / 硬盘 Ceph RBD ✅ 是 Ceph 块存储 Glusterfs ✅ 是 分布式文件系统 NFS ❌ 否 网络文件系统,需装外部工人 本地存储(Local) ❌ 否 节点本地硬盘,需装外部工人 iSCSI/CephFS ❌ 否 需外部工人 reclaimPolicy:PV 回收策略,指定 PV 不用怎么处理 PV。
- Delete(默认):PVC 删除后,PV 也自动删,对应的后端存储(比如云硬盘、NFS 目录)也会清掉;
- Retain:PVC 删除后,PV 保留下来,后端存储的数据也不删,管理员可以手动处理。
volumeBindingMode:PVC 创建出来后,指定 PVC 与P V 绑定时机。
- Immediate(立即绑定):创建 PVC 就造卷,不管 Pod 会不会用;适合存储能被所有节点访问的场景(比如云硬盘);
- WaitForFirstConsumer(等使用者):先不造卷,等第一个用这个 PVC 的 Pod 创建后,根据 Pod 要跑的节点造卷;适合本地存储、只能被特定节点访问的存储。
mountOptions:指定 PV 挂载时附加的 “额外设置”,比如权限、调试模式。
注意:如果存储不支持这些选项,造卷会失败;如果选项填错,PV 挂载会失败。
parameters:比如存储类型(SSD/HDD)、副本数等,不同工具参数不一样。
基础示例
apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:standard# 套餐名,用户PVC要填这个provisioner:kubernetes.io/aws-ebs# 用AWS EBS的造卷工具parameters:type:gp2# 存储类型为gp2(AWS的通用型SSD)reclaimPolicy:Retain# PVC 不用了,pv保留allowVolumeExpansion:true# 允许扩容mountOptions:-debug# 挂载时开启调试模式volumeBindingMode:Immediate# 立即创建并绑定PV部署 Local Path provisioner
Local Path Provisioner 是 K8s 官方推荐的本地存储动态卷插件,适合需要使用节点本地磁盘的场景。
部署本地路径制备器
# 下载官方模板root@master30 ~13:25:02# wget https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml#可以以直接在浏览器搜# 查看资源配置root@master30 ~13:25:35# vim local-path-storage.yamlapiVersion:v1kind:Namespacemetadata:name:local-path-storage---apiVersion:v1kind:ServiceAccountmetadata:name:local-path-provisioner-service-accountnamespace:local-path-storage---apiVersion:rbac.authorization.k8s.io/v1kind:Rolemetadata:name:local-path-provisioner-rolenamespace:local-path-storagerules:-apiGroups:[""]resources:["pods"]verbs:["get","list","watch","create","patch","update","delete"]---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRolemetadata:name:local-path-provisioner-rolerules:-apiGroups:[""]resources:["nodes","persistentvolumeclaims","configmaps","pods","pods/log"]verbs:["get","list","watch"]-apiGroups:[""]resources:["persistentvolumes"]verbs:["get","list","watch","create","patch","update","delete"]-apiGroups:[""]resources:["events"]verbs:["create","patch"]-apiGroups:["storage.k8s.io"]resources:["storageclasses"]verbs:["get","list","watch"]---apiVersion:rbac.authorization.k8s.io/v1kind:RoleBindingmetadata:name:local-path-provisioner-bindnamespace:local-path-storageroleRef:apiGroup:rbac.authorization.k8s.iokind:Rolename:local-path-provisioner-rolesubjects:-kind:ServiceAccountname:local-path-provisioner-service-accountnamespace:local-path-storage---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRoleBindingmetadata:name:local-path-provisioner-bindroleRef:apiGroup:rbac.authorization.k8s.iokind:ClusterRolename:local-path-provisioner-rolesubjects:-kind:ServiceAccountname:local-path-provisioner-service-accountnamespace:local-path-storage---apiVersion:apps/v1kind:Deploymentmetadata:name:local-path-provisionernamespace:local-path-storagespec:replicas:1selector:matchLabels:app:local-path-provisionertemplate:metadata:labels:app:local-path-provisionerspec:serviceAccountName:local-path-provisioner-service-accountcontainers:-name:local-path-provisionerimage:rancher/local-path-provisioner:v0.0.35imagePullPolicy:IfNotPresentcommand:-local-path-provisioner---debug-start---config-/etc/config/config.jsonvolumeMounts:-name:config-volumemountPath:/etc/config/env:-name:POD_NAMESPACEvalueFrom:fieldRef:fieldPath:metadata.namespace-name:CONFIG_MOUNT_PATHvalue:/etc/config/volumes:-name:config-volumeconfigMap:name:local-path-config---apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:local-pathprovisioner:rancher.io/local-pathvolumeBindingMode:WaitForFirstConsumerreclaimPolicy:Delete---kind:ConfigMapapiVersion:v1metadata:name:local-path-confignamespace:local-path-storagedata:config.json:|-{"nodePathMap":[{"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES","paths":["/opt/local-path-provisioner"]}]}setup:|-#!/bin/shset-eu mkdir-m 0777-p "$VOL_DIR"teardown:|-#!/bin/shset-eu rm-rf "$VOL_DIR"helperPod.yaml:|-apiVersion:v1kind:Podmetadata:name:helper-podspec:priorityClassName:system-node-criticaltolerations:-key:node.kubernetes.io/disk-pressureoperator:Existseffect:NoSchedulecontainers:-name:helper-podimage:busyboximagePullPolicy:IfNotPresentlocal-path-storage.yaml核心由5 类资源组成,每部分各司其职:
| 资源类型 | 名称 | 核心作用 |
|---|---|---|
| Namespace | local-path-storage | 隔离 Local Path Provisioner 相关资源 |
| ConfigMap | local-path-config | 定义本地存储路径规则(如默认/opt/local-path-provisioner) |
| ServiceAccount + RBAC | local-path-provisioner | 赋予 Provisioner 操作 PV/PVC 的权限 |
| Deployment | local-path-provisioner | 运行 Local Path Provisioner 核心程序(动态创建本地 PV) |
| StorageClass | local-path | 提供给 PVC 调用的「本地存储模板」 |
# 资源默认部署在命名空间:local-path-storageroot@master30 ~13:26:19# kubectl apply -f local-path-storage.yaml# 确认资源状态root@master30 ~13:27:16# kubectl get sc local-pathNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE local-path rancher.io/local-path Delete WaitForFirstConsumerfalse67m root@master30 ~13:27:02# kubectl get all -n local-path-storageNAME READY STATUS RESTARTS AGE pod/local-path-provisioner-85d9db4886-5t4sn1/1 Running014s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/local-path-provisioner1/11114s NAME DESIRED CURRENT READY AGE replicaset.apps/local-path-provisioner-85d9db488611114s验证部署
# 设置默认命名空间root@master30 ~13:34:45# kubectl config set-context --current --namespace default1. 创建 pvc
#向名为local‑path的存储类申请5Gi本地存储,访问模式为单节点读写,放在 default 命名空间,由 local‑path provisioner 自动生成绑定 PV,供 Pod 挂载持久化数据。root@master30 ~13:35:01# cat > local-path-pvc.yaml <<'EOF'apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-path-pvc namespace: default spec: accessModes:["ReadWriteOnce"]resources: requests: storage: 5Gi storageClassName: local-path# 指定 Local Path 的 StorageClassEOF root@master30 ~13:36:28# kubectl apply -f local-path-pvc.yamlroot@master30 ~13:37:49# kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE local-path-pvc Pending local-path<unset>14s# 等待pod关联才会创建本地目录2. 创建 deployment
root@master30 ~13:38:03# cat > local-path-deployment.yaml <<'EOF'apiVersion: apps/v1 kind: Deployment metadata: labels: app: webapp name: webapp namespace: default spec: replicas:2selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - image: nginx name: nginx volumeMounts: - name: webapp-data mountPath: /usr/share/nginx/html volumes: - name: webapp-data persistentVolumeClaim: claimName: local-path-pvc EOF root@master30 ~13:38:18# kubectl apply -f local-path-deployment.yaml# 查看 pod 状态root@master30 ~13:38:56# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES webapp-6d46b54487-92cpv1/1 Running030s10.224.26.130 worker31.ggg.cloud<none><none>webapp-6d46b54487-frcvf1/1 Running030s10.224.26.191 worker31.ggg.cloud<none><none># 所有节点都调度到 worker31,因为只用worker31可以提供这个卷。# 查看 pvc 状态root@master30 ~13:38:58# kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE local-path-pvc Bound pvc-756f682b-4e9c-4307-a1af-0e315cb282a7 5Gi RWO local-path<unset>88s# 写入测试文件root@master30 ~13:40:24# kubectl exec webapp-6d46b54487-92cpv -- bash -c 'echo Hello World From Nginx > /usr/share/nginx/html/index.html'#查看本地路径root@master30 ~13:41:24# vim local-path-storage.yaml....kind: ConfigMap...."paths":["/opt/local-path-provisioner"].....# 验证本地路径root@worker31 ~12:12:53# ls /opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/index.html root@worker31 ~13:43:46# cat /opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/index.htmlHello World From Nginx3. 清理资源
# 删除 deploymentroot@master30 ~13:43:28# kubectl delete deployments.apps webapp# 删除 pvcroot@master30 ~13:44:20# kubectl delete pvc local-path-pvc# 本地目录也被删除root@worker31 ~13:44:00# ls /opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/ls: cannot access'/opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/':No suchfileor directory部署 NFS provisioner
部署 NFS 服务
# 安装 NFS serverroot@master30 ~13:45:35# apt install -y nfs-kernel-server# 安创建NFS目录 修改创建文件夹的权限root@master30 ~13:45:35# mkdir -m 777 /shares/# 配置共享,允许所有客户端访问root@master30 ~13:50:25# cat << EOF > /etc/exports/shares *(rw,sync,no_root_squash,no_all_squash,insecure)EOF# 重启 nfs serverroot@master30 ~13:50:37# systemctl restart nfs-server.service# 客户端安装root@worker31 ~13:51:03# apt install -y nfs-commonroot@worker32 ~13:51:07# apt install -y nfs-common部署 NFS provisioner
NFS没有内置制备器,这里我们自定义外部分配器。
# 资源部署到命名空间:kube-storageroot@master30 ~13:50:50# kubectl create ns kube-storageroot@master30 ~13:51:33# kubectl config set-context --current --namespace kube-storage1. 创建 RBAC 权限
创建nfs-rbac.yaml文件,赋予 Provisioner 操作 PV/PVC 的权限。
root@master30:~# cat > nfs-rbac.yaml <<'EOF'apiVersion:v1kind:ServiceAccountmetadata:name:nfs-client-provisionernamespace:kube-storage---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRolemetadata:name:nfs-client-provisioner-runnerrules:-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"]-apiGroups:[""]resources:["endpoints"]verbs:["get","list","watch","create","update","patch"]---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRoleBindingmetadata:name:run-nfs-client-provisionersubjects:-kind:ServiceAccountname:nfs-client-provisionernamespace:kube-storageroleRef:kind:ClusterRolename:nfs-client-provisioner-runnerapiGroup:rbac.authorization.k8s.io EOFroot@master30 ~13:51:56# kubectl apply -f nfs-rbac.yaml2. 部署 NFS Provisioner 应用
root@master30:~# cat > nfs-provisioner.yaml <<'EOF'apiVersion:apps/v1kind:Deploymentmetadata:name:nfs-client-provisionernamespace:kube-storagespec:replicas:1strategy:type:Recreateselector:matchLabels:app:nfs-client-provisionertemplate:metadata:labels:app:nfs-client-provisionerspec:serviceAccountName:nfs-client-provisionervolumes:-name:nfs-client-rootnfs:server:10.1.8.30# 同上 NFS 服务器 IPpath:/shares# 核心:同步改为 /sharescontainers:-name:nfs-client-provisionerimage:registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2volumeMounts:-name:nfs-client-rootmountPath:/persistentvolumesenv:-name:PROVISIONER_NAMEvalue:fuseim.pri/ifs-name:NFS_SERVERvalue:10.1.8.30# 替换为你的 NFS 服务器 IP-name:NFS_PATHvalue:/shares# 核心:共享路径改为 /sharesEOF# 部署 NFS Provisioner 应用root@master30 ~13:52:48# kubectl apply -f nfs-provisioner.yaml# 查看 NFS Provisioner 应用root@master30 ~13:53:49# kubectl get deployments.apps -n kube-storageNAME READY UP-TO-DATE AVAILABLE AGE nfs-client-provisioner1/11150s3. 创建 NFS StorageClass
root@master30:~# cat > nfs-storageclass.yaml <<'EOF'apiVersion:storage.k8s.io/v1kind:StorageClassmetadata:name:nfs-storagenamespace:kube-storageprovisioner:fuseim.pri/ifs# 必须和 Provisioner 名称一致parameters:onDelete:retain# 删除 PVC 保留 NFS 数据archiveOnDelete:"false"# 按需设置回收策略#reclaimPolicy: RetainreclaimPolicy:DeleteallowVolumeExpansion:truevolumeBindingMode:Immediate EOF# 创建 StorageClassroot@master30 ~13:54:07# kubectl apply -f nfs-storageclass.yaml# 查看 StorageClassroot@master30 ~13:54:20# kubectl get scNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs-storage fuseim.pri/ifs Delete Immediatetrue7s验证部署
在default命名空间测试。
root@master30 ~13:54:56# kubectl config set-context --current --namespace default1. 创建 pvc
root@master30 ~13:55:19# cat > nfs-pvc.yaml <<'EOF'apiVersion: v1 kind: PersistentVolumeClaim metadata: name: webclaim namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName:"nfs-storage"EOF# 创建 pvcroot@master30 ~13:55:32# kubectl apply -f nfs-pvc.yaml# 查看 pvc 状态root@master30 ~13:55:40# kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE webclaim Bound pvc-7c9258e7-7589-4ecf-bce1-85611518a351 5Gi RWO nfs-storage<unset>12s# 查看 pv 状态root@master30 ~13:55:52# kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pvc-7c9258e7-7589-4ecf-bce1-85611518a351 5Gi RWO Delete Bound default/webclaim nfs-storage<unset>24s# 查看 nfs 共享目录root@master30 ~13:56:04# ls /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351# 准备 web 主页root@master30 ~13:56:33# echo Hello World From Nginx > /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351/index.html2. 创建 deployment
root@master30 ~13:56:42# cat > nfs-deploy.yaml << EOFapiVersion: apps/v1 kind: Deployment metadata: labels: app: webapp name: webapp spec: replicas:2selector: matchLabels: app: webapp template: metadata: labels: app: webapp spec: containers: - image: nginx name: nginx volumeMounts: - name: webapp-data mountPath: /usr/share/nginx/html volumes: - name: webapp-data persistentVolumeClaim: claimName: webclaim EOF root@master30 ~13:56:52# kubectl apply -f nfs-deploy.yamlroot@master30 ~13:57:00# kubectl expose deployment webapp --target-port 80 --port 80 --protocol TCProot@master30 ~13:57:05# kubectl get svc webappNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)AGE webapp ClusterIP10.99.50.211<none>80/TCP 13s# 访问测试root@master30 ~13:57:18# curl 10.99.50.211Hello World From Nginx root@master30 ~13:57:35# curl 10.99.50.211Hello World From Nginx3. 清理资源
# 删除 deploymentroot@master30 ~13:57:36# kubectl delete deployments.apps webapp# 删除 pvc,pv也将一起删除root@master30 ~13:57:48# kubectl delete pvc webclaimroot@master30 ~13:57:59# kubectl get pvNo resources found# 查看后端存储中数据,仍然保留root@master30 ~13:58:23# ls /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351/index.html保留 NFS provisioner ,下一章 statefulset 使用。
