k8s-动态卷供应

📅 2026/8/16 8:29:52
k8s-动态卷供应
Kubernetes 动态卷供应动态卷介绍静态 PV/PVC传统手动方式必须管理员提前手动写 YAML 创建 PV用户再创建 PVC 去匹配 PV没有 PV 就会一直 Pending。动态供给StorageClass PVC不用手动创建 PV只需要创建 PVC集群自动生成 PV 并绑定也就是动态分配 PV。这就是动态卷供应的能力。它彻底省去了集群管理员提前手动配置存储的工作用户只要提出存储需求系统就会自动创建对应的存储卷。动态卷供应流程管理员先创建 “存储模板”StorageClass指定用哪个 “造卷工具”Provisioner制备器来创建卷用户创建 “存储申请单”PVC时指定要用哪个 “存储模板”系统收到申请后会让模板绑定的 “造卷工具” 自动创建一个 PV并且把 PV 和用户的 PVC 绑定用户直接用 PVC 就行。StorageClassStorageClass 就像管理员定义的 “存储套餐”—— 不同套餐对应不同的存储类型、服务质量比如读写速度、备份策略等。Kubernetes 不关心套餐叫什么只负责按套餐规则造卷。命名很重要用户创建 PVC 时要通过这个名字选择对应的存储套餐创建后不能改StorageClass 一旦创建名字和参数就没法修改了要改只能重新建。核心配置每个 “存储套餐” 都包含以下核心信息用来指导系统创建 PVprovisioner造卷工具制备器指定用哪个工具创建 PV必须设置。Kubernetes 自带一些 Provisioner名字以 “kubernetes.io” 开头比如 AWS EBS、Azure Disk也可以使用第三方提供的比如 NFS 没有内置工人需要自己装Provisioner按 Kubernetes 规则运行即可。存储类型是否内置说明AWSElasticBlockStore✅ 是亚马逊云硬盘AzureFile/AzureDisk✅ 是微软云文件 / 硬盘Ceph RBD✅ 是Ceph 块存储Glusterfs✅ 是分布式文件系统NFS❌ 否网络文件系统需装外部工人本地存储Local❌ 否节点本地硬盘需装外部工人iSCSI/CephFS❌ 否需外部工人reclaimPolicyPV 回收策略指定 PV 不用怎么处理 PV。Delete默认PVC 删除后PV 也自动删对应的后端存储比如云硬盘、NFS 目录也会清掉RetainPVC 删除后PV 保留下来后端存储的数据也不删管理员可以手动处理。volumeBindingModePVC 创建出来后指定 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# 存储类型为gp2AWS的通用型SSDreclaimPolicy:Retain# PVC 不用了pv保留allowVolumeExpansion:true# 允许扩容mountOptions:-debug# 挂载时开启调试模式volumeBindingMode:Immediate# 立即创建并绑定PV部署 Local Path provisionerLocal Path Provisioner 是 K8s 官方推荐的本地存储动态卷插件适合需要使用节点本地磁盘的场景。部署本地路径制备器# 下载官方模板rootmaster30 ~13:25:02# wget https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml#可以以直接在浏览器搜# 查看资源配置rootmaster30 ~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_DIRteardown:|-#!/bin/shset-eu rm-rf $VOL_DIRhelperPod.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 类资源组成每部分各司其职资源类型名称核心作用Namespacelocal-path-storage隔离 Local Path Provisioner 相关资源ConfigMaplocal-path-config定义本地存储路径规则如默认/opt/local-path-provisionerServiceAccount RBAClocal-path-provisioner赋予 Provisioner 操作 PV/PVC 的权限Deploymentlocal-path-provisioner运行 Local Path Provisioner 核心程序动态创建本地 PVStorageClasslocal-path提供给 PVC 调用的「本地存储模板」# 资源默认部署在命名空间local-path-storagerootmaster30 ~13:26:19# kubectl apply -f local-path-storage.yaml# 确认资源状态rootmaster30 ~13:27:16# kubectl get sc local-pathNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE local-path rancher.io/local-path Delete WaitForFirstConsumerfalse67m rootmaster30 ~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验证部署# 设置默认命名空间rootmaster30 ~13:34:45# kubectl config set-context --current --namespace default1. 创建 pvc#向名为local‑path的存储类申请5Gi本地存储访问模式为单节点读写放在 default 命名空间由 local‑path provisioner 自动生成绑定 PV供 Pod 挂载持久化数据。rootmaster30 ~13:35:01# cat local-path-pvc.yaml EOFapiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-path-pvc namespace: default spec: accessModes:[ReadWriteOnce]resources: requests: storage: 5Gi storageClassName: local-path# 指定 Local Path 的 StorageClassEOF rootmaster30 ~13:36:28# kubectl apply -f local-path-pvc.yamlrootmaster30 ~13:37:49# kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE local-path-pvc Pending local-pathunset14s# 等待pod关联才会创建本地目录2. 创建 deploymentrootmaster30 ~13:38:03# cat local-path-deployment.yaml EOFapiVersion: 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 rootmaster30 ~13:38:18# kubectl apply -f local-path-deployment.yaml# 查看 pod 状态rootmaster30 ~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.cloudnonenonewebapp-6d46b54487-frcvf1/1 Running030s10.224.26.191 worker31.ggg.cloudnonenone# 所有节点都调度到 worker31因为只用worker31可以提供这个卷。# 查看 pvc 状态rootmaster30 ~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-pathunset88s# 写入测试文件rootmaster30 ~13:40:24# kubectl exec webapp-6d46b54487-92cpv -- bash -c echo Hello World From Nginx /usr/share/nginx/html/index.html#查看本地路径rootmaster30 ~13:41:24# vim local-path-storage.yaml....kind: ConfigMap....paths:[/opt/local-path-provisioner].....# 验证本地路径rootworker31 ~12:12:53# ls /opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/index.html rootworker31 ~13:43:46# cat /opt/local-path-provisioner/pvc-756f682b-4e9c-4307-a1af-0e315cb282a7_default_local-path-pvc/index.htmlHello World From Nginx3. 清理资源# 删除 deploymentrootmaster30 ~13:43:28# kubectl delete deployments.apps webapp# 删除 pvcrootmaster30 ~13:44:20# kubectl delete pvc local-path-pvc# 本地目录也被删除rootworker31 ~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 serverrootmaster30 ~13:45:35# apt install -y nfs-kernel-server# 安创建NFS目录 修改创建文件夹的权限rootmaster30 ~13:45:35# mkdir -m 777 /shares/# 配置共享允许所有客户端访问rootmaster30 ~13:50:25# cat EOF /etc/exports/shares *(rw,sync,no_root_squash,no_all_squash,insecure)EOF# 重启 nfs serverrootmaster30 ~13:50:37# systemctl restart nfs-server.service# 客户端安装rootworker31 ~13:51:03# apt install -y nfs-commonrootworker32 ~13:51:07# apt install -y nfs-common部署 NFS provisionerNFS没有内置制备器这里我们自定义外部分配器。# 资源部署到命名空间kube-storagerootmaster30 ~13:50:50# kubectl create ns kube-storagerootmaster30 ~13:51:33# kubectl config set-context --current --namespace kube-storage1. 创建 RBAC 权限创建nfs-rbac.yaml文件赋予 Provisioner 操作 PV/PVC 的权限。rootmaster30:~# cat nfs-rbac.yaml EOFapiVersion: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 EOFrootmaster30 ~13:51:56# kubectl apply -f nfs-rbac.yaml2. 部署 NFS Provisioner 应用rootmaster30:~# cat nfs-provisioner.yaml EOFapiVersion: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 应用rootmaster30 ~13:52:48# kubectl apply -f nfs-provisioner.yaml# 查看 NFS Provisioner 应用rootmaster30 ~13:53:49# kubectl get deployments.apps -n kube-storageNAME READY UP-TO-DATE AVAILABLE AGE nfs-client-provisioner1/11150s3. 创建 NFS StorageClassrootmaster30:~# cat nfs-storageclass.yaml EOFapiVersion: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# 创建 StorageClassrootmaster30 ~13:54:07# kubectl apply -f nfs-storageclass.yaml# 查看 StorageClassrootmaster30 ~13:54:20# kubectl get scNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs-storage fuseim.pri/ifs Delete Immediatetrue7s验证部署在default命名空间测试。rootmaster30 ~13:54:56# kubectl config set-context --current --namespace default1. 创建 pvcrootmaster30 ~13:55:19# cat nfs-pvc.yaml EOFapiVersion: v1 kind: PersistentVolumeClaim metadata: name: webclaim namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName:nfs-storageEOF# 创建 pvcrootmaster30 ~13:55:32# kubectl apply -f nfs-pvc.yaml# 查看 pvc 状态rootmaster30 ~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-storageunset12s# 查看 pv 状态rootmaster30 ~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-storageunset24s# 查看 nfs 共享目录rootmaster30 ~13:56:04# ls /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351# 准备 web 主页rootmaster30 ~13:56:33# echo Hello World From Nginx /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351/index.html2. 创建 deploymentrootmaster30 ~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 rootmaster30 ~13:56:52# kubectl apply -f nfs-deploy.yamlrootmaster30 ~13:57:00# kubectl expose deployment webapp --target-port 80 --port 80 --protocol TCProotmaster30 ~13:57:05# kubectl get svc webappNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)AGE webapp ClusterIP10.99.50.211none80/TCP 13s# 访问测试rootmaster30 ~13:57:18# curl 10.99.50.211Hello World From Nginx rootmaster30 ~13:57:35# curl 10.99.50.211Hello World From Nginx3. 清理资源# 删除 deploymentrootmaster30 ~13:57:36# kubectl delete deployments.apps webapp# 删除 pvcpv也将一起删除rootmaster30 ~13:57:48# kubectl delete pvc webclaimrootmaster30 ~13:57:59# kubectl get pvNo resources found# 查看后端存储中数据仍然保留rootmaster30 ~13:58:23# ls /shares/default-webclaim-pvc-7c9258e7-7589-4ecf-bce1-85611518a351/index.html保留 NFS provisioner 下一章 statefulset 使用。