本文基于实战操作整理,补全了生产环境的常见踩坑点。如需 NFS 服务端搭建,可参考 Centos7下NFS文件系统挂载

一、架构概览

NFS Subdir External Provisioner 是 Kubernetes SIG 维护的动态卷供应(Dynamic Provisioning) 插件,核心原理:

Pod → PVC → StorageClass "nfs-client" → Provisioner 自动创建 PV → 自动在 NFS 服务器上建子目录 → 挂载给 Pod

对比传统静态 PV:NFS 服务器上每个 PVC 对应一个自动生成的子目录,无需手工创建 PV。

二、部署步骤

2.1 所有 node 节点安装 NFS 客户端

yum -y install nfs-utils

⚠️ 关键:是 所有 node 节点,不只是 NFS 服务器。Kubelet 在挂载 PV 时会在每个 node 上调用 mount.nfs,缺包会导致 MountVolume.SetUp failed 报错。

验证:

showmount -e <NFS_SERVER_IP>

2.2 添加 Helm 仓库

helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/

helm repo update

2.3 准备 values.yaml

helm show values nfs-subdir-external-provisioner/nfs-subdir-external-provisioner > values.yaml

编辑关键字段:

nfs:
  server: nfs.todoit.tech      # NFS 服务器地址(IP 或域名)
  path: /mnt/nfs               # NFS 共享目录
  mountOptions:
    - vers=4.1                 # 强制 NFSv4.1,生产推荐
    - rsize=1048576
    - wsize=1048576
    - hard
    - timeo=600
    - retrans=3
  volumeName: nfs-subdir-external-provisioner-root
  # Reclaim policy for the main nfs volume
  reclaimPolicy: Retain        # Root PV 不自动删,生产必改 Retain

💡 mountOptions 调优说明:

  • vers=4.1:固定 NFS 版本,避免客户端协商到 v3(锁问题)
  • rsize/wsize=1048576:每次读写 1MB,NFSv4 最大值,提升 IO
  • hard:网络断后硬重试(不要用 soft,会丢数据)

2.4 Dry-run 预览

helm install -f values.yaml nfs nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --dry-run

输出正常再正式部署。重点确认:

  • image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
  • serviceAccount 命名空间正确

2.5 镜像拉取(国内环境)

registry.k8s.io 在国内直连不通,需要中转。详见 K8s gcr.io 镜像拉取修复

方案 A:国外主机中转(适合生产,稳定可控)

# 国外主机
docker pull registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker save -o nfs.tar registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2

# 国内主机
docker load -i nfs.tar

方案 B:国内镜像代理(快速)

docker pull registry.lank8s.cn/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker tag registry.lank8s.cn/sig-storage/nfs-subdir-external-provisioner:v4.0.2 \
           registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2

方案 C:导入到集群内私有仓库(推荐生产)

# 推送到内网 Harbor / registry
docker tag registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 \
           registry.internal:5000/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker push registry.internal:5000/sig-storage/nfs-subdir-external-provisioner:v4.0.2

# Helm values.yaml 中覆盖 image.repository

2.6 正式部署

# 先创建命名空间
kubectl create ns nfs-provisioner

helm install -f values.yaml nfs nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -n nfs-provisioner

2.7 验证部署

kubectl get pods -n nfs-provisioner -w

期望输出:STATUS = Running,READY = 1/1

进一步验证 RBAC 和 Provisioner 工作正常:

# 查看日志
kubectl logs -n nfs-provisioner -l app=nfs-subdir-external-provisioner

# 测试动态供应(创建一个测试 PVC)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-nfs-pvc
  namespace: default
spec:
  accessModes: [ReadWriteMany]
  storageClassName: nfs-client
  resources:
    requests:
      storage: 1Gi
EOF

# 等待几秒,查看是否自动创建了 PV
kubectl get pv
kubectl get pvc test-nfs-pvc

成功标志:STATUS = Bound,且在 NFS 服务器上能看到自动生成的子目录 archived-default-test-nfs-pvc-pvc-xxxxx

2.8 查看默认 StorageClass

kubectl get sc

输出示例:

NAME         PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION
nfs-client   cluster.local/nfs-nfs-subdir-external-provisioner   Delete          Immediate           true

三、生产环境关键配置

3.1 回收策略:慎用 Delete

默认 StorageClass 的 reclaimPolicy: Delete 意味着 PVC 删除时会自动删 NFS 服务器上的子目录——生产必改 Retain

storageClass:
  defaultClass: false          # 不要设为默认,避免误用
  reclaimPolicy: Retain        # 改为 Retain,数据保留
  allowVolumeExpansion: true   # 允许扩容

3.2 StorageClass 不设为默认

defaultClass: false,让业务显式指定 storageClassName: nfs-client,避免 PVC 无声明时被自动绑定到 NFS。

3.3 NFS 服务器高可用

NFS 本身没有 HA,生产建议:

  • 方案 A:DRBD + Heartbeat 做主备 NFS,虚拟 IP 漂移
  • 方案 B:商用 NAS(如 NetApp / 华为 OceanStor)替代自建 NFS
  • 方案 C:多 NFS 服务器 + 不同 StorageClass(用 topology 调度)

3.4 权限配置

Provisioner 默认以 root 写入 NFS 目录(no_root_squash),安全风险高。生产建议:

  • NFS 服务器 exports 限制允许的客户端网段
  • 应用容器使用非 root 用户运行时,在 PVC 中配置 fsGroup 做权限收敛
  • 必要时给 NFS 子目录单独建用户,all_squash + anonuid/anongid

3.5 监控与告警

# Prometheus ServiceMonitor 启用(若集群装了 Prometheus Operator)
metrics:
  enabled: true

关键指标:

  • nfs_provisioner_total:供应次数
  • nfs_provisioner_errors_total:错误次数
  • nfs_provisioner_duration_seconds:供应耗时

四、常见故障排查

现象 原因 修复
Pod 启动报 MountVolume.SetUp failed for volume "xxx": mount failed node 缺 nfs-utils 所有 node 执行 yum install -y nfs-utils
Pod 报 ImagePullBackOffregistry.k8s.io 国内不通 见 2.5 镜像拉取方案
PVC 一直 Pending,无 PV 创建 Provisioner Pod 未 Running kubectl logs 看 provisioner 报错
Provisioner 报 connection refused NFS 服务器防火墙或服务未起 showmount -e 验证 + systemctl status nfs
NFS 子目录创建了但权限错(业务写不进去) NFS exports 配置问题 检查 no_root_squashfsGroup
删除 PVC 后 NFS 数据还在 reclaimPolicy: Retain(正常) 手工清理即可

五、完整示例:部署一个使用 NFS 的 WordPress

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-data
  namespace: blog
spec:
  accessModes: [ReadWriteMany]
  storageClassName: nfs-client
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  namespace: blog
spec:
  replicas: 1
  selector:
    matchLabels: { app: wordpress }
  template:
    metadata:
      labels: { app: wordpress }
    spec:
      containers:
      - name: wordpress
        image: wordpress:6-php8.3-apache
        volumeMounts:
        - name: data
          mountPath: /var/www/html
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: wordpress-data

相关阅读