ARTICLE DETAIL

资讯详情

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

Kubernetes中用NFS实现动态存储供应的落地实践

Kubernetes中用NFS实现动态存储供应的落地实践 1. 项目概述为什么在Kubernetes里用NFS做动态存储不是“凑合”而是务实选择你刚在生产环境里部署完一套微服务Pod跑得挺稳日志也进了ELK结果第二天业务方甩来一条消息“用户上传的Excel模板不见了。”——查了一圈发现某个处理文件的Job Pod重启后挂载的临时目录清空了。这时候你才意识到容器天生无状态但业务数据必须有落脚地。而Kubernetes原生的emptyDir和hostPath根本扛不住滚动更新、跨节点调度、权限隔离这三座大山。于是你开始翻文档看到PersistentVolumePV和PersistentVolumeClaimPVC这两个词再往下翻又撞上StorageClass——它像一道闸门把底层存储细节和上层应用隔开。而NFS就是这道闸门背后最常被选中的“水厂”。NFS不是新技术但它在Kubernetes生态里扮演的角色非常特殊它不追求极致IOPS也不标榜超低延迟而是用极低的运维门槛、天然的多读多写支持、以及对POSIX语义的完整兼容成为中小规模集群里最可靠的共享存储底座。尤其当你面对的是日志归集、配置热更新、CI/CD中间产物缓存、或者多个Pod需要协同读写同一份报表模板这类场景时NFS提供的“一个路径处处可挂”能力比块存储如iSCSI或对象存储如S3更直接、更省心。我带过的几个项目里某高校实验室用NFS支撑20个Jupyter Notebook实例共享课程数据集某电商中台用NFS做灰度发布时的配置快照池都跑了一年半没出过存储层故障。这不是因为NFS多先进而是因为它足够简单、足够透明、足够可控——你ssh进NFS服务器ls -l一眼就能看清谁在读、谁在写、空间还剩多少你改一行/etc/exports加个sync参数就能立刻感知到性能变化。这种“所见即所得”的掌控感在云原生世界里反而成了稀缺品。所以“Kubernetes: 利用NFS动态提供后端存储”这个标题说的不是一项炫技工程而是一套经过千锤百炼的落地方法论它把NFS从传统Linux服务器上的“配角”升级成Kubernetes调度体系里的“编排友好型存储资源”。核心在于“动态提供”四个字——你不再需要提前在NFS服务器上手工创建好每个目录、再一个个定义PV对象而是通过StorageClass声明“我要什么样的NFS卷”由nfs-subdir-external-provisioner这个外部供应器自动完成目录创建、PV绑定、权限设置一整套动作。整个过程对开发者完全透明他们只管写pvc.spec.resources.requests.storage: 5Gi剩下的交给系统。这种解耦让存储管理真正回归到“按需申请、用完释放”的云原生本意而不是变成运维同学每周定时巡检df -h的体力活。2. 整体架构设计与方案选型逻辑为什么是nfs-subdir-external-provisioner而不是其他2.1 三种NFS集成路径的硬核对比在Kubernetes里对接NFS业界其实有三条主流路径每条背后都是不同的权衡取舍纯静态PV方式管理员手动在NFS服务器上创建目录如/data/app-logs再在K8s里定义对应PVnfs.path: /data/app-logs最后由PVC去绑定。这种方式最原始但胜在绝对可控。我见过某金融后台系统坚持用这套方案理由很实在审计要求所有存储路径必须提前审批、留痕、备案自动创建目录等于绕过流程。但代价也很明显——每次上线新服务都要走工单、等审批、人工执行平均耗时2天。当集群里PVC数量超过50个光是PV YAML文件的版本管理就让人头皮发麻。FlexVolume或CSI插件直连比如早期用nfs-clientFlexVolume或者现在更主流的nfs-csi-driver。它们让Kubelet能直接和NFS服务器通信跳过PV/PVC抽象层。好处是性能略优少一层转发但问题更致命FlexVolume已被K8s官方弃用而CSI驱动虽然功能全却要求你在每个Node上安装二进制、配置RBAC、还要处理NFS客户端内核模块兼容性比如CentOS 7默认的nfs-utils版本太老挂载NFSv4.1会报错。去年帮某物流平台排查一个“部分Node挂载失败”的问题折腾三天才发现是某批新采购的服务器BIOS里禁用了CONFIG_NFS_V4_1选项。External Provisioner动态供应也就是本项目采用的nfs-subdir-external-provisioner方案。它本质是个独立的Deployment以Sidecar模式运行在集群里监听PVC创建事件然后调用NFS服务器的mkdir和exportfs命令生成新目录并反向创建PV对象。它的优势不是性能而是抽象干净、依赖极简、调试直观。整个供应器只依赖一个ServiceAccount权限、一个NFS Server地址、一个基础路径如/data/k8s-volumes没有Node级组件没有内核模块要求升级只需替换镜像。更重要的是它的日志就是你的操作记录——kubectl logs -f nfs-provisioner-xxx里能看到每一行msg:creating new volume后面跟着完整的mkdir命令和返回码故障定位效率远超CSI驱动那种“挂载失败但日志只显示rpc error”的黑盒体验。提示nfs-subdir-external-provisioner并非K8s官方维护而是由社区项目kubernetes-sigs/nfs-subdir-external-provisioner孵化。它之所以成为事实标准是因为它精准卡在了“够用”和“不过度设计”的黄金分割点上——不支持快照、不支持在线扩容、不支持多租户配额但正因如此代码只有不到2000行Go漏洞极少升级风险极低。如果你的业务不需要这些高级特性强行上CSI反而增加复杂度。2.2 Provisioner核心组件拆解它到底在做什么nfs-subdir-external-provisioner看似只是一个Pod但内部逻辑非常精巧。我们把它拆成三个关键角色来看Controller Manager这是主循环线程持续调用K8s API Server的Watch接口监听PersistentVolumeClaim资源的ADDED事件。一旦发现新PVC就提取其spec.storageClassName匹配到对应的StorageClass对象再读取该StorageClass里定义的provisioner字段必须是k8s-sigs.io/nfs-subdir-external-provisioner和parameters如archiveOnDelete: false。这一步决定了“谁来供应”和“按什么规则供应”。Volume Provisioner拿到PVC元数据后它会生成一个唯一卷名格式为namespace-pvc-name-random-string然后拼接出NFS服务器上的完整路径如/data/k8s-volumes/default-myapp-pvc-abc123。接着它通过exec.Command(ssh, ...)或exec.Command(mount, ...)方式取决于配置连接NFS服务器执行mkdir -p创建目录并调用exportfs -i -o rw,sync,no_root_squash nfs-server:/data/k8s-volumes/default-myapp-pvc-abc123导出该路径。注意这里no_root_squash是关键——它允许K8s集群内的root用户即Pod以root身份运行时在NFS目录里拥有真实root权限否则会出现Permission denied错误。很多初学者卡在这一步以为是K8s权限问题其实是NFS导出参数没配对。PV Creator目录创建成功后Provisioner会构造一个PersistentVolume对象填入spec.nfs.serverNFS服务器IP、spec.nfs.path刚创建的路径、spec.capacity.storage等于PVC申请的大小、spec.accessModes继承自PVC最后调用API Server的Create接口提交。此时K8s Scheduler会自动将这个PV和PVC绑定后续Pod挂载时Kubelet就按标准流程发起NFS挂载请求。这个流程里最值得玩味的设计是Provisioner本身不参与实际的数据读写它只负责“造房子”和“发钥匙”。真正的挂载、卸载、IO转发全部由Kubelet内置的NFS客户端完成。这就意味着你完全可以用kubectl delete pvc my-pvc删除一个PVCProvisioner会自动清理NFS服务器上的对应目录如果archiveOnDelete设为false而整个过程对正在运行的Pod零影响——因为Pod挂载的是NFS路径不是Provisioner的Pod。3. 核心细节解析与实操要点从NFS服务器配置到StorageClass定义3.1 NFS服务器端安全与性能的平衡术NFS服务器不是随便装个nfs-kernel-server就能用的它需要针对K8s场景做三处关键加固目录结构规划不要把所有动态卷都塞进/export根目录下。我建议分三级路径/data/k8s-volumes/cluster-name/namespace。比如/data/k8s-volumes/prod-cluster/default。这样做的好处是1exportfs导出时可以精确控制每个命名空间的权限/data/k8s-volumes/prod-cluster/default *(rw,sync,no_root_squash)2运维巡检时du -sh /data/k8s-volumes/prod-cluster/*一眼看出哪个命名空间占空间最多3未来要做存储配额可以直接在namespace目录上用xfs_quota限制。exports配置的魔鬼细节/etc/exports里这一行看似简单实则暗藏玄机/data/k8s-volumes/prod-cluster *(rw,sync,no_root_squash,subtree_check,fsid0)sync强制同步写入避免NFS服务器崩溃导致数据丢失。虽然比async慢10%~15%但对日志、配置类场景数据一致性远比吞吐量重要。no_root_squash必须开启K8s里绝大多数容器以UID 0root运行如果关闭此选项NFS会把root映射成nobody用户导致Pod内touch /mnt/test.txt失败。但这也带来风险——恶意Pod可能通过chown root:root提权。缓解方案是在Provisioner的parameters里加上mountOptions: [nfsvers4.1,hard,timeo600]让挂载更健壮同时在NFS服务器上启用rpcbind的防火墙白名单只允许K8s Node IP段访问。subtree_check开启子树检查防止客户端绕过导出路径访问上级目录。虽然会略微降低性能但安全收益远大于成本。fsid0为整个导出树指定唯一文件系统ID避免NFS客户端混淆多个导出路径。内核参数调优在NFS服务器上执行以下命令解决高并发挂载时的Stale file handle错误# 增加NFS服务器的TCP连接队列长度 echo net.core.somaxconn 65535 /etc/sysctl.conf # 提高NFS服务器处理RPC请求的线程数Ubuntu/Debian echo RPCBIND_OPTIONS--manage-gids /etc/default/rpcbind systemctl restart rpcbind # 对于RHEL/CentOS修改/etc/sysconfig/nfs echo RPCNFSDCOUNT32 /etc/sysconfig/nfs systemctl restart nfs-server这些参数不是凭空写的。去年某视频平台在压测时100个Pod同时申请PVC前20个成功后80个全卡在Pending状态。抓包发现是rpcbind的连接队列溢出ss -s | grep tcp:显示orphan连接数爆表。调大somaxconn后问题消失。3.2 Kubernetes侧StorageClass与Provisioner部署的黄金参数StorageClass是整个动态供应的“宪法”它的每一个字段都直接影响PVC的行为apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须和Provisioner Deployment里spec.template.spec.containers[0].args[0]一致 parameters: archiveOnDelete: false # 关键设为true会在删除PVC时重命名目录为archived-name设为false则彻底删除 onDelete: delete # 配合archiveOnDelete决定PV被回收时的动作 pathPattern: ${.PVC.namespace}/${.PVC.name} # 动态生成NFS路径的模板${.PVC.namespace}会被替换成PVC所在命名空间 reclaimPolicy: Delete # PV回收策略必须是Delete或Retain不能是Recycle已废弃 volumeBindingMode: Immediate # 立即绑定PVC创建后立刻触发Provisioner也可设为WaitForFirstConsumer延迟到Pod创建时再绑定适合需要拓扑感知的场景其中pathPattern参数最容易被忽略但它决定了你的NFS目录结构是否可管理。默认值是${.PVC.namespace}/${.PVC.name}这意味着PVCdefault/my-app-logs会映射到NFS路径/data/k8s-volumes/prod-cluster/default/my-app-logs。但如果你的业务要求所有日志卷必须放在/logs子目录下可以改成logs/${.PVC.namespace}-${.PVC.name}。注意pathPattern里不能出现..或绝对路径符号Provisioner会校验并拒绝非法模式。Provisioner Deployment的部署也有讲究。官方YAML里args部分通常长这样args: - -provisionercluster.local/nfs-client这里的cluster.local是K8s集群的域名后缀必须和你集群的实际clusterDomain一致可通过kubectl get cm -n kube-system coredns -o yaml查看。如果填错Provisioner会一直报failed to get server version错误因为连不上API Server。另外resources.requests.memory建议至少设为128Mi否则在高负载集群里Provisioner Pod可能被OOMKilled导致PVC创建失败。注意Provisioner的ServiceAccount必须有足够权限。最小化RBAC清单如下apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-provisioner-runner rules: - apiGroups: [] resources: [persistentvolumes] verbs: [get, list, watch, create, delete] - apiGroups: [] resources: [persistentvolumeclaims] verbs: [get, list, watch, update] - apiGroups: [storage.k8s.io] resources: [storageclasses] verbs: [get, list, watch] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: run-nfs-provisioner subjects: - kind: ServiceAccount name: nfs-provisioner namespace: default roleRef: kind: ClusterRole name: nfs-provisioner-runner apiGroup: rbac.authorization.k8s.io3.3 PVC与Pod挂载那些文档里不会写的细节定义一个PVC看似简单但有几个坑必须避开apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-app-logs namespace: default spec: accessModes: - ReadWriteMany # 关键NFS必须用RWX不能用ReadWriteOnceRWO resources: requests: storage: 10Gi storageClassName: nfs-client # 必须和上面定义的StorageClass name一致accessModes必须是ReadWriteManyRWX这是NFS区别于块存储的核心特征。ReadWriteOnceRWO意味着卷只能被一个Node挂载而NFS服务器允许多个客户端同时挂载同一路径所以K8s里NFS PV的accessModes永远是RWX。如果你误写了RWOPVC会一直处于Pending状态kubectl describe pvc会显示no persistent volumes available for this claim and no storage class is set——这不是没PV而是PV的accessModes和PVC不匹配。存储大小只是“软限制”NFS本身不支持配额除非你额外配xfs_quota所以requests.storage: 10Gi只是告诉Provisioner“请给我建个目录”并不阻止Pod往里面写100Gi数据。真正的空间控制得靠NFS服务器端的quota或监控告警。我习惯在NFS服务器上部署node_exporter用Prometheus采集node_filesystem_avail_bytes{mountpoint~/data/k8s-volumes/.*}指标当可用空间低于15%时触发企业微信告警。Pod挂载PVC时还有一个隐藏技巧利用subPath实现单个PVC多用途。比如一个10Gi的PVC你想让Web容器写日志到/var/log/nginx让Sidecar容器收集日志到/logs可以这样写volumeMounts: - name: app-storage mountPath: /var/log/nginx subPath: nginx-logs - name: app-storage mountPath: /logs subPath: collected-logs volumes: - name: app-storage persistentVolumeClaim: claimName: my-app-logs这样两个容器实际共享同一个NFS目录下的不同子目录既节省PV数量又避免权限冲突。subPath的另一个妙用是当PVC里已有历史数据而你只想让Pod读取其中某个子目录时用subPath可以精准定位不用把整个卷挂载进来污染容器根文件系统。4. 实操过程与核心环节实现从零部署到验证全流程4.1 环境准备与前置检查清单在动手之前务必完成以下五项检查否则90%的失败都源于这里NFS服务器连通性验证从任意一个K8s Node上执行# 检查NFS服务是否响应 showmount -e nfs-server-ip # 尝试手动挂载模拟Kubelet行为 mkdir -p /mnt/test-nfs mount -t nfs4 -o vers4.1,hard,intr,rsize1048576,wsize1048576 nfs-server-ip:/data/k8s-volumes/prod-cluster /mnt/test-nfs echo test /mnt/test-nfs/hello.txt cat /mnt/test-nfs/hello.txt umount /mnt/test-nfs如果showmount超时检查NFS服务器防火墙ufw status或firewall-cmd --list-all是否放行2049/tcp和111/tcp端口如果mount失败且报Operation not permitted检查Node内核是否加载nfs模块lsmod | grep nfs。K8s集群版本确认nfs-subdir-external-provisionerv4.0.2要求K8s 1.20。执行kubectl version --short如果Server版本低于1.20必须降级使用v3.x分支。Provisioner镜像选择官方镜像registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2在国内拉取极慢。我推荐用阿里云镜像加速kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/class.yaml # 修改deployment.yaml将image改为 # image: registry.cn-hangzhou.aliyuncs.com/google_containers/nfs-subdir-external-provisioner:v4.0.2Namespace隔离Provisioner默认部署在default命名空间。生产环境强烈建议新建storage命名空间并修改所有YAML里的namespace: storage避免和业务Pod混在一起。NFS服务器磁盘健康执行smartctl -a /dev/sdb假设/data在sdb上检查Reallocated_Sector_Ct和Current_Pending_Sector是否为0。曾有个案例NFS服务器硬盘有坏道导致Provisioner创建目录时偶尔失败错误日志里只显示mkdir: cannot create directory排查三天才发现是硬件问题。4.2 分步部署手把手带你走通全流程第一步部署Provisioner含RBAC保存以下内容为provisioner-deploy.yaml--- apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner namespace: storage --- # RBAC内容同3.2节最小化RBAC清单 --- apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner namespace: storage spec: replicas: 1 selector: matchLabels: app: nfs-provisioner strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAccount: nfs-provisioner containers: - name: nfs-provisioner image: registry.cn-hangzhou.aliyuncs.com/google_containers/nfs-subdir-external-provisioner:v4.0.2 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 192.168.1.100 # 替换为你的NFS服务器IP - name: NFS_PATH value: /data/k8s-volumes/prod-cluster # 替换为你的基础路径 volumes: - name: nfs-client-root nfs: server: 192.168.1.100 path: /data/k8s-volumes/prod-cluster执行kubectl apply -f provisioner-deploy.yaml。等待Pod就绪kubectl get pods -n storage -l appnfs-provisioner # 应看到 STATUSRunningREADY1/1 kubectl logs -n storage deploy/nfs-provisioner | head -20 # 正常日志应包含 Starting NFS Subdir External Provisioner第二步创建StorageClass保存为storageclass.yamlapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client annotations: storageclass.kubernetes.io/is-default-class: true # 设为默认省去PVC里写storageClassName provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: false pathPattern: ${.PVC.namespace}/${.PVC.name} reclaimPolicy: Delete volumeBindingMode: Immediate执行kubectl apply -f storageclass.yaml。验证kubectl get sc # 输出应包含 nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false第三步创建测试PVC与Pod保存为test-app.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi --- apiVersion: v1 kind: Pod metadata: name: test-pod namespace: default spec: containers: - name: nginx image: nginx:alpine volumeMounts: - name: nfs-volume mountPath: /usr/share/nginx/html volumes: - name: nfs-volume persistentVolumeClaim: claimName: test-pvc执行kubectl apply -f test-app.yaml。观察状态kubectl get pvc -n default # 应看到 STATUSBoundVOLUMEpvc-xxxxxx自动生成的PV名 kubectl get pv # 找到对应PV检查SPEC.NFS.PATH是否为/data/k8s-volumes/prod-cluster/default/test-pvc-xxxxxx kubectl exec -it test-pod -- sh -c echo Hello from NFS /usr/share/nginx/html/index.html # 在Node上检查NFS服务器ls -l /data/k8s-volumes/prod-cluster/default/test-pvc-xxxxxx/ # 应看到index.html文件且属主为nobody:nogroupNFS默认映射第四步压力测试与边界验证创建10个并发PVC验证Provisioner稳定性for i in {1..10}; do cat EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: stress-pvc-$i namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 500Mi storageClassName: nfs-client EOF done等待2分钟执行kubectl get pvc -n default | grep stress-pvc | wc -l # 应输出10 kubectl get pv | grep stress-pvc | wc -l # 应输出10 # 登录NFS服务器检查目录数量 ls -l /data/k8s-volumes/prod-cluster/default/ | grep stress-pvc | wc -l # 应输出10如果某个PVC卡在Pending立即看Provisioner日志kubectl logs -n storage deploy/nfs-provisioner --since1m | grep -A5 -B5 stress-pvc-5 # 常见错误mkdir: cannot create directory ‘/data/k8s-volumes/prod-cluster/default/stress-pvc-5’: Permission denied # 解决检查NFS服务器上/data/k8s-volumes/prod-cluster目录权限应为777或755且属主为nfsnobody4.3 生产级增强监控、备份与故障演练动态供应只是起点生产环境必须配套三件事监控Provisioner健康度在Prometheus里添加以下指标抓取- job_name: nfs-provisioner static_configs: - targets: [nfs-provisioner-service:8080] # Provisioner自带/metrics端点 metrics_path: /metrics关键告警规则nfs_provisioner_pvc_create_total{statuserror} 0PVC创建失败立即告警rate(nfs_provisioner_pvc_create_duration_seconds_sum[5m]) / rate(nfs_provisioner_pvc_create_duration_seconds_count[5m]) 30平均创建耗时超30秒说明NFS服务器IO瓶颈NFS目录自动备份在NFS服务器上配置rsync定时任务每天凌晨2点同步/data/k8s-volumes/prod-cluster到异地备份机# /etc/cron.d/nfs-backup 0 2 * * * root rsync -avz --delete /data/k8s-volumes/prod-cluster/ backup-userbackup-server:/backup/nfs-prod/注意--delete参数确保备份目录和源目录完全一致但首次运行前务必手动验证rsync -ndry-run。故障演练脚本编写一个failover-test.sh模拟NFS服务器宕机# 1. 记录当前PVC状态 kubectl get pvc -A before-failover.txt # 2. 在NFS服务器上停服务 systemctl stop nfs-server # 3. 等待30秒检查Pod是否受影响 kubectl get pods -A | grep Running | wc -l # 应基本不变NFS客户端有重试机制 # 4. 恢复服务 systemctl start nfs-server # 5. 验证Pod能否继续IO kubectl exec test-pod -- sh -c date /usr/share/nginx/html/timestamp.log实测结果NFS客户端默认重试1000次间隔从0.1秒指数增长总超时约3分钟。这意味着只要NFS服务器在3分钟内恢复业务Pod完全无感。这个数据比任何文档都管用。5. 常见问题与排查技巧实录那些踩过的坑我都帮你记下来了5.1 PVC长期Pending不只是网络问题这是最高频的故障现象是kubectl get pvc显示Pendingkubectl describe pvc里Events为空或只有waiting for a volume to be created。别急着查网络先按顺序排查排查步骤检查命令预期结果常见原因1. Provisioner Pod是否Runningkubectl get pods -n storage -l appnfs-provisionerSTATUSRunningPod CrashLoopBackOff通常是RBAC权限不足或NFS服务器地址填错2. Provisioner日志是否有错误kubectl logs -n storage deploy/nfs-provisioner --tail50无ERROR或panic日志里出现failed to create volume: mkdir: permission denied说明NFS服务器目录权限不对3. StorageClass是否存在且名称匹配kubectl get sc输出包含nfs-clientPVC里storageClassName拼写错误如写成nfs-client-14. PVC的accessModes是否为RWXkubectl get pvc test-pvc -o yaml | grep accessModesaccessModes: [ReadWriteMany]误写为ReadWriteOnceK8s无法匹配PV5. NFS服务器是否能被Provisioner Pod访问kubectl exec -n storage deploy/nfs-provisioner -- ping -c 3 192.168.1.10064 bytes from ...Provisioner Pod所在Node的网络策略NetworkPolicy阻止了出向流量我遇到过最诡异的一次所有检查都通过但PVC就是Pending。最后发现是NFS服务器的/etc/exports里用了主机名nfs-server.local代替IP而Provisioner Pod的/etc/resolv.conf里DNS服务器配置错误导致showmount -e nfs-server.local超时。解决方案很简单/etc/exports里一律用IP永不写主机名。5.2 Pod挂载后Permission DeniedNFS权限的迷宫Pod里执行touch /mnt/test.txt报Permission denied90%的情况是NFS的root_squash惹的祸。但解决方案不止一种方案A推荐NFS服务器端关闭root_squash修改/etc/exports把*(rw,sync)改成*(rw,sync,no_root_squash)然后exportfs -ra重载。这是最直接的解法适用于内网可信环境。方案BPod内以非root用户运行在Pod spec里加securityContext: runAsUser: 1001 runAsGroup: 1001 fsGroup: 1001同时确保NFS服务器上/data/k8s-volumes/prod-cluster目录属主为1001:1001权限为775。这样Pod以UID 1001运行NFS会将其映射为真实UID 1001自然有写权限。方案CProvisioner自动修复权限高级在Provisioner的parameters里加parameters: archiveOnDelete: false pathPattern: ${.PVC.namespace}/${.PVC.name} onDemand: chmod 777 # Provisioner创建目录后自动执行chmod注意onDemand参数需要Provisioner v4.0.0且必须确保NFS服务器上/data/k8s-volumes/prod-cluster目录的父目录有setgid位chmod gs /data/k8s-volumes/prod-cluster否则新目录的属组不会继承。5.3 动态卷被意外删除如何找回“消失”的数据当archiveOnDelete: true时删除PVC会重命名目录为archived-default-test-pvc-xxxxxx。但如果误设为false数据就真没了。这时候别慌NFS服务器的/data/k8s-volumes/prod-cluster目录下可能还有线索**检查.nfs*临时
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表