在 K8s 里搞存储,很多人一开始都是手动写 YAML 建 PV,后来才发现动态供给(Dynamic Provisioning) 才是王道。
本文不扯虚的,直接上手两套方案:先搞定轻量级的本地存储 local-path-provisioner,再搭建企业级共享存储 NFS。读完这篇,你就能根据业务需求,灵活搞定 K8s 的持久化存储了。
一、安装本地动态存储
如果你只是想快速测试,或者跑一些对 IO 要求极高、且不需要跨节点共享的组件(比如单机 Prometheus、临时计算任务),Rancher 开源的 local-path-provisioner 是最佳选择。它直接利用节点本地磁盘给 Pod 分配存储,性能几乎等同于裸盘。
1.一键部署
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
kubectl patch storageclass local-path -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl delete pv --all 2>/dev/null || true kubectl delete pvc --all --all-namespaces 2>/dev/null || true
kubectl delete sc local-path --ignore-not-found=true
kubectl delete deployment local-path-provisioner -n local-path-storage --ignore-not-found=true kubectl delete ns local-path-storage --ignore-not-found=true
|
2. 验证与使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| kubectl get sc
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-local-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi EOF
kubectl get pvc test-local-pvc
|
总结:local-path 部署极简,性能接近裸盘,但无法跨节点读写(仅 ReadWriteOnce),且 Pod 漂移后数据可能丢失(除非配合节点亲和性)。这就引出了我们下面要讲的 NFS。
二、方案对比:Local-Path vs NFS
在决定上 NFS 之前,我们先看一张核心对比图,理清两者的边界:
| 对比维度 |
local-path-provisioner |
NFS (网络文件系统) |
| 存储位置 |
节点本地磁盘(SSD/HDD) |
独立的远程 NFS 服务器 |
| 读写性能 |
⭐⭐⭐⭐⭐ 极高(无网络开销) |
⭐⭐⭐ 中等(受限于网络带宽和延迟) |
| 访问模式 |
仅支持 ReadWriteOnce(单节点) |
支持 ReadWriteMany(多节点同时读写) |
| 数据持久性 |
节点故障则数据丢失(除非节点恢复) |
集中存储,节点故障不影响数据安全 |
| 适用场景 |
高性能数据库、缓存、临时计算任务 |
共享配置文件、AI 模型仓库、日志收集、跨 Pod 共享文件 |
| 维护成本 |
极低(无需额外服务器) |
需要独立维护 NFS 服务器及磁盘阵列 |
一句话总结:追求极致性能且数据不共享,选 local-path;需要多 Pod 共享数据或做高可用持久化,选 NFS。
三、NFS 服务端安装与配置
- 假设你有一台独立的节点(或 NAS 设备)作为 NFS 服务端,IP 为
192.168.32.131。
1.安装NFS
Ubuntu / Debian 环境:
1 2 3 4 5 6 7
| sudo apt update
sudo apt install -y nfs-kernel-server nfs-common rpcbind
sudo apt install -y nfs-common rpcbind
|
CentOS / RHEL 环境:
1 2
| sudo yum install -y nfs-utils rpcbind
|
2.服务端-创建共享目录 /nfs/data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| sudo mkdir -p /data/nfs-share
echo "/data/nfs-share *(insecure,rw,sync,no_root_squash)" | sudo tee /etc/exports
sudo systemctl enable --now rpcbind
sudo systemctl enable --now nfs-server
sudo exportfs -arv
sudo ufw allow nfs && sudo ufw allow rpcbind
|
3.NFS 从节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| showmount -e 192.168.32.131
sudo mkdir -p /data/nfs-share sudo mount -t nfs 192.168.32.131:/data/nfs-share /data/nfs-share
echo "hello nfs server" | sudo tee /data/nfs-share/test.txt
echo "192.168.32.131:/data/nfs-share /data/nfs-share nfs defaults,_netdev 0 0" | sudo tee -a /etc/fstab
sudo mount -a
|
4. 常用运维命令(备忘)
1 2 3 4
| sudo systemctl restart nfs-server rpcbind
sudo umount -lf /data/nfs-share
|
四、K8s接入 NFS 动态存储
手动建 PV 太原始了,我们使用官方推荐的 nfs-subdir-external-provisioner。它就像一个小管家,监听到 PVC 请求后,会自动在 NFS 目录下创建子目录并绑定 PV。
1. 通过 Helm 安装 Provisioner
1 2 3 4 5 6 7 8 9 10
|
helm repo remove nfs-subdir-external-provisioner
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm repo update
helm search repo nfs-subdir-external-provisioner
|
2. 准备核心配置文件 values.yaml
把默认配置导出并修改关键参数:
1
| helm show values nfs-subdir-external-provisioner/nfs-subdir-external-provisioner > nfs-values.yaml
|
nfs-values.yaml默认字段的解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| replicaCount: 1
strategyType: Recreate
image: repository: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner tag: v4.0.2 pullPolicy: IfNotPresent
imagePullSecrets: []
nfs: server: path: /nfs-storage mountOptions: volumeName: nfs-subdir-external-provisioner-root reclaimPolicy: Retain
storageClass: create: true
defaultClass: false
name: nfs-client
allowVolumeExpansion: true
reclaimPolicy: Delete
archiveOnDelete: true
onDelete:
pathPattern:
accessModes: ReadWriteOnce
volumeBindingMode: Immediate
annotations: {}
leaderElection: enabled: true
rbac: create: true
podSecurityPolicy: enabled: false
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
serviceAccount: create: true
annotations: {}
name:
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}
labels: {}
podDisruptionBudget: enabled: false maxUnavailable: 1
|
最终我的修改 nfs-values.yaml,如下(其他保持默认即可):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
replicaCount: 1
strategyType: Recreate
image: repository: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisioner tag: v4.0.2 nfs: server: 192.168.32.131 path: /data/nfs-share mountOptions: - nolock,tcp,noresvport
storageClass: create: true defaultClass: true name: nfs-client reclaimPolicy: Delete archiveOnDelete: true allowVolumeExpansion: true volumeBindingMode: Immediate accessModes: ReadWriteMany
resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi
|
注意:archiveOnDelete: true 非常实用!删除 PVC 时不会直接删数据,而是把目录重命名归档,给运维留一条后路。
3. 部署到集群
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ -f nfs-values.yaml \ -n nfs-system \ --create-namespace
helm upgrade nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ -f nfs-values.yaml \ -n nfs-system
kubectl get pods -n nfs-system
|
4. 验证动态存储是否生效
创建一个测试 PVC:
1 2 3 4 5 6 7 8 9 10 11 12 13
| cat <<EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: storageClassName: nfs-client # 与上面的name一致 accessModes: - ReadWriteOnce resources: requests: storage: 1Gi EOF
|
查看状态和自动生成的 PV:
1 2
| kubectl get pvc test-pvc kubectl get pv
|
此时去 NFS 服务端的 /data/nfs-share 下查看,你会发现多了一个类似 default-test-pvc-pvc-xxxx 的子目录——大功告成!

