ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

K8S中NFS动态存储供给实战:从部署到生产避坑

K8S中NFS动态存储供给实战:从部署到生产避坑 1. 为什么要在K8S里折腾NFS动态供给很多团队在K8S里跑有状态服务时第一个绕不开的问题就是存储。容器本身是无状态的Pod一重启写在容器里的数据就没了。早期大家最常用的办法是hostPath或者emptyDir简单粗暴但问题也很明显Pod漂移到别的节点数据就找不到了多个副本想共享同一份数据更是无从谈起。再往后一点有人开始用PVPersistentVolume加PVCPersistentVolumeClaim的静态绑定方式。管理员提前创建好一堆PV用户写PVC去申请。这个方案在小规模场景下能用但一旦集群里跑了几十个需要存储的服务管理员就得手动创建几十个PV还得盯着哪个被用了、哪个空着维护成本高得离谱。更麻烦的是用户提交PVC时如果容量、访问模式跟现有PV对不上PVC就会一直卡在Pending状态排查起来也费劲。NFS动态供给就是为了解决这个痛点出现的。它的核心思路是集群里部署一个Provisioner供给器用户只需要提交PVCProvisioner会自动在NFS服务器上创建对应的目录并生成匹配的PV然后自动跟PVC绑定。整个过程不需要管理员介入PVC提交后几秒钟就能变成Bound状态Pod直接挂载使用。这套方案特别适合以下几类场景中小规模集群里跑有状态应用比如数据库、日志收集、文件共享服务团队内部测试环境需要快速给不同项目分配独立存储空间以及那些对存储性能要求不是极致、但需要数据持久化和多副本共享的场景。NFS本身是网络文件系统天然支持多节点同时挂载读写权限控制也相对成熟配合K8S的动态供给机制能省掉大量手工运维工作。不过我得先说清楚NFS动态供给不是银弹。它的性能受网络和NFS服务器磁盘IO限制高并发写入场景下可能成为瓶颈另外NFS本身是单点服务器挂了所有挂载它的Pod都会受影响。所以生产环境用这套方案NFS服务端的高可用和备份策略必须提前规划好。这篇文章我会从零开始把NFS服务端配置、Provisioner部署、StorageClass定义、PVC动态挂载、常见坑排查整个链路讲透代码和配置都可以直接抄作业。2. NFS服务端的搭建与导出目录规划2.1 选一台合适的机器做NFS ServerNFS服务端不一定非要独立物理机用一台配置还行的虚拟机或者闲置服务器都行。我的经验是磁盘IO尽量好一点因为所有Pod的读写最终都落到这台机器的磁盘上。如果预算有限至少保证数据盘是SSD系统盘用机械盘无所谓。内存和CPU要求不高2核4G就能跑得很稳但如果挂载的Pod数量多、并发读写频繁建议4核8G起步。操作系统层面主流的Linux发行版都自带NFS服务端软件包。我习惯用较新的稳定版系统内核里NFS模块支持比较完善。安装之前先确认一下系统是否已经装了nfs-utils或者nfs-kernel-server不同发行版包名不一样。用rpm -qa | grep nfs或者dpkg -l | grep nfs查一下有的话就不用重复装。网络方面NFS服务端和K8S集群节点最好在同一个内网网段延迟低、带宽足。如果跨网段记得防火墙要放行NFS相关端口。NFS默认用2049端口做数据传输但辅助服务mountd、rpcbind会随机用一些端口所以生产环境建议在配置文件里把端口固定下来方便防火墙规则管理。2.2 安装NFS服务端并配置导出目录以常见的Linux发行版为例安装命令大概是这样的# Debian/Ubuntu系 apt-get update apt-get install -y nfs-kernel-server # RHEL/CentOS系 yum install -y nfs-utils装完之后创建专门用于K8S存储的目录。我一般会单独挂一块数据盘挂载到/data/nfs这样的路径下然后在这个目录里再分门别类。比如mkdir -p /data/nfs/k8s chmod 777 /data/nfs/k8s这里权限给777是为了省事让Provisioner能直接创建子目录。但生产环境更稳妥的做法是创建一个专用用户把目录属主改掉然后在NFS导出配置里做用户映射。不过K8S的NFS Provisioner默认以root身份操作如果服务端做了root_squash创建目录时可能会权限不足。所以测试环境我通常先关掉root_squash生产环境再根据安全要求调整。接下来编辑/etc/exports文件配置导出规则/data/nfs/k8s *(rw,sync,no_subtree_check,no_root_squash)这行配置的意思是把/data/nfs/k8s目录导出给所有网段读写权限同步写入不检查子目录并且不压缩root用户权限。sync选项保证数据写入磁盘后才返回成功虽然性能比async差一点但数据安全性高很多。no_root_squash让客户端的root用户在服务端也保持root权限这样Provisioner创建目录时不会因为权限问题失败。配置改完后让导出规则生效exportfs -arv然后重启NFS服务systemctl restart nfs-kernel-server systemctl enable nfs-kernel-server在K8S节点上验证一下能不能挂载showmount -e NFS_SERVER_IP mount -t nfs NFS_SERVER_IP:/data/nfs/k8s /mnt能挂上并且能读写文件说明服务端配置没问题。这一步很关键很多后续问题都是因为服务端导出配置不对导致的所以务必先手工验证通过再往下走。2.3 固定NFS辅助服务端口前面提到NFS除了2049端口还会用到mountd和rpcbind的随机端口。如果K8S节点和NFS服务端之间有防火墙随机端口会被拦掉导致挂载超时。解决办法是在NFS服务端配置文件里把端口固定。编辑/etc/nfs.conf或者/etc/sysconfig/nfs不同发行版路径不同设置[lockd] port32803 udp-port32769 [mountd] port20048 [statd] port32765 outgoing-port32766改完后重启服务然后用rpcinfo -p确认端口都固定了。防火墙放行2049、20048、32803、32765这些端口。这一步做完跨网段挂载基本就不会因为端口问题卡住了。3. 部署NFS Subdir External Provisioner3.1 Provisioner的工作原理与选型K8S官方并没有内置NFS动态供给器社区里用得最多的是nfs-subdir-external-provisioner这个项目。它的工作逻辑很清晰监听集群里新创建的PVC一旦发现PVC的StorageClass匹配自己就在NFS服务端的导出目录下创建一个以${namespace}-${pvcName}-${pvName}命名的子目录然后生成对应的PV对象最后把PV和PVC绑定。这个Provisioner以Deployment形式跑在集群里通常单副本就够了。它内部用了一个小的控制器循环通过K8S的API监听PVC事件。因为NFS本身不支持配额管理所以PVC里申请的容量大小实际上只是个标签并不会真正限制目录使用空间。这一点要特别注意别以为申请了10G就只能用10G实际能写多少取决于NFS服务端磁盘剩余空间。选型方面社区还有基于CSI的NFS驱动功能更丰富支持快照、扩容等高级特性。但如果只是基础的文件共享和持久化需求nfs-subdir-external-provisioner足够用部署简单维护成本低。我下面就以这个方案为例来展开。3.2 用Helm快速部署Provisioner手工写一堆YAML比较繁琐推荐用Helm Chart来部署。先添加仓库helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm repo update然后创建一个values文件把NFS服务端地址和导出路径填进去nfs: server: NFS_SERVER_IP path: /data/nfs/k8s storageClass: name: nfs-client defaultClass: false reclaimPolicy: Delete archiveOnDelete: true这里几个参数值得说明一下。reclaimPolicy: Delete表示PVC删除后对应的PV和NFS目录也会被清理掉。archiveOnDelete: true是个很实用的选项它不会直接删目录而是把目录重命名成archived-xxx相当于留了个后悔药。万一误删了PVC数据还在NFS服务端能找到。生产环境我强烈建议开启这个选项。执行安装helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ -f values.yaml \ -n kube-system装完后检查Pod状态kubectl get pods -n kube-system | grep nfs看到Running就说明Provisioner起来了。再看一下StorageClasskubectl get sc应该能看到名为nfs-client的StorageClass。如果没看到多半是RBAC权限或者镜像拉取有问题用kubectl logs看Provisioner的日志排查。3.3 手工部署方式与关键配置解读有些环境不方便用Helm那就手工部署。核心资源包括ServiceAccount、ClusterRole、ClusterRoleBinding、Deployment、StorageClass。我重点说Deployment里的几个关键配置。Provisioner容器启动参数里--provisioner指定供给器名称必须和StorageClass里的provisioner字段一致否则PVC不会触发动态供给。--nfs-server和--nfs-path指定NFS服务端信息。环境变量POD_NAMESPACE用fieldRef注入让Provisioner知道自己跑在哪个命名空间。Deployment的volume部分要挂载一个NFS卷指向服务端的导出目录。这样Provisioner容器内部才能在该目录下创建子目录。挂载配置大概长这样volumes: - name: nfs-client-root nfs: server: NFS_SERVER_IP path: /data/nfs/k8s容器里的挂载点通常是/persistentvolumesProvisioner就在这个路径下创建子目录。StorageClass的YAML里provisioner字段填k8s-sigs.io/nfs-subdir-external-provisionerreclaimPolicy按需选Delete或RetainvolumeBindingMode用Immediate就行NFS不涉及拓扑感知调度。手工部署的好处是每个字段都看得见出问题容易定位。Helm部署省事但排查时得先helm get manifest把渲染后的YAML导出来看。两种方式我都用过建议新手先手工部署一遍理解每个组件的作用之后再上Helm提效。4. PVC动态挂载的完整实操链路4.1 创建一个测试PVC并观察绑定过程StorageClass就绪后创建一个PVC试试apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc namespace: default spec: accessModes: - ReadWriteMany storageClassName: nfs-client resources: requests: storage: 1Gi注意accessModes这里写的是ReadWriteMany因为NFS天然支持多节点读写。如果写ReadWriteOnce虽然也能绑定但语义上不太匹配NFS的能力。storageClassName必须和前面创建的StorageClass名字一致。提交之后用kubectl get pvc -w盯着看。正常流程是PVC先变成PendingProvisioner监听到事件在NFS目录下创建子目录生成PV然后PVC变成Bound。整个过程通常几秒钟。如果超过一分钟还是Pending就要查Provisioner日志了。绑定成功后kubectl get pv能看到自动生成的PV名字一般是pvc-uuid格式。PV的claimRef指向刚才的PVCnfs字段里的path就是Provisioner创建的子目录路径。到NFS服务端上ls /data/nfs/k8s应该能看到一个类似default-test-pvc-pvc-xxxx的目录。4.2 用Deployment挂载PVC验证读写光绑定成功还不够得实际挂载进Pod验证读写。写一个简单的DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: nfs-test-app spec: replicas: 2 selector: matchLabels: app: nfs-test template: metadata: labels: app: nfs-test spec: containers: - name: app image: busybox command: [/bin/sh, -c, while true; do echo $(date) /data/out.txt; sleep 5; done] volumeMounts: - name: nfs-vol mountPath: /data volumes: - name: nfs-vol persistentVolumeClaim: claimName: test-pvc这个Deployment跑两个副本每个副本每5秒往/data/out.txt追加一行时间戳。因为两个副本挂的是同一个NFS目录所以它们会往同一个文件里写。等一会儿进Pod里看kubectl exec -it pod-name -- cat /data/out.txt应该能看到时间戳在不断增加而且两个副本写入的内容混在一起。这就验证了NFS的ReadWriteMany能力。如果只有一个副本能写、另一个报权限错误那多半是NFS服务端导出配置里root_squash没关或者目录权限不对。4.3 删除PVC时数据到底去哪了这是很多人容易踩坑的地方。前面StorageClass里配了reclaimPolicy: Delete和archiveOnDelete: true。当你删除PVC时PV会被删除但NFS目录不会被真删而是被重命名为archived-原目录名。你可以到NFS服务端确认ls /data/nfs/k8s/ # 会看到 archived-default-test-pvc-pvc-xxxx 这样的目录如果archiveOnDelete设为false那目录就直接被删了数据找不回来。所以生产环境务必开启归档。另外如果reclaimPolicy设为RetainPV会变成Released状态目录保留原名但PV不能被新PVC复用需要管理员手工清理。三种组合的行为差异挺大建议在测试环境都试一遍心里有数。还有一个细节删除PVC时如果还有Pod在挂载使用PVC会卡在Terminating状态直到Pod被删掉。这是K8S的保护机制防止数据正在写入时被强制卸载。所以下线服务时先删Pod再删PVC顺序别搞反。5. 那些让我熬夜排查的坑与应对方案5.1 PVC一直Pending的几种典型原因PVC卡在Pending是最常见的故障。排查思路要按顺序来先看StorageClass名字对不对kubectl get pvc name -o yaml看storageClassName字段如果为空或者拼错Provisioner根本不会理它。再看Provisioner Pod是否正常运行kubectl logs看有没有报错。常见错误包括连不上NFS服务端、创建目录权限不足、RBAC权限不够。连不上NFS服务端的情况日志里会显示mount failed或者connection timed out。这时候先在Provisioner Pod里手动挂载测试kubectl exec进去mount -t nfs server:/path /mnt看报什么错。如果是端口不通检查防火墙和端口固定配置如果是权限拒绝检查/etc/exports里的客户端网段限制。权限不足的报错通常是mkdir permission denied。这是因为NFS服务端做了root_squash而Provisioner以root身份创建目录被映射成了nobody。解决办法是在exports里加no_root_squash或者把导出目录权限放开。我一般测试环境直接chmod 777加no_root_squash生产环境再收紧。5.2 挂载成功但写入报Read-only file system这个坑我踩过好几次。Pod挂载PVC成功但一写文件就报只读。原因通常是NFS服务端导出目录的权限问题或者exports配置里写成了ro。检查/etc/exports确认是rw不是ro。另外如果NFS服务端磁盘满了或者文件系统变成只读比如磁盘故障也会导致客户端写入失败。用df -h和mount命令确认服务端磁盘状态。还有一种情况是SELinux在捣乱。某些发行版默认开启SELinux会阻止NFS客户端写入。临时用setenforce 0测试如果好了就说明是SELinux策略问题需要配置相应的布尔值或者策略规则。生产环境不建议直接关SELinux而是加规则放行。5.3 Provisioner Pod反复重启的排查Provisioner Pod如果一直CrashLoopBackOff先看日志。常见原因是镜像拉取失败国内环境拉registry.k8s.io的镜像可能超时需要提前把镜像同步到私有仓库或者用镜像加速。另一个原因是RBAC配置不全Provisioner没有权限监听PVC和创建PV日志里会报forbidden。检查ClusterRole里是否包含了persistentvolumes的create、delete、list、watch权限以及persistentvolumeclaims的get、list、watch、update权限。还有一个隐蔽的问题Provisioner的Deployment里挂载NFS卷时如果NFS服务端地址写错或者网络不通Pod会一直卡在ContainerCreating。用kubectl describe pod看Events会显示FailedMount。这时候回到NFS服务端确认服务是否正常网络是否可达。5.4 多副本并发写入的数据一致性问题NFS本身提供的是最终一致性不是强一致性。多个Pod同时往同一个文件追加内容在低并发下一般没问题但高并发时可能出现内容交错或者部分丢失。如果业务对数据一致性要求高建议每个Pod写自己的独立文件而不是共享同一个文件。比如用Pod名字或者IP作为文件名后缀避免写冲突。另外NFS的锁机制在某些场景下不太可靠尤其是跨多个NFS客户端做文件锁时。如果应用依赖文件锁做互斥最好换成基于数据库或者Redis的分布式锁别依赖NFS锁。这个坑在跑一些老式应用时特别容易遇到表现是偶尔出现两个进程同时写同一份数据导致文件损坏。6. 生产环境落地时的容量与性能考量6.1 NFS服务端磁盘容量规划与监控因为NFS动态供给不限制单个PVC的实际使用量所以服务端磁盘很容易被某个失控的应用写满。一旦磁盘满所有挂载该NFS的Pod都会写入失败影响面很大。我的做法是给NFS数据盘单独分区并且设置监控告警使用率超过80%就通知。监控可以用Node Exporter采集磁盘指标配合Prometheus和Alertmanager做告警规则。容量规划上先估算所有有状态服务的总存储需求然后留至少30%的余量。比如预计总共需要500G那就准备至少700G的磁盘。如果增长快可以考虑用LVM做动态扩容或者直接上分布式存储方案替代NFS。但分布式存储运维复杂度高小团队用NFS加监控就够了。6.2 提升NFS读写性能的几个实用参数NFS挂载参数对性能影响很大。K8S的NFS Volume默认挂载参数比较保守可以在StorageClass的mountOptions里调整mountOptions: - nfsvers4.1 - rsize1048576 - wsize1048576 - hard - timeo600 - retrans2 - noresvportrsize和wsize设成1M可以提升大文件传输吞吐。nfsvers4.1比3版本在并发和安全性上更好。hard表示挂载操作会一直重试而不是报错返回避免网络抖动导致应用看到IO错误。noresvport让客户端使用非保留端口某些NFS服务端配置下必须加这个才能挂载成功。不过这些参数不是万能的得根据实际网络和负载调。我一般先用默认参数跑一轮基准测试记录IOPS和吞吐然后逐项调整对比。用fio或者dd做简单测试就行别搞太复杂。6.3 NFS服务端高可用与数据备份思路NFS服务端是单点挂了整个存储层就不可用。提升可用性有两个方向一是用DRBD加Keepalived做双机热备主备切换时VIP漂移客户端重新挂载二是直接用云厂商的NAS服务底层做了高可用省心但成本高。小团队如果预算有限双机热备方案比较现实但切换过程中会有短暂中断应用要能容忍。数据备份方面NFS服务端上的数据目录要定期备份到另一台机器或者对象存储。可以用rsync做增量同步或者用restic、borg这类工具做去重加密备份。备份策略根据数据重要程度定核心数据每天全备加每小时增量非核心数据每天一次就行。备份完一定要做恢复演练不然真出事的时候发现备份不能用就尴尬了。7. 从静态PV到动态供给的迁移经验7.1 存量静态PV如何平滑过渡如果集群里已经有一批静态PV在用想迁移到动态供给不能直接删了重建得考虑数据迁移。我的做法是先部署好Provisioner和StorageClass新业务一律用动态PVC。存量静态PV继续保留等对应服务下次发版或者维护窗口时再逐步迁移。迁移单个服务时先把老PV里的数据用rsync或者cp拷到新创建的动态PVC挂载目录里然后改Deployment的PVC引用重启Pod验证数据完整。确认没问题后再删掉老PVC和PV。整个过程可以在线做但最好选业务低峰期避免数据拷贝影响性能。7.2 多命名空间下的存储隔离动态供给默认所有命名空间共用一个StorageClassPVC名字在不同命名空间可以重复Provisioner创建的目录会带上命名空间前缀所以不会冲突。但如果想给不同团队分配不同的NFS导出目录可以创建多个StorageClass每个指向不同的NFS路径。比如给开发团队用/data/nfs/dev给测试团队用/data/nfs/test通过StorageClass名字区分。RBAC层面也可以做限制让某个命名空间只能使用特定的StorageClass。不过这需要额外配置K8S原生不直接支持StorageClass级别的命名空间隔离得靠准入控制器或者策略引擎来实现。小规模场景下靠命名规范和人工约定就够了不必过度设计。7.3 日常运维中值得固化的几个检查项跑了一段时间后我总结了几条日常检查清单每周花几分钟过一遍能避免大部分突发故障。第一确认Provisioner Pod状态正常没有重启记录。第二检查NFS服务端磁盘使用率超过阈值就清理归档目录。第三看有没有长期处于Pending或者Terminating状态的PVC及时处理。第四验证备份任务是否按时完成备份文件大小是否正常。这些检查项可以写成脚本定时跑输出结果发到运维群或者邮件。自动化程度不用太高关键是形成习惯。存储这东西平时不出事一出事就是大事提前花点时间巡检比事后救火划算得多。8. 写在最后的一点个人体会这套NFS动态供给方案我在好几个集群里用过从测试环境到中小规模生产环境都跑得挺稳。它最大的价值是把存储分配这件事从手工操作变成了自助服务开发同学提交PVC就能用运维不用再当人肉PV工厂。但前提是NFS服务端要配好、监控要到位、备份要可靠这三样缺一个都可能埋雷。另外提醒一句NFS终究是网络存储延迟比本地盘高一个数量级。对IO延迟敏感的应用比如高频交易或者实时数据库还是老老实实用本地SSD或者专业块存储。NFS适合的是那些对延迟不敏感、但需要共享和持久化的场景。选型的时候想清楚业务需求别为了图省事把不合适的负载硬塞进来后面性能问题会让你更头疼。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表