☰
深入解析 NFS-exporter 容器:在 Kubernetes 中构建自带文件的最小 NFS 存储导出器
2026/10/9 5:15:46 网站建设 项目流程

【免费下载链接】charts

⚠️(OBSOLETE) Curated applications for Kubernetes

项目地址:https://gitcode.com/gh_mirrors/chart/charts
点击查看免费下载

导读

nfs-data是一个以"容器内导出共享目录并预置测试文件"为核心的最小 NFS 导出器实现:它在容器中通过 NFS 协议导出/exports目录,并预置一个index.html文件供客户端挂载后读取验证。本文以该组件的 README 为核心,结合同仓库中它的Dockerfile、启动脚本run_nfs.sh以及上层nfsHelm Chart(stable/spark-history-server/charts/nfs)的模板源码,完整讲解该容器的构建方式、NFSv3 启动原理、镜像使用方式,以及它在 Kubernetes PV/PVC 体系中扮演的角色。读完本文,你将掌握"如何在容器中安全运行 NFS 服务、为何只开放 NFSv3、以及如何利用它快速验证一个可读写的 NFS 共享"。

容器核心定位:导出 /exports 并携带一个测试文件

组件文档 stable/spark-history-server/charts/nfs/nfs-data/README.md 对该容器给出了非常精炼的定义:

该容器通过 NFS 导出/exports目录,其中包含index.html。它基于上游../exports示例改造而来。由于部分 Linux 内核在容器中运行 NFSv4 守护进程存在已知问题,本容器只开放 NFSv3。镜像以gcr.io/google-samples/nfs-server的名称对外提供。

这份描述虽然简短,却包含了三个关键技术决策,值得逐一展开:

  1. 导出的不是空目录,而是预置测试文件的共享:/exports/index.html是客户端挂载后用于连通性验证的"探针文件"。
  2. 主动放弃 NFSv4,仅开放 NFSv3:这是对容器化 NFS 场景中"内核与容器环境兼容性"的现实妥协,也是本组件最具辨识度的技术特征。
  3. 镜像发布名:gcr.io/google-samples/nfs-server,说明它来自 Kubernetes 官方 samples 项目,属于"演示/测试用途"的镜像,而非生产级存储方案。

镜像构建:从 Dockerfile 看容器的组装细节

容器镜像的构建定义位于 stable/spark-history-server/charts/nfs/nfs-data/Dockerfile,其内容与 README 的描述一一对应:

FROM centos RUN yum -y install /usr/bin/ps nfs-utils && yum clean all RUN mkdir -p /exports ADD run_nfs.sh /usr/local/bin/ ADD index.html /tmp/index.html RUN chmod 644 /tmp/index.html # expose mountd 20048/tcp and nfsd 2049/tcp and rpcbind 111/tcp EXPOSE 2049/tcp 20048/tcp 111/tcp 111/udp ENTRYPOINT ["/usr/local/bin/run_nfs.sh", "/exports"]

逐条拆解该构建过程:

构建指令作用与 README 的对应关系
FROM centos以 CentOS 为基础镜像,提供 glibc 与 yum 包管理基础运行环境
yum -y install /usr/bin/ps nfs-utils安装ps(进程工具,供pidof/状态检查使用)与nfs-utils(提供 rpcbind、rpc.mountd、rpc.nfsd、exportfs 等 NFS 服务端工具链),随后yum clean all清理缓存以缩小镜像提供 NFS 服务端完整运行能力
mkdir -p /exports预创建导出根目录对应 README 中"导出 /exports"
ADD run_nfs.sh /usr/local/bin/将启动脚本放入 PATH,作为容器 ENTRYPOINT对应 README 中"基于 ../exports 的启动逻辑"
ADD index.html /tmp/index.html+chmod 644暂存测试文件到 /tmp,运行期由脚本复制到 /exports对应 README 中"index.html 测试文件"
EXPOSE 2049/tcp 20048/tcp 111/tcp 111/udp声明 nfsd、mountd、rpcbind 三类端口对应 NFSv3 服务端口布局
ENTRYPOINT ["/usr/local/bin/run_nfs.sh", "/exports"]以/exports为参数启动脚本镜像启动即导出目录