五、结语与最佳实践建议
存储选型:在 K8s 中,local-path 和 NFS 并不是二选一,而是共存互补的关系。你可以将 NFS 作为默认存储,专门给无状态应用和共享存储用;将 local-path 留给需要高速读写的数据库(如 etcd、Prometheus)。
性能调优:如果 NFS 传输大文件(比如 AI 模型),可以在 mountOptions 里加上 rsize=1048576,wsize=1048576 提高吞吐量。
生产安全:务必把 /etc/exports 中的 * 换成具体的 CIDR(如 192.168.32.0/24),并考虑 NFS 结合 Kerberos 加密或内网隔离。
故障排查三板斧:
PVC 一直 Pending?执行 kubectl describe pvc <name> 看 Events。
提示 mount failed?去节点上执行 showmount -e <server_ip> 检查网络通不通。
容器内报 Permission denied?检查 NFS 服务端的目录属主,或者确认是否加了 no_root_squash。
现在,你已经具备了在 K8s 中搭建完整存储体系的能力。下一期我们将基于这套 NFS 存储,实战部署 Harbor 私有镜像仓库 和 KServe AI 模型推理平台,敬请期待!
如果觉得本文对你有帮助,欢迎 点赞、在看、转发 支持一下!有任何配置疑问,欢迎在评论区留言交流~