引言手动创建 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 + 挂载选项生效
  1. 查自动生成的 PV:

bash

运行

kubectl get pv | grep kafka-data
# 输出示例:pvc-7f9d6d789d-2xqzk   20Gi        RWO            Bound   kafka/kafka-data-kafka-0   kafka-nfs-sc   5m
  1. 验证 PV 的挂载选项(继承自 SC):

bash

运行

kubectl describe pv pvc-7f9d6d789d-2xqzk | grep MountOptions -A 5
# 输出示例:
# MountOptions:  soft
#                timeo=30
#                retrans=2
#                noatime
  1. 验证 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 的场景都试试这招,绝对能省一半的运维时间!

更多推荐