本文基于实战操作整理,补全了生产环境的常见踩坑点。如需 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 最大值,提升 IOhard:网络断后硬重试(不要用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.2serviceAccount命名空间正确
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 报 ImagePullBackOff 拉 registry.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_squash 或 fsGroup |
| 删除 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
相关阅读
- Centos7下NFS文件系统挂载 — NFS 服务端搭建基础
- K8s gcr.io 镜像拉取修复 — 国内环境镜像拉取专题