从构建顺序可以看出设计意图:测试文件先落在/tmp,由运行脚本在真正导出时拷贝到导出目录,这样镜像层不直接写入导出数据,运行时目录内容由脚本统一管理,与 README 中"exports 目录携带 index.html"的描述完全吻合。

启动脚本剖析:run_nfs.sh 如何"只开放 NFSv3"

容器真正的运行逻辑在 stable/spark-history-server/charts/nfs/nfs-data/run_nfs.sh 中,这是理解"为何只开 NFSv3"的关键源码。脚本分为start、stop与主流程三个部分。

start:启动序列

function start() { # prepare /etc/exports for i in "$@"; do # fsid=0: needed for NFSv4 echo "$i *(rw,fsid=0,insecure,no_root_squash)" >> /etc/exports # move index.html to here /bin/cp /tmp/index.html $i/ chmod 644 $i/index.html echo "Serving $i" done # start rpcbind if it is not started yet /usr/sbin/rpcinfo 127.0.0.1 > /dev/null; s=$? if [ $s -ne 0 ]; then echo "Starting rpcbind" /usr/sbin/rpcbind -w fi mount -t nfsd nfds /proc/fs/nfsd # -N 4.x: disable NFSv4 # -V 3: enable NFSv3 /usr/sbin/rpc.mountd -N 2 -V 3 -N 4 -N 4.1 /usr/sbin/exportfs -r # -G 10 to reduce grace time to 10 seconds (the lowest allowed) /usr/sbin/rpc.nfsd -G 10 -N 2 -V 3 -N 4 -N 4.1 2 /usr/sbin/rpc.statd --no-notify echo "NFS started" }

该序列的关键步骤与参数含义如下:

  1. 写入/etc/exports:对每个参数目录(本例为/exports)写入导出规则*(rw,fsid=0,insecure,no_root_squash):
    • rw:允许读写;
    • fsid=0:为导出根目录固定文件系统 ID(注释说明该选项为 NFSv4 所需,保留它以兼容部分客户端);
    • insecure:允许使用非特权端口(大于 1024)发起挂载请求,容器网络环境中客户端端口不可控,此选项必不可少;
    • no_root_squash:不将 root 用户映射为匿名用户,便于容器内以 root 读写。
  2. 拷贝测试文件:将/tmp/index.html复制到每个导出目录并设置为 644 权限。
  3. 启动 rpcbind:通过rpcinfo探测本机 RPC 服务是否存活,未启动则以-w(热启动、保持 UDP/TCP 绑定)拉起。NFSv3 依赖 RPC 端口映射,这是协议栈的前置条件。
  4. 挂载内核 nfsd 文件系统:mount -t nfsd nfds /proc/fs/nfsd,将内核 NFS 服务器状态挂载到/proc/fs/nfsd,供rpc.nfsd管理线程。
  5. 启动 mountd(禁用 NFSv2/v4):rpc.mountd -N 2 -V 3 -N 4 -N 4.1。-N 2、-N 4、-N 4.1显式禁用 2、4、4.1 三个版本,-V 3明确只启用 NFSv3——这就是 README 中"只开放 NFSv3"的直接源码证据。注释也点明了原因:部分 Linux 内核在容器中运行 NFSv4 守护进程存在问题。
  6. 刷新导出表:exportfs -r让/etc/exports的改动立即生效。
  7. 启动 nfsd 内核线程:rpc.nfsd -G 10 -N 2 -V 3 -N 4 -N 4.1 2:
    • -G 10:将 NFSv3 的宽限期(grace period)缩短到最低允许值 10 秒,加快重启后的挂载恢复;
    • 版本参数与 mountd 保持一致(仅 NFSv3);
    • 末尾的2指定启动 2 个 nfsd 内核线程。
  8. 启动 statd(不发送通知):rpc.statd --no-notify提供 NFS 状态监视服务,--no-notify跳过重启通知,符合演示容器的轻量定位。

stop:优雅退出

function stop() { echo "Stopping NFS" /usr/sbin/rpc.nfsd 0 /usr/sbin/exportfs -au /usr/sbin/exportfs -f kill $( pidof rpc.mountd ) umount /proc/fs/nfsd echo > /etc/exports exit 0 }

退出序列依次完成:关闭全部 nfsd 线程(rpc.nfsd 0)→ 取消导出所有共享(exportfs -au)→ 刷新导出表(exportfs -f)→ 终止 mountd → 卸载 nfsd 状态文件系统 → 清空/etc/exports。

主流程:信号驱动的生命周期

trap stop TERM start "$@" # Ugly hack to do nothing and wait for SIGTERM while true; do sleep 5 done

主流程先注册TERM信号处理器为stop,然后调用start "$@"把 ENTRYPOINT 传入的目录参数(/exports)交给启动逻辑,最后进入一个sleep 5的空转循环等待 SIGTERM。这样 Kubernetes 在停止 Pod 时发送的SIGTERM会被捕获并触发优雅退出,而不是让容器被强制杀死——这是容器化守护进程的标准生命周期管理手法。

在 Kubernetes 中如何消费该镜像:上层 NFS Chart 的完整闭环

虽然nfs-dataREADME 只描述镜像本身,但本仓库将其真正"用起来"的完整示例位于其父目录 stable/spark-history-server/charts/nfs,其中:

  • Chart.yaml 声明这是一个名为nfs、版本0.1.0的 Helm Chart,用途描述为 "A Helm chart for NFS server",源码来源指向 kubernetes/examples 的staging/volumes/nfs示例目录;
  • values.yaml 提供可调参数:
gceStorage: 5Gi # NFS 服务器底层 PVC 的容量(GCE 磁盘自动供应) pvcStorage: 1Mi # 面向业务方的 NFS PVC 请求容量 pvStorage: 1Mi # 面向业务方的 NFS PV 容量 pvName: nfs-pv # NFS 类型 PersistentVolume 名称 pvcName: nfs-pvc # NFS 类型 PersistentVolumeClaim 名称 enableExampleNFS: true # 是否安装演示用 NFS 卷与服务器

Chart 的模板文件完整复刻了"容器导出目录 + PV/PVC 解耦"的架构:

模板文件作用
templates/nfs-server-deployment.yaml以k8s.gcr.io/volume-nfs:0.8镜像创建 Deployment,暴露 nfs(2049)/mountd(20048)/rpcbind(111) 三个端口,securityContext.privileged: true满足 NFS 守护进程挂载内核文件系统的特权要求,并把底层 PVC 挂载到/exports
templates/nfs-server-service.yaml为 NFS 服务器创建 Service,转发 2049/20048/111 三个端口
templates/nfs-server-gce-pv.yaml为 NFS 服务器创建ReadWriteOnce的底层 PVC(默认 5Gi,GCE 自动供应)
templates/nfs-pv.yaml创建ReadWriteMany的 NFS 类型 PV,nfs.server指向{{ .Release.Name }}-nfs.<namespace>.svc.cluster.local,path: "/"即对应导出根目录
templates/nfs-pvc.yaml创建对应的 NFS PVC(nfs-pvc),storageClassName: ""强制静态绑定

这条链路恰好诠释了 nfs-data 容器在 Kubernetes 中的真实用途:

  1. 底层存储:GCE PD 等云磁盘通过 PVC 自动供应;
  2. NFS 服务器:Pod 以特权模式运行 NFS 守护进程,将底层磁盘通过 NFS 导出;
  3. 共享层:NFS 类型的 PV/PVC 以ReadWriteMany能力让多个 Pod 同时读写同一份数据,且客户端通过 Service DNS 名而非硬编码 IP 访问服务器。

在 Spark History Server 场景中的落地

stable/spark-history-server/README.md 展示了该示例 NFS 的实际消费场景:Spark History Server Chart 默认创建一个由 NFS 卷支撑的 PVC,使历史服务器"开箱即用";用户也可以把 NFS 替换为 Gluster 等其他存储,只需通过pvc.existingClaimName指向预先创建好的 PVC。其参数表中明确列出了nfs.enableExampleNFS(默认true)用于控制是否安装演示用 NFS 卷与服务器。

需要留意的是,README 同时提示了该方案的边界:NFS 客户端共享同一块卷时,若底层卷不具备多 Pod 共享能力,spark-submit执行时会报 "PVCnfs-pvcis already mounted by another pod" 的错误。这说明该 NFS 示例的价值在于快速演示与验证共享存储语义,生产环境应根据共享能力选型底层存储。

手动验证 NFS 共享的经典流程(源自上游示例)

仓库内 stable/spark-history-server/charts/nfs/README.md 保留了源自 kubernetes/examples 的完整验证流程,可结合本组件镜像使用:

# 创建底层 PVC(GCE 磁盘) $ kubectl create -f examples/staging/volumes/nfs/provisioner/nfs-server-gce-pv.yaml # 创建 NFS 服务器与 Service $ kubectl create -f examples/staging/volumes/nfs/nfs-server-rc.yaml $ kubectl create -f examples/staging/volumes/nfs/nfs-server-service.yaml # 查询 NFS 服务器集群 IP $ kubectl describe services nfs-server # 用该 IP 更新 nfs-pv.yaml 后创建 PV/PVC $ kubectl create -f examples/staging/volumes/nfs/nfs-pv.yaml $ kubectl create -f examples/staging/volumes/nfs/nfs-pvc.yaml # 启动写端 fake backend(busybox,每 10 秒更新 index.html) $ kubectl create -f examples/staging/volumes/nfs/nfs-busybox-rc.yaml # 检查写端写入是否落盘 $ kubectl get pod -l name=nfs-busybox $ kubectl exec nfs-busybox-jdhf3 -- cat /mnt/index.html

如上流程所示,index.html既是nfs-data容器导出的初始测试文件,也是后续读写验证的探针——写端 busybox 会不断更新它,读端则通过挂载点或 HTTP 读取它,从而闭环验证 NFS 共享的读写可用性。

小结:nfs-data 组件的三个层次

  • 镜像层:CentOS + nfs-utils,/exports目录预置 644 权限的index.html,对外发布为gcr.io/google-samples/nfs-server(Dockerfile)。
  • 运行层:run_nfs.sh通过-N 2 -V 3 -N 4 -N 4.1严格限定 NFSv3,配合 rpcbind、mountd、exportfs、nfsd、statd 完成完整的 NFSv3 服务栈,并用 SIGTERM 陷阱实现优雅退出(run_nfs.sh)。
  • 消费层:上层nfsHelm Chart 以ReadWriteMany的 NFS PV/PVC 将它接入 Kubernetes 存储体系,供 Spark History Server 等需要多 Pod 共享读写的应用使用(values.yaml)。

如果把容器比作"一台自带文件的迷你 NFS 服务器",那么"为何只用 NFSv3"这一设计取舍,正是理解容器化 NFS 兼容性问题的第一课:内核 NFSv4 守护进程在容器环境中的已知问题,促使该项目选择了更成熟稳妥的 NFSv3 协议路径,并用清晰的命令行参数将这一决策固化在镜像构建与启动脚本中。

【免费下载链接】charts

⚠️(OBSOLETE) Curated applications for Kubernetes

项目地址:https://gitcode.com/gh_mirrors/chart/charts
点击查看免费下载
上一篇:免费找回误删与格式化数据:TestDisk 与 PhotoRec 的 480 种格式恢复指南
下一篇:Burp Suite汉化完整指南:5分钟用BurpSuiteCN-Release搞定专业中文界面

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询