资讯动态

Kubernetes存储自动化:NFS动态供给与PVC/PV绑定实战

发布时间:2026/10/22 18:10:28 来源:尧图企业网站定制
1. NFS动态供给的整体思路与应用场景1.1 为什么需要PVC自动生成PV用过Kubernetes的老哥都知道写PV和PVC的YAML是最烦人的事情之一。尤其是集群里跑着几十个有状态应用每个应用要独立的存储空间PV得一个一个手写PVC还得小心别写错storageClassName一旦哪个字段没对上PVC就永远Pending在那里查半天才发现是存储类名字写错了一个字母。动态供给Dynamic Provisioning解决的就是这个问题。它的核心逻辑是你不再需要预先创建PV只需要在PVC里指定一个StorageClass集群里的Provisioner组件会自动根据PVC的请求容量、访问模式去存储后端创建存储卷然后自动生成PV并绑定到PVC上。整个过程对用户完全透明写PVC的人根本不用关心PV长什么样就像你去食堂吃饭不用关心后厨是怎么备菜的一样点单就完了。NFS是很多中小团队的首选存储后端原因很简单便宜、好维护、不需要额外买商业存储设备能凑合用。虽然性能跟Ceph这种分布式存储比有一定差距但是对于开发环境、测试环境、日志收集这类对IO要求不苛刻的场景NFS动态供给完全够用。1.2 动态供给的工作链路解析在动手配置之前有必要把整个链路的运行原理梳理清楚。动态供给涉及几个核心组件PVCPersistentVolumeClaim用户的存储请求声明需要多大空间、什么访问模式。StorageClass存储类定义了使用哪种Provisioner、回收策略、挂载参数等。Provisioner真正干活的组件它监听集群中PVC的创建事件然后调用存储后端的API去创建卷。NFS Server实际存储数据的文件服务器。整个流程是用户创建PVC → Provisioner监听到新的PVC请求 → 根据StorageClass中的配置在NFS服务器上创建一个子目录或者独立目录 → 把这个目录封装成一个PV对象 → 自动绑定PVC → Pod正常挂载使用。我最早用NFS动态供给的时候碰到的坑主要集中在外部Provisioner的部署方式上。早期版本的NFS Provisioner是以Pod方式跑的需要一个Deployment加上RBAC授权还要给Provisioner传NFS服务器的地址和导出路径版本比较老很多人卡的版本兼容问题。后来社区把NFS Provisioner整合到csi-driver-nfs中用CSI插件的方式接入Kubernetes配置逻辑更规范容器镜像也不用自己费劲去搞了。下面我梳理一下两种主流的方案然后给出一种我个人推荐、实测稳定的落地操作流程。2. 环境准备与存储服务端配置2.1 基础环境要求和版本选型先说环境要求。动态供给这套东西对Kubernetes集群版本没有特别苛刻的限制但为了保证兼容性还是建议集群版本不要太老。我自己在1.24到1.32的集群上都验证过1.26以上用CSI驱动的方式基本没有任何兼容问题如果还是1.23以下的集群建议先升级集群再搞存储别在老旧版本上硬凑。NFS服务器端建议用独立的服务器或者虚拟机不要在Kubernetes集群节点上直接部署NFS服务。我是吃过亏的把NFS放在其中一个节点上结果这个节点故障导致整个集群的存储全挂排查问题的时候定位到存储服务本身也在同一个故障域里相当被动。版本方面NFS服务端用nfs-utils内核版本要求不影响正常使用只要内核支持NFSv4协议就没问题。客户端节点需要安装nfs-utils或nfs-common包这个很重要如果节点上没有装NFS客户端工具后面Pod创建的时候会报mount.nfs: command not found基本上一查一个准。2.2 NFS服务端安装与目录规划服务端的安装其实不难我把常用的几个步骤写一下。以Linux服务器为例安装NFS服务# CentOS / Rocky Linux / 等红帽系 sudo yum install -y nfs-utils # Ubuntu / Debian sudo apt update sudo apt install -y nfs-kernel-server安装完成后需要规划一个父目录作为动态供给的根目录。这个目录规划很有讲究千万不要直接用根分区或者系统盘。我建议单独挂载一块数据盘无论是云盘还是物理盘都行然后格式化挂载到/data目录下。接着在/data下创建一个nfs-share目录专门用于Kubernetes动态供给创建的目录。sudo mkdir -p /data/nfs-share sudo chmod -R 0770 /data/nfs-share sudo chown nobody:nogroup /data/nfs-share # 或者 nfsnobody取决于系统配置/etc/exports导出这个目录这里有一个细节导出选项不能写死no_root_squash除非你知道自己在做什么否则会有权限放大风险。我一般是这么写的/data/nfs-share *(rw,sync,no_subtree_check,no_root_squash)等等我刚才说不要写no_root_squash但这里又写了。这里需要解释一下no_root_squash在这种场景下确实能让NFS动态供给少出一些权限问题但它的安全性确实存在争议。如果集群环境是内部受信网络、没有公网暴露风险很多生产团队也用这种配置属于“权衡之后的取舍”。如果你对安全性要求极高可以改成默认的root_squash选项但那样就需要额外考虑目录属主和权限映射权限问题排查起来会麻烦不少。我的建议是内部测试环境用no_root_squash能降低很多麻烦生产环境如果安全规范很严那就规规矩矩用root_squash然后仔细设计目录权限。配置完成后重启NFS服务并验证sudo systemctl restart nfs-server # 红帽系 sudo systemctl restart nfs-kernel-server # Debian系 sudo exportfs -rav showmount -e localhost2.3 集群节点安装NFS客户端NFS服务端搞定了别忘了客户端的依赖。Kubernetes的每个节点都需要安装nfs-utils或者nfs-common因为最终挂载动作是在节点上完成的节点上没有NFS工具后面所有Pod的挂载都会失败。在节点上批量执行# 红帽系 sudo yum install -y nfs-utils # Debian系 sudo apt update sudo apt install -y nfs-common这个操作一定不要漏。我见过有人服务端配置了半天PVC也创建好了PV也有了但Pod一直ContainerCreating最后看事件才看到mount.nfs not found原因就是客户端节点少装了工具。这一步很小但很致命。3. StorageClass配置与默认存储类设置3.1 使用CSI驱动部署NFS Provisioner现在社区主流的做法是使用csi-driver-nfs这个项目。它由Kubernetes SIG-Storage社区维护通过CSI接口与Kubelet通信负责处理卷的创建、挂载、卸载等操作。相比早期那种纯自定义的Provisioner PodCSI驱动在生命周期管理、异常处理、可观测性方面都更规范。部署CSI驱动有两种方式一种是用Helm一种是从仓库直接部署YAML。我推荐用Helm升级方便配置也直观。先把仓库添加好helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts helm repo update查看一下可用版本然后安装。安装的时候有几个关键参数需要关注helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs \ --namespace kube-system \ --set driver.namenfs.csi.k8s.io \ --set controller.namecsi-nfs-controller \ --set controller.replicaCount1控制器副本数默认1就够了因为这个控制器主要处理创建、删除卷的操作又不是什么高并发服务。当然你要想高可用可以调成2但要注意RBAC的leader-election支持不过1已经能应付绝大多数场景。部署完成之后确认一下Pod状态kubectl get pods -n kube-system | grep csi-nfs正常情况下会看到类似csi-nfs-controller-xxxx的Pod处于Running以及每个节点上都有一个csi-nfs-node-xxxx的DaemonSet Pod。3.2 创建StorageClassCSI驱动就绪之后关键一步是创建StorageClass。这里要注意StorageClass的provisioner字段必须和驱动部署时的driver.name保持一致否则PVC创建的卷请求不会交给这个驱动处理。我常用的StorageClass配置如下apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 192.168.1.100 share: /data/nfs-share mountPermissions: 0770 reclaimPolicy: Delete volumeBindingMode: Immediate mountOptions: - nfsvers4逐项解释一下serverNFS服务器的IP地址这里写的是内网IP。shareNFS导出的根目录路径注意不要带斜杠结束。mountPermissions这个参数会设置NFS目录的权限如果省略目录可能会保持默认权限遇到挂载后无法写入的问题。reclaimPolicy: Delete删除PVC时自动删除PV和NFS中的目录。如果你想保留数据改成Retain。volumeBindingMode: Immediate表示立即绑定PVC创建后马上触发Provisioner创建卷。还有一种模式是WaitForFirstConsumer延迟到第一个Pod调度后再创建卷适合需要拓扑感知的场景但NFS本身没有拓扑限制用Immediate没问题。mountOptions: nfsvers4强制使用NFSv4协议避免NFSv3和NFSv4在某些环境下的兼容性差异。创建StorageClasskubectl apply -f storageclass-nfs-csi.yaml3.3 设置默认存储类如果集群中已经有一个默认存储类比如云服务商自带的你可以把某个存储类设置为集群默认这样用户创建PVC时不写storageClassName也能自动使用NFS动态供给。设置默认存储类的操作kubectl patch storageclass nfs-csi -p {metadata: {annotations:{storageclass.kubernetes.io/is-default-class:true}}}这里有一个坑如果集群里已经存在默认StorageClass你新设置的这个不会生效因为系统发现有多个默认存储类时会拒绝自动填充。你需要先把原来的StorageClass注解去掉kubectl patch storageclass 旧的默认存储类名称 -p {metadata: {annotations:{storageclass.kubernetes.io/is-default-class:null}}}注意这里的null是加引号的字符串不是JSON里的null删注解用这个方式有效。不过我的建议是除非你确定要让NFS作为全集群默认存储否则不要轻易设置默认存储类因为原默认存储类可能是云盘的如果原来那些工作负载打算继续使用云盘你改成NFS后新创建的PVC会全跑到NFS上性能表现差异会让你怀疑人生。4. PVC创建与PV自动生成的实测验证4.1 编写测试PVC并验证自动绑定配置到这里真正核心的验证环节就开始了。写一个最简单的PVC只声明资源请求不指定storageClassName前提是你已经设置了默认存储类或者显式指定storageClassName为nfs-csi。apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-claim spec: accessModes: - ReadWriteOnce storageClassName: nfs-csi resources: requests: storage: 5Gi应用这个PVCkubectl apply -f test-claim.yaml kubectl get pvc test-claim正常情况下几秒内PVC就从Pending变成Bound状态。接着看一下PVkubectl get pv | grep test-claim你会看到自动生成了一个PV名字跟手写PV不一样是一长串随机生成的标识。查看PV的详细定义kubectl get pv pv-name -o yaml能看到这个PV的spec里设置了persistentVolumeReclaimPolicy、storageClassName、csi节点的driver、volumeHandle等信息。最重要的是NFS服务器上对应的目录已经被创建出来了。到NFS服务器上看一眼ls -la /data/nfs-share/会看到一个类似pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx的目录这个目录就是动态供给创建出来的。4.2 挂载到Pod验证读写PVC绑定成功只是第一步真正能读写才说明全链路是通的。创建一个测试Pod把PVC挂载进去apiVersion: v1 kind: Pod metadata: name: test-pod spec: containers: - name: app image: busybox command: [/bin/sh] args: [-c, echo dynamic provisioning works /data/test.txt sleep 3600] volumeMounts: - name: storage mountPath: /data volumes: - name: storage persistentVolumeClaim: claimName: test-claim应用后查看Pod状态kubectl apply -f test-pod.yaml kubectl get pod test-pod kubectl logs test-podPod应该很快进入Running状态日志里不会有任何Error输出。如果一切正常到NFS服务器上验证文件是否落地cat /data/nfs-share/pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/test.txt能读到dynamic provisioning works那整个动态供给链路就完全打通了。这里补充一个我实际遇到的问题有的环境下PVC创建后PV会自动生成但Pod启动后一直报mount.nfs: Stale file handle这种情况大概率是NFS服务端重启过、目录句柄端口冲突导致的重启一下节点上的kubelet或者重新创建Pod通常能解决。4.3 扩容与回收策略验证创建完PVC之后有一个重要验证不能跳过就是存储扩容。CSI NFS驱动是支持在线扩容的前提是StorageClass里没有禁止allowVolumeExpansion并且你在创建时没有设置filesystem层限制。用同样的PVC修改容量请求kubectl patch pvc test-claim -p {spec:{resources:{requests:{storage:10Gi}}}}然后查看PVC状态kubectl get pvc test-claim容量字段会变成10Gi并显示为FileSystemResizePending或直接扩容完成。注意实际生效要等Pod重新挂载或者重启后你说完全无视挂载状态直接扩容有些环境会报错需要把Pod删除后重建才能体面完成扩容。这不是驱动的问题是CSI和Kubelet扩容流程的配合机制就是这样。再验证回收策略如果你用的是Delete策略删除PVC后PV和NFS目录应该自动被清理。这个能力对开发环境很友好动态创建的垃圾卷不需要人工清理。但生产环境建议把reclaimPolicy改为Retain否则一个误删PVC操作会把所有历史数据全部清掉这个风险是真实存在的。我在某次演练中就误删了一个PVC数据全没了虽然是测试环境但心有余悸。5. 权限配置与安全防护的注意事项5.1 目录权限和挂载权限参数的处理NFS动态供给的权限问题是一个高频踩坑点。很多时候PVC绑定了Pod也能起来但应用写入文件时报Permission denied查半天是目录属主和UID/GID不匹配。CSI NFS驱动的mountPermissions参数可以给动态创建的目录设置权限但要注意它设置的是目录权限不是所有者也不是递归处理。如果你的应用容器以root身份运行问题不大如果以非root用户运行可能需要额外处理。举个例子容器内应用UID是1000而NFS目录的所有者是nobody权限是0770导致容器内非root用户无法写入。这时候有两个方向一个是调整Pod的安全上下文比如在securityContext里设置runAsUser和fsGroupsecurityContext: runAsUser: 1000 fsGroup: 2000这样Kubelet在挂载卷时会把NFS目录的属组调整到2000并将卷的组权限设置为可读写。但实际表现取决于NFS服务端的配置如果启用了root_squash节点上以root身份执行chown可能被映射成nobody那就无效了。另一个方向是在NFS服务端对子目录做权限映射不过动态供给创建的目录没法提前创建所以这个方案基本不可行。综合来看最实用的做法是让应用容器的UID和NFS目录的所有者一致或者直接用NFS root_squash配合fsGroup调通这个需要结合你实际的应用镜像来配合。5.2 针对NFS共享的安全配置NFS本身是不加密的协议传输层没有认证机制如果网络不可信数据包很容易被截获。这个问题在内部网络里往往被忽略但当你把Kubernetes集群和NFS服务器放在不同机柜或者跨网段时就要考虑是否有必要用Kubernetes网络策略限制到NFS的访问或者考虑用带加密的替代方案。还有一个小细节——/etc/exports的访问控制。不要把NFS共享暴露给整个网段而是只放行Kubernetes节点的网段/data/nfs-share 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)如果节点跨多个网段就用多个规则条目分别指定。否则NFS共享暴露在整个内网里其他机器只要知道路径就能挂载风险还是不小的。另外建议在NFS服务端配置只读或按需导出的选项不要用星号通配所有来源。这种安全习惯能避免很多莫名其妙的泄露问题。6. 常见问题排查与运维实录6.1 PVC状态一直Pending的排查思路这个现象非常常见新手老手都会遇到。首先查看PVC的详细描述kubectl describe pvc test-claim重点看最后Events内容。我自己遇到的几种典型错误和处理思路storageclass.storage.k8s.io/nfs-csi not foundStorageClass没创建成功或者名字写错了。回去检查StorageClass的metadata.name。provisioning failed: failed to create volume ... no such file or directoryNFS服务端共享目录不存在或者NFS导出方式不对。到服务端用showmount查看导出路径和StorageClass的share字段对比。invalid option nfsvers4是mountOptions语法问题有些系统要求写成vers4而不是nfsvers4这个和NFS客户端版本有关尽量用nfsvers4不好用就换成vers4试试。6.2 Pod一直ContainerCreating的排查思路PVC如果已经Bound但Pod起不来问题和Provisioner没什么关系了基本卡在节点层。查看Pod描述kubectl describe pod test-pod下面几种错误是我遇到过的failed to mount volume ... mount failed: exit status 32节点上NFS客户端工具没装或者NFS服务端防火墙没放行2049端口。先确认端口通不通nc -vz nfs-server-ip 2049。mount.nfs: Connection timed out网络不通检查节点和NFS服务器之间的路由、安全组、防火墙。mount.nfs: access denied by server while mountingexports配置里没有允许该节点的IP或者节点IP不在放行网段内。修改exports后执行exportfs -ra生效。排查这类问题的时候最忌讳的就是在YAML里瞎改然后反复apply更重要的是看事件、看日志、看系统挂在NFS服务端的实时连接。6.3 性能与稳定性调优建议NFS动态供给用起来之后性能监控和调优是长期工作。几个参数值得关注NFS服务端调优默认的NFS线程数适合小规模并发如果负载高可以调整NFS服务进程的数量在/etc/sysconfig/nfs配置文件里修改RPCNFSDCOUNT参数。不过这个影响的是服务端并发处理能力一般几十个Pod挂载的话默认值也够用。客户端挂载参数调优StorageClass的mountOptions里可以加一些参数改善表现mountOptions: - nfsvers4 - noatime - nodiratime - hard - intr其中hard和intr组合比较重要前者表示NFS挂载遇到问题时不放弃重试后者允许在等待期间用信号中断挂载请求避免进程卡死。noatime能减少不必要的元数据写入对读多写少的场景有收益。监控和告警NFS的动态供给本质上是把存储的生命周期管理自动化了但你还是要监控NFS服务器的磁盘容量。因为Provisioner在创建目录的时候不会检查父目录剩余空间写满后所有动态卷都会出问题。建议对/data/nfs-share做容量监控设置阈值告警比如用到80%就报警。6.4 数据备份与灾备思路动态供给会让人产生一个错觉数据好像自动管理了就更安全了。其实恰恰相反PVC一删配合Delete策略数据就没了。所以一定要有备份方案。我在团队里推的做法是对于重要的有状态应用数据库这类不用动态供给而是用静态PV绑定到独立NFS目录同时配置定期的文件级备份到异机。动态供给更多用于无状态化改造过程中的临时磁盘、日志存储、缓存目录这类对数据持久性要求不高的场景。如果你确实要备份动态卷的数据可以通过挂载PVC到备份Pod然后用rsync同步到其他存储或者给NFS服务器本身的目录做快照。这个要看你的NFS后端是什么类型如果只是普通的Linux文件系统上的共享目录那就用传统的文件拷贝方式。7. 实操总结与个人体会整套NFS动态供给搞下来最大的感受就是Kubernetes这套存储抽象确实解决了手工维护PV的痛点但它的学习曲线是前置的你得先理解StorageClass、Provisioner、PVC绑定之间的协作关系不然出了错会很懵。按照本文这套步骤操作从零到一个可用的NFS动态供给环境熟练的话大概半小时能搞定。不熟练的话也不用慌把每个环节的验证步骤都走一遍基本不会出大问题。我自己的经验是先从最基础的配置跑通再逐步增加安全加固和性能优化不要一上来就把所有参数都堆上去否则出了问题都不知道是哪个配置引起的。最后再分享一个小技巧如果你在创建PVC后发现PV没有自动生成可以先查Provisioner的日志用kubectl logs -n kube-system csi-nfs-controller-pod -c nfs-plugin日志里会明确告诉你是哪一步出了问题比你在那里瞎猜YAML快得多。动态供给本来就是为了省事排查手段也得跟上节奏。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