在 Kubernetes 中构建 NFSv3 数据导出容器:解析 examples 仓库 nfs-data 镜像的构建与实战
示例工程【免费下载链接】examplesKubernetes application example tutorials项目地址https://gitcode.com/gh_mirrors/examp/examples点击查看免费下载导读本文以 Kubernetes 官方示例仓库gh_mirrors/examp/examples中的 nfs-data 文档 为骨架深入剖析一个自带数据文件的 NFS 服务器容器它把包含index.html的/exports目录通过 NFS 导出给集群内的其他 Pod 使用并刻意只启用 NFSv3 以规避部分 Linux 内核在容器中运行 NFSv4 守护进程的兼容性问题。读完本文你将掌握该镜像的 Dockerfile 与启动脚本细节、协议版本选择背后的原因以及如何把它部署为 Kubernetes 中的 NFS 持久卷后端并串联 PV/PVC、busybox 写入端与 nginx 读取端完成一次完整的读写验证。上图为仓库中 NFS 持久卷示例的架构示意Web 前端 Pod 使用基于 NFS 服务器的持久卷而 NFS 服务器自身又使用由 GCE PD 或 Azure Disk 自动供应auto-provision的持久卷。一、nfs-data 容器定位与镜像形态按 nfs-data 的 README 的描述这个容器做的事情非常聚焦导出挂载目录/exports并在其中放入一个index.html文件数据来源基于同级目录_archived/volumes/nfs下的整套 NFS 示例即父级 NFS 示例目录 所描述的 Web 前端 NFS 持久卷场景由于部分 Linux 内核在容器内运行 NFSv4 守护进程存在问题该容器只开放 NFSv3镜像以gcr.io/google-samples/nfs-server提供。值得注意的是当前仓库中的 nfs-server-deployment.yaml 实际使用的镜像是registry.k8s.io/volume-nfs:0.8即该容器镜像随 Kubernetes 版本演进已迁移到registry.k8s.io镜像域读者在部署时以部署清单中的实际镜像为准即可。二、Dockerfile 逐层拆解从 CentOS 到 NFS 服务进程完整内容见 _archived/volumes/nfs/nfs-data/Dockerfile核心指令如下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]每一层的关键点基础镜像centosnfs-utilsnfs-utils提供rpcbind、rpc.mountd、rpc.nfsd、exportfs、rpcinfo、rpc.statd等完整 NFS 服务端工具链同时顺带安装了ps命令/usr/bin/ps供运行期诊断进程使用。安装后立即yum clean all清理缓存控制镜像体积。目录与文件布局创建导出根目录/exports把启动脚本放到/usr/local/bin/run_nfs.sh把种子文件放到/tmp/index.html并赋予644权限由启动脚本在运行时拷贝到导出目录中。端口暴露EXPOSE明确了 NFS 服务涉及的三个端口——2049/tcpnfsdNFS 协议本体、20048/tcpmountd挂载协议、111/tcp与111/udprpcbindRPC 端口映射。这三个端口必须与后续 Service 暴露的端口一一对应。入口容器启动即执行run_nfs.sh /exports把/exports作为待导出的共享目录传给脚本。三、run_nfs.sh 启动脚本导出、协议裁剪与优雅退出镜像的核心逻辑集中在 _archived/volumes/nfs/nfs-data/run_nfs.sh 中它依次完成四件事准备导出清单、启动 RPC 基础设施、启动 mountd 与 nfsd、进入等待信号的空循环。3.1 生成 /etc/exports 并投放种子文件function start() { # prepare /etc/exports for i in $; do # fsid0: needed for NFSv4 echo $i *(rw,fsid0,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脚本接受一个或多个导出目录参数本例为/exports逐个写入/etc/exports导出选项*(rw,fsid0,insecure,no_root_squash)的含义rw允许客户端读写fsid0为导出目录设置文件系统标识是 NFSv4 所需的特性脚本注释也明确说明这一点即便本容器禁用 NFSv4保留它也无害insecure允许客户端使用大于 1024 的源端口发起挂载Kubernetes 中 NodePort 等场景常需要no_root_squash不把客户端 root 映射为匿名用户便于在演示环境中的写入操作随后把/tmp/index.html拷贝进每个导出目录并置为644这是带一个文件的关键客户端挂载后立刻能看到index.html。3.2 启动 rpcbind 与挂载协议守护进程# 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这一段的工程细节值得逐条展开rpcbind 幂等启动先用rpcinfo 127.0.0.1探测 RPC 服务是否已就绪未就绪才执行rpcbind -w-w表示热重启保留已注册的 RPC 服务避免重复启动冲突。挂载内核 nfsd 模块mount -t nfsd nfds /proc/fs/nfsd把内核的 NFS 服务端文件系统挂到/proc/fs/nfsd这是 nfsd 守护进程运行的前提。NFS 版本裁剪是核心rpc.mountd -N 2 -V 3 -N 4 -N 4.1与rpc.nfsd -G 10 -N 2 -V 3 -N 4 -N 4.1 2明确表示——禁用 NFSv2、NFSv4、NFSv4.1只启用 NFSv3-N为 disable-V为 enable。这与 README 中由于部分 Linux 内核在容器内运行 NFSv4 守护进程有问题只开放 NFSv3的说明完全对应是规避容器内核兼容性问题的务实取舍。NFSv3 流程补齐exportfs -r重新导出/etc/exports中的目录rpc.statd --no-notify启动状态监控守护进程NFSv3 锁与恢复所需--no-notify抑制重启时的通知风暴。缩短宽限期rpc.nfsd -G 10把 NFS 重启后的宽限期grace time压到允许的最低值 10 秒加快服务端故障切换后的客户端恢复。2参数rpc.nfsd ... 2中的2指定 nfsd 内核线程数量演示环境保持轻量。3.3 优雅停止与等待信号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 } trap stop TERM start $ # Ugly hack to do nothing and wait for SIGTERM while true; do sleep 5 donetrap stop TERM让容器收到SIGTERM如kubectl delete或节点驱逐时后按顺序收尾rpc.nfsd 0停止全部 nfsd 线程、exportfs -au取消全部导出、清空/etc/exports、卸载/proc/fs/nfsd主进程随后进入一个while true; sleep 5的空转循环等待SIGTERM触发 trap。脚本作者也自嘲这是 Ugly hack但对以 bash 脚本为 PID 1的容器来说这是让信号能被正确处理的最简方案——没有它SIGTERM会直接终止进程而不执行任何清理。四、为什么只开 NFSv3容器内核兼容性的务实选择README 中Since some Linux kernels have issues running NFSv4 daemons in containers是一句简短但关键的工程结论。结合 run_nfs.sh 的启动参数可以确认NFSv4 依赖内核态的 nfsd 与复杂的 RPC 状态在部分容器运行时/内核组合下/proc/fs/nfsd挂载或守护进程初始化可能失败导致服务无法启动NFSv3 的协议路径更简单服务端只需 rpcbind mountd nfsd 即可工作跨内核/容器运行时兼容性更好因此本容器显式-N 2 -V 3 -N 4 -N 4.1把能力收敛到 NFSv3换取在容器环境中的稳定可用。这也提醒读者挂载端的选项必须与服务端能力对齐。当前仓库的 nfs-pv.yaml 中写有mountOptions: nfsvers4.2而父级 NFS 示例 README 同时注明 this example uses an NFS container that doesnt support NFSv4。两者存在版本不一致的风险实际使用时请根据你部署的服务器镜像能力NFSv3 容器 vs 支持 v4 的镜像把mountOptions调整为对应的nfsvers3或nfsvers4.2否则客户端可能因协议版本不匹配而挂载失败。五、容器之外的完整链路从镜像到 PV/PVC 的 Kubernetes 实战单看 nfs-data 只是一个带 index.html 的 NFS 服务器容器它真正的价值是在父级 NFS 持久卷示例 中扮演存储后端。下面把完整链路串起来说明该容器产出的共享目录如何被集群消费。5.1 为 NFS 服务器准备底层存储NFS 服务器自身需要一个持久化底座。在 GCE 上创建 200Gi 的 PVCprovisioner/nfs-server-gce-pv.yamlapiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pv-provisioning-demo labels: demo: nfs-pv-provisioning spec: accessModes: [ ReadWriteOnce ] resources: requests: storage: 200Gi在 Azure 上则通过注解指定managed-premium存储类provisioner/nfs-server-azure-pv.yamlmetadata: annotations: volume.beta.kubernetes.io/storage-class: managed-premium5.2 部署 NFS 服务器 Deployment 与 Servicenfs-server-deployment.yaml 把上述 PVC 挂到容器的/exports——这正是 nfs-data/volume-nfs镜像导出共享目录的位置实现NFS 服务器导出一个自动供应的持久卷containers: - name: nfs-server image: registry.k8s.io/volume-nfs:0.8 ports: - name: nfs # 2049 - name: mountd # 20048 - name: rpcbind # 111 securityContext: privileged: true # 需要特权以操作内核 nfsd volumeMounts: - mountPath: /exports name: mypvc volumes: - name: mypvc persistentVolumeClaim: claimName: nfs-pv-provisioning-demo注意两个关键点privileged: true脚本需要挂载/proc/fs/nfsd、绑定内核端口因此容器必须运行在特权模式这是该镜像在 Kubernetes 中运行的前提条件三个容器端口与 Dockerfile 的EXPOSE一一对应也即与 nfs-server-service.yaml 暴露的2049、20048、111三个 Service 端口一致。5.3 用 PV/PVC 把 NFS 共享暴露为集群存储NFS 服务器就绪后用 nfs-pv.yaml 定义指向该服务器的持久卷注意按 4 节说明核对mountOptions的协议版本再用 nfs-pvc.yaml 发起 1Mi 的ReadWriteMany声明# PV spec: capacity: storage: 1Mi accessModes: - ReadWriteMany nfs: server: nfs-server.default.svc.cluster.local path: / mountOptions: - nfsvers4.2 # PVC spec: accessModes: - ReadWriteMany storageClassName: resources: requests: storage: 1Mi volumeName: nfs这里正是 nfs-data 容器价值放大的地方多个 Pod 不必关心 NFS 服务器的具体地址只需通过 PVC 名字nfs引用共享存储实现用符号名代替硬编码服务器地址的间接层。5.4 写入端与读取端验证共享文件确实可用nfs-busybox-deployment.yaml扮演写入端两个 busybox Pod 把 PVC 挂到/mnt并以一个 shell 循环持续覆写index.html写入当前时间和自身 hostnamewhile true; do date /mnt/index.html; hostname /mnt/index.html; sleep $(($RANDOM % 5 5)); donenfs-web-deployment.yaml扮演读取端两个 nginx Pod 把同一个 PVC 挂到/usr/share/nginx/html直接对外提供该文件nfs-web-service.yaml 以80端口把两个前端 Pod 聚合成一个访问入口。由此构成闭环busybox 写入 → NFS 服务器nfs-data 容器导出→ nginx 读取单份数据被多个 Pod 并发读写充分体现 NFS 的ReadWriteMany特性。六、端到端验证步骤父级 NFS 示例 README 给出了完整命令序列结合本文主题整理如下# 1. 按云平台创建底层 PVCGCE 或 Azure 二选一 $ kubectl create -f _archived/volumes/nfs/provisioner/nfs-server-gce-pv.yaml # 2. 创建 NFS 服务器及其 Service $ kubectl create -f _archived/volumes/nfs/nfs-server-deployment.yaml $ kubectl create -f _archived/volumes/nfs/nfs-server-service.yaml $ kubectl describe services nfs-server # 确认 Endpoints 已关联运行中的 Pod # 3. 按 Service 地址更新 nfs-pv.yaml 的 server 字段后创建 PV/PVC $ kubectl create -f _archived/volumes/nfs/nfs-pv.yaml $ kubectl create -f _archived/volumes/nfs/nfs-pvc.yaml # 4. 启动写入端 busybox $ kubectl create -f _archived/volumes/nfs/nfs-busybox-deployment.yaml $ kubectl get pod -l namenfs-busybox # 5. 检查写入结果应看到时间和 pod 名 $ kubectl exec nfs-busybox-jdhf3 -- cat /mnt/index.html # 6. 启动读取端 nginx 并验证 $ kubectl create -f _archived/volumes/nfs/nfs-web-deployment.yaml $ kubectl create -f _archived/volumes/nfs/nfs-web-service.yaml $ kubectl exec nfs-busybox-jdhf3 -- wget -qO- http://nfs-web-service-IP原文的验证输出示例为cat /mnt/index.html返回一行时间戳加一行nfs-busybox-w3s4t之类的 Pod 名wgetnginx 服务返回同样内容即证明写入端的数据经 NFS 共享被读取端成功读出。若失败优先检查两点PV 中的服务器 IP 是否已替换为真实 Service 地址以及kubectl describe services nfs-server是否列出了 Endpoints。七、小结与使用注意事项围绕_archived/volumes/nfs/nfs-data这个带文件的 NFS 导出容器可以提炼出几条可直接复用的结论镜像职责单一Dockerfile 与启动脚本的全部目的就是把一个含index.html的目录以 NFS 导出作为 Kubernetes 存储示例的共享后端产物即gcr.io/google-samples/nfs-server仓库内部署清单使用registry.k8s.io/volume-nfs:0.8。协议裁剪是设计核心-N 2 -V 3 -N 4 -N 4.1的参数组合把服务收敛到 NFSv3规避容器内核 NFSv4 兼容性问题挂载端mountOptions必须与服务端能力对齐。特权模式是硬前提脚本要挂载/proc/fs/nfsdKubernetes 中必须以privileged: true运行。信号处理值得借鉴trap stop TERM 空转循环的模式是 bash 脚本作为容器 PID 1 时实现优雅退出的经典写法。完整的读写闭环nfs-data 容器 PV/PVC busybox 写入端 nginx 读取端共同构成一个可复现的 NFSReadWriteMany共享存储演示相关清单均在 _archived/volumes/nfs 目录下可查。对于要在自己的集群里搭建 NFS 共享存储的读者直接复用本文涉及的 Dockerfile、run_nfs.sh 与配套 YAML即可在 GCE/Azure 等云平台或本地集群中快速起一套可读写的 NFS 后端。赞分享示例工程【免费下载链接】examplesKubernetes application example tutorials项目地址https://gitcode.com/gh_mirrors/examp/examples点击查看免费下载相关推荐Kubernetes Goat 镜像构建实战users-repo 脆弱容器与私有镜像仓库泄露场景源码解析Kubernetes Goat 镜像构建实战users repo 脆弱容器与私有镜像仓库泄露场景源码解析 users repo 是 Kubernetes Go云原生应用安全教程容器训练项目使用Kaniko在Kubernetes中构建容器镜像容器训练项目使用Kaniko在Kubernetes中构建容器镜像 什么是Kaniko Kaniko是一款开源工具专门设计用于在Kubernetes环境中构建Woodpecker CI 中使用 Buildah 构建容器镜像Podman 后端的镜像构建实战Woodpecker CI 中使用 Buildah 构建容器镜像Podman 后端的镜像构建实战 本文基于 Woodpecker 社区博客《Podman inCI/CDDevOps上一篇NSQ分布式消息平台完整指南从入门到实战部署下一篇leaflet-color-markers完全指南如何快速实现地图标记颜色定制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考