别再手动创 PV 了!K8S+NFS 动态 PV:StorageClass 一键解决关机阻塞 + 状态混乱
引言手动创建 Kafka 的静态 PV,我真是受够了:
- 写 3 个 PV 的 yaml,复制粘贴都能写错 path;
- 绑定 PVC 时,经常把 kafka-0 的 PVC 绑到 kafka-pv-1 上;
- 删 PV 时卡 Terminating,折腾半天还得硬清 etcd;
- 每个 PV 都要写 mountOptions,重复劳动能把人逼疯。
直到用了StorageClass+NFS 动态供给器,我才发现:原来 K8S 的 PV 可以自动创建、统一配置 —— 今天教你用这招,彻底告别静态 PV 的所有坑,还能顺便解决 NFS 关机阻塞的问题!
一、静态 PV 的痛点,StorageClass 全给你解决了
先列个对比表,看看静态 PV 有多坑,StorageClass 有多香:
| 静态 PV 的痛点 | StorageClass 动态 PV 的优势 |
|---|---|
| 手动写 yaml,重复劳动易出错 | 自动为 PVC 创建 PV,不用写一行 PV 的配置 |
| 每个 PV 都要配 mountOptions,易遗漏 | 统一在 SC 里配 mountOptions,所有 PV 自动继承,解决关机阻塞 |
| PVC 和 PV 绑定混乱,经常绑错 | 自动匹配 PVC 和 PV,绑定关系清晰稳定 |
| 删 PV 时卡 Terminating,状态混乱 | 动态管理资源生命周期,减少死锁问题 |
二、分步实现:StorageClass+NFS 动态 PV(含关机阻塞解决方案)
要实现动态 PV,需要 2 个核心组件:NFS 动态供给器(监听 PVC 请求,自动创 PV)+ StorageClass(统一配置 PV 的挂载选项、回收策略)。
步骤 1:部署 NFS 动态供给器(核心组件)
K8S 原生不支持 NFS 动态 PV,得部署社区维护的nfs-subdir-external-provisioner—— 它会监听 PVC 的创建请求,自动在 NFS 服务器上创建子目录、生成 PV 并绑定 PVC。
创建nfs-provisioner.yaml(包含权限配置 + 供给器部署):
yaml
# nfs-provisioner.yaml:部署NFS动态供给器
# 1. 服务账户:给供给器授权,让它能创建/删除PV
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kafka # 和Kafka同命名空间,方便管理
---
# 2. 集群角色:定义供给器需要的权限(创建PV、更新PVC等)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
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"]
---
# 3. 角色绑定:把集群角色授权给服务账户
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: kafka
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
# 4. 部署供给器Pod:核心组件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kafka
spec:
replicas: 1
strategy:
type: Recreate # 避免滚动更新导致的状态混乱
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
# 用国内镜像,避免拉取失败(国外镜像:k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2)
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes # 供给器挂载NFS的路径
env:
# NFS服务器IP(填你的NFS地址)
- name: NFS_SERVER
value: 192.168.142.141
# NFS根目录(供给器会在这个目录下创子目录)
- name: NFS_PATH
value: /data/kafka
volumes:
- name: nfs-client-root
nfs:
server: 192.168.142.141
path: /data/kafka
# 供给器自身也用soft挂载,避免供给器Pod卡住
mountOptions:
- soft
- timeo=30
应用配置,并验证供给器 Pod 运行正常:
bash
运行
kubectl apply -f nfs-provisioner.yaml
# 查Pod状态,必须是Running
kubectl get pod -n kafka | grep nfs-client-provisioner
# 输出示例:nfs-client-provisioner-7f9d6d789d-2xqzk 1/1 Running 0 5m
步骤 2:创建 StorageClass(统一配置 PV)
StorageClass 是 “动态 PV 的配置模板”—— 在这里配置mountOptions(解决关机阻塞)、回收策略等,所有自动创建的 PV 都会继承这些配置。
创建kafka-sc.yaml:
yaml
# kafka-sc.yaml:统一配置动态PV
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: kafka-nfs-sc # SC名称,后续PVC要引用这个名字
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须和供给器的provisioner一致
parameters:
# NFS子目录的命名格式:命名空间/PVC名(方便后续管理数据)
pathPattern: "${.PVCNamespace}/${.PVCName}"
# 删除PVC时保留NFS数据(对应Retain策略)
onDelete: retain
reclaimPolicy: Retain # PV回收策略:保留数据,避免误删(Kafka是有状态应用,数据不能丢)
# 核心:统一配置NFS挂载选项,解决关机阻塞
mountOptions:
- soft # 软挂载,超时终止
- timeo=30 # 超时3秒
- retrans=2 # 重试2次
- noatime # 优化IO,禁用访问时间更新
应用 StorageClass,并验证创建成功:
bash
运行
kubectl apply -f kafka-sc.yaml
# 查SC列表,状态是Available
kubectl get sc | grep kafka-nfs-sc
# 输出示例:kafka-nfs-sc k8s-sigs.io/nfs-subdir-external-provisioner Retain WaitForFirstConsumer false 10m
步骤 3:创建 PVC,自动生成 PV
现在不用手动创 PV 了!只要创建 PVC 并引用上面的 StorageClass,供给器会自动创建 PV 并绑定。
创建kafka-pvc.yaml(对应 3 个 Kafka 节点):
yaml
# kafka-pvc.yaml:创建PVC,自动触发PV生成
# kafka-0的PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kafka-data-kafka-0
namespace: kafka
spec:
accessModes:
- ReadWriteOnce # 和SC的配置匹配
resources:
requests:
storage: 20Gi # 按需分配存储
storageClassName: kafka-nfs-sc # 引用上面创建的StorageClass
---
# kafka-1的PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kafka-data-kafka-1
namespace: kafka
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: kafka-nfs-sc
---
# kafka-2的PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kafka-data-kafka-2
namespace: kafka
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: kafka-nfs-sc
应用 PVC,并验证绑定状态:
bash
运行
kubectl apply -f kafka-pvc.yaml
# 查PVC状态,会从Pending变成Bound(供给器在创建PV)
kubectl get pvc -n kafka | grep kafka-data
# 输出示例:kafka-data-kafka-0 Bound pvc-7f9d6d789d-2xqzk 20Gi RWO kafka-nfs-sc 2m
步骤 4:验证动态 PV + 挂载选项生效
- 查自动生成的 PV:
bash
运行
kubectl get pv | grep kafka-data
# 输出示例:pvc-7f9d6d789d-2xqzk 20Gi RWO Bound kafka/kafka-data-kafka-0 kafka-nfs-sc 5m
- 验证 PV 的挂载选项(继承自 SC):
bash
运行
kubectl describe pv pvc-7f9d6d789d-2xqzk | grep MountOptions -A 5
# 输出示例:
# MountOptions: soft
# timeo=30
# retrans=2
# noatime
- 验证 NFS 服务器上的子目录:登录 NFS 服务器,会看到供给器自动创建了子目录(对应
pathPattern的配置):
bash
运行
ls /data/kafka/kafka/
# 输出示例:kafka-data-kafka-0 kafka-data-kafka-1 kafka-data-kafka-2
三、最终效果:关机不卡 + 管理省心
- 关机不阻塞:所有 PV 自动继承 SC 的
soft+timeo选项,关机时 NFS 请求会超时终止,再也不会卡 stop job; - 不用手动创 PV:新增 Kafka 节点只需加 PVC,供给器自动创 PV,告别重复劳动;
- 状态稳定:动态管理绑定关系,减少静态 PV 的 “绑定错、删不掉” 问题。
总结:
StorageClass+NFS 动态供给器,是 K8S 部署有状态应用(比如 Kafka)的 “终极存储方案”—— 既解决了 NFS 关机阻塞的痛点,又彻底摆脱了静态 PV 的繁琐和混乱。建议所有 K8S+NFS 的场景都试试这招,绝对能省一半的运维时间!
更多推荐

所有评论(0)