跳转至

NFS 存储服务 运行手册(RUNBOOK)

交付目录:nfs-storage/ 适用对象:接手日常运维与值班的人(假设你没接触过本服务,凌晨被叫起来也要能照着做完) 配套文档:变更实施与回滚见 nfs-storage/SOP.md;挂载参数调优见 nfs-mount-tuning/docs/SOP.md 最后核对时间:2026-09-19(文中所有水位与状态均以该时刻实测为准)

目录


1. 服务概览

用途:为 k8s_ins 集群提供动态供给、支持 RWX 与快照的持久存储,供有状态应用挂载。

组成

层 组件 位置 作用
服务端 nfs-server(nfs-kernel-server 1:2.6.4-3ubuntu5.1)+ rpcbind 宿主 dell-R620 192.168.31.249 提供三处导出(NFSv4.2)
服务端后端 SSD 120GiB loop 镜像 → /srv/nfs/k8s 宿主 sda3 nfs-csi 存储类的数据目录
服务端后端 HDD 500GiB loop 镜像 → /srv/nfs/k8s-hdd 宿主 /data nfs-csi-hdd 存储类的数据目录
服务端后端 HDD 裸目录 /data/nfs-k8s-snap 宿主 /data 卷快照归档(.tar.gz)
集群控制面 csi-nfs-controller(5 容器 Deployment) kube-system,当前在 k8s-node-02 CreateVolume / DeleteVolume / 快照编排
集群数据面 csi-nfs-node(3 容器 DaemonSet,4 副本) kube-system,全部 4 节点 节点侧挂载/卸载
集群快照 snapshot-controller(1 容器 Deployment) kube-system 处理 VolumeSnapshot 对象
集群对象 StorageClass nfs-csi(默认) / nfs-csi-hdd、CSIDriver nfs.csi.k8s.io、3 个 snapshot CRD、VolumeSnapshotClass csi-nfs-snapclass、Secret nfs-mount-options 集群级 / kube-system 供应用引用

依赖

  • 宿主 dell-R620 在线、nfs-server 运行;/data(sdb1)与宿主根分区(sda3)可读写。
  • 集群节点可经 192.168.31.0/24 访问宿主 2049 端口。
  • 宿主上还有 Slurm 的导出 /data/nfs/slurm-state 共用同一个 nfs-server 服务与 /etc/exports(见第 9 章)。

异常时的业务影响

服务不可用时,下列业务会I/O 阻塞(hard 挂载特性,不是报错而是卡住):

命名空间 业务 卷
server MariaDB(数据库)、MinIO(GitLab 对象存储) storage-mariadb-0(20Gi)、minio-data(50Gi)
server / ldap OpenLDAP ×2 套(conf/data/backups) *-openldap-0(1/8/8Gi ×2)
monitoring Prometheus(100Gi)、Grafana(10Gi)、Alertmanager(10Gi) 见 SOP 第 4 章
宿主机 Slurm 集群 /data/nfs/slurm-state

重要性等级:高。理由:承载数据库与监控栈;服务端与全部计算节点同宿主、无冗余,宿主故障即存储与计算同时中断。


2. 接入与凭据

地址与端口

项 值
NFS 服务端 192.168.31.249:2049(NFSv4.2,TCP)
rpcbind 192.168.31.249:111
允许客户端网段 192.168.31.0/24(/etc/exports 限定)
导出路径 /srv/nfs/k8s、/srv/nfs/k8s-hdd、/data/nfs-k8s-snap

账号与凭据存放(本文不记录任何口令/密钥)

用途 账号 凭据从哪取
宿主机运维 rain(在 sudo 组,sudo -n 免密) 已有 SSH 会话;无需口令
集群操作 kubeconfig 的 kubernetes-admin 默认 ~/.kube/config(执行 kubectl config view --minify 查看)
节点排障 root(免密公钥登录) ssh root@<节点IP> 直接登录

客户端接入示例

宿主机或同网段机器手工挂载(与集群 Pod 看到的是同一份数据,勿同时写):

# 挂载整个导出(推荐用与 StorageClass 一致的参数)
mount -t nfs -o vers=4.2,nconnect=4,hard,timeo=600,retrans=3,noatime \
  192.168.31.249:/srv/nfs/k8s /mnt

# 只挂某个 PVC 的子目录(路径取自 PV 的 volumeHandle 第三段)
mount -t nfs -o vers=4.2 192.168.31.249:/srv/nfs/k8s/pvc-204be3a9-1adf-4123-8c04-94fa0da2f27f /mnt

# 用完记得卸载
umount /mnt

集群内应用引用(PVC 片段):

spec:
  storageClassName: nfs-csi          # 或 nfs-csi-hdd;不写则用默认的 nfs-csi
  accessModes: [ReadWriteMany]
  resources:
    requests:
      storage: 10Gi

3. 日常速查表

# 目的 命令
1 看整体是否正常 kubectl -n kube-system get pods \| grep -E 'csi-nfs\|snapshot'
2 看存储类 kubectl get sc
3 看卷与业务绑定 kubectl get pvc -A \| grep -E 'nfs-csi\|NAMESPACE'
4 看宿主服务端状态 systemctl is-active nfs-server && sudo exportfs -v && nfsconf --get nfsd threads
5 看容量水位 df -h /srv/nfs/k8s /srv/nfs/k8s-hdd /data /
6 看各卷占用 sudo du -sh /srv/nfs/k8s/* /srv/nfs/k8s-hdd/* 2>/dev/null
7 看 CSI 日志 kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=100 --prefix
8 看服务端日志 sudo journalctl -u nfs-server --since "30 min ago" \| tail -50
9 看节点真实挂载参数 ssh root@192.168.31.13 'findmnt -t nfs4 -o TARGET,OPTIONS'
10 驱动升级后必做 kubectl apply -f nfs-storage/manifests/02-volumesnapshotclass.yaml

4. 日常操作

4.1 状态检查

# 集群侧:期望 csi-nfs-controller 5/5、csi-nfs-node 4 个各 3/3、snapshot-controller 1/1
kubectl -n kube-system get pods -o wide | grep -E 'csi-nfs|snapshot'

预期输出(示例):

csi-nfs-controller-f54d5775d-9c98g   5/5   Running   0   2d15h   192.168.31.14   k8s-node-02
csi-nfs-node-9j8md                   3/3   Running   0   2d15h   192.168.31.14   k8s-node-02
csi-nfs-node-t57sm                   3/3   Running   0   2d15h   192.168.31.13   k8s-node-01
csi-nfs-node-vp5tm                   3/3   Running   0   2d15h   192.168.31.10   k8s-master-01
csi-nfs-node-xlh2d                   3/3   Running   0   2d15h   192.168.31.15   k8s-node-03
snapshot-controller-887465679-85j2g  1/1   Running   0   2d15h   10.244.44.194   k8s-node-02

不符时:Running 但 READY 不足 → 看该 Pod 日志(4.4);Pending → kubectl -n kube-system describe pod <名> 看事件。

# 服务端侧:期望 active / 四处导出(含 Slurm 的)+ threads 32
systemctl is-active nfs-server
sudo exportfs -v
nfsconf --get nfsd threads
sudo ss -lntp | grep -E '2049|111'          # 期望 2049 与 111 均在 LISTEN

4.2 启动 / 停止 / 重启

# 服务端:查看状态(安全)
systemctl status nfs-server --no-pager

# 谨慎:重载导出配置(不会中断已挂载的客户端)
sudo exportfs -ra && sudo exportfs -v

# 高危:重启 nfs-server —— 会同时中断集群全部 NFS 业务与 Slurm
# 仅在确认两侧均可中断时执行,执行后按第 8 章场景 4 验证
sudo systemctl restart nfs-server

不要在故障排查中随手重启 nfs-server:客户端是 hard 挂载,服务端短暂不可用期间业务会卡在 I/O 上, 重启后虽能自愈,但会拉长业务受影响时间。优先查日志与导出状态。

4.3 扩容与缩容

  • 应用侧申请新卷:直接创建 PVC 即可,无需人工干预。
  • 已有卷改请求容量:kubectl patch pvc <名> -n <ns> -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'。 注意:NFS 后端不强制容量,改请求值只是记账(详见第 10 章)。
  • 后端总体扩容:见第 10 章"扩容操作"。

4.4 日志查看

# CSI 驱动(控制面:创建/删除卷、快照;数据面:挂载/卸载)
kubectl -n kube-system logs -l app=csi-nfs-controller -c nfs --tail=200
kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=200 --prefix

# 快照控制器
kubectl -n kube-system logs deploy/snapshot-controller --tail=100

# 服务端
sudo journalctl -u nfs-server --since "1 hour ago" | tail -100
sudo journalctl -u nfs-server -p err --since today        # 只看错误级别

# 单次操作的完整上下文(排查挂载失败必看)
kubectl -n <ns> describe pod <pod名> | sed -n '/Events/,$p'

4.5 进入宿主机或节点排障

# 宿主机就是本机(dell-R620),直接执行;带 sudo 的命令已授权免密
hostname -I && sudo exportfs -v

# 进入某个 k8s 节点(root 免密)
ssh root@192.168.31.13                      # k8s-node-01
ssh root@192.168.31.14                      # k8s-node-02
ssh root@192.168.31.15                      # k8s-node-03
ssh root@192.168.31.10                      # k8s-master-01

# 在节点上查看某卷的真实挂载点与参数
findmnt -t nfs4 -o TARGET,SOURCE,OPTIONS
# 查看 kubelet 侧的 CSI 挂载目录
ls -l /var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~csi/    # <pod-uid> 来自 kubectl get pod -o jsonpath='{.metadata.uid}'

5. 配置管理

配置项位置

层 文件 内容
集群(客户端参数、两个 SC、快照开关) nfs-storage/values-nfs-csi.yaml 核心实现体,改这里
集群(删除卷所需挂载参数) nfs-storage/manifests/00-mount-options-secret.yaml 必须与 SC 的 mountOptions 一致
集群(快照 CRD) nfs-storage/manifests/01-snapshot-crds.yaml 独立于 Helm 管理
集群(快照落盘位置) nfs-storage/manifests/02-volumesnapshotclass.yaml 每次 helm 升级后都要重新 apply
服务端(导出与权限) 宿主 /etc/exports 三处本方案导出 + Slurm 导出共存
服务端(nfsd 线程等) 宿主 /etc/nfs.conf [nfsd] threads = 32
服务端(loop 挂载持久化) 宿主 /etc/fstab 两个镜像的 loop 挂载行

修改流程

# 1) 改客户端参数 / SC(示例:改 mountOptions)
vi nfs-storage/values-nfs-csi.yaml
# 2) 同步 Secret(内容必须与 SC 一致)
vi nfs-storage/manifests/00-mount-options-secret.yaml
kubectl apply -f nfs-storage/manifests/00-mount-options-secret.yaml
# 3) 应用
helm upgrade --install nfs-csi nfs-storage/charts/csi-driver-nfs -n kube-system \
  -f nfs-storage/values-nfs-csi.yaml --wait --timeout 6m
# 4) 必做:恢复快照落盘分流(helm 会重置该对象)
kubectl apply -f nfs-storage/manifests/02-volumesnapshotclass.yaml
# 5) 若改的是挂载参数,已有 PV 不会自动生效,按 SOP 第 5.3 节逐个 patch 并重启使用方
# 改服务端导出(示例:把某处导出改回 sync)
sudo cp -a /etc/exports "/etc/exports.bak.$(date +%Y%m%d-%H%M%S)"
sudo vi /etc/exports
sudo exportfs -ra && sudo exportfs -v

易错点(踩过或极易踩)

  1. helm upgrade 会重置 VolumeSnapshotClass,快照会静默回落到 SSD,可能撑满才发现 → 每次升级后必做第 4 步。
  2. 改 mountOptions 只对新建 PV 生效;已有 PV 需逐个 kubectl patch pv 并重启使用方。
  3. /etc/exports 与 Slurm 共用:编辑时不要整文件重写,改完用 exportfs -ra 而不是 exportfs -ua。
  4. /etc/nfs.conf 的 threads 必须用 sudo nfsconf --set nfsd threads 32 维护(文件末尾是 [svcgssd] 段,tee -a 追加会写错段)。
  5. 两个 SC 只能有一个带默认注解;当前默认是 nfs-csi。

6. 备份与恢复

备份策略

对象 方式 位置
卷数据(业务数据本体) 后端 loop 镜像文件,天然整体可复制 /kvm/nfs/k8s-120g.img(SSD 后端)、/data/nfs/k8s-hdd-500g.img(HDD 后端)
卷快照 走 VolumeSnapshot 对象(tar 归档) /data/nfs-k8s-snap/snapshot-*/
服务端配置 变更前 cp -a 带时间戳备份 原位 /etc/*.bak.*,台账见 nfs-storage/backup/README.md
集群配置 values 与 manifests 纳入版本管理 nfs-storage/、nfs-mount-tuning/

手工备份命令

# 服务端配置备份(高危操作前必做)
sudo cp -a /etc/exports   "/etc/exports.bak.$(date +%Y%m%d-%H%M%S)"
sudo cp -a /etc/fstab     "/etc/fstab.bak.$(date +%Y%m%d-%H%M%S)"
sudo cp -a /etc/nfs.conf  "/etc/nfs.conf.bak.$(date +%Y%m%d-%H%M%S)"

# 卷数据备份(示例:把 SSD 后端某个 PVC 的目录打包到 HDD;先确认该 PVC 无写入高峰)
PVC_DIR=/srv/nfs/k8s/pvc-204be3a9-1adf-4123-8c04-94fa0da2f27f      # 取自 kubectl get pv -o jsonpath='{.spec.csi.volumeHandle}' 的第三段
sudo tar czf "/data/backup-$(basename "$PVC_DIR")-$(date +%Y%m%d-%H%M%S).tar.gz" -C "$PVC_DIR" .
sudo ls -lh /data/backup-*.tar.gz

# 镜像整体备份(慎用,占用同等空间;确认目标盘余量充足)
# sudo cp --sparse=always /data/nfs/k8s-hdd-500g.img /data/k8s-hdd-500g.img.bak

恢复步骤

# 场景一:误删了某个 PVC 的目录内容 → 从 tar 恢复
sudo tar xzf /data/backup-pvc-xxxx-<时间戳>.tar.gz -C /srv/nfs/k8s/pvc-xxxx/
# 恢复后在业务侧校验(示例见第 8 章场景 2 的验证段)

# 场景二:从卷快照恢复 → 用 PVC 的 dataSource(参考 nfs-storage/examples/03-snapshot-and-restore.yaml)
kubectl apply -f nfs-storage/examples/03-snapshot-and-restore.yaml
kubectl get pvc demo-data-restored        # 预期 Bound
kubectl logs pvc-restore-check            # 预期打印恢复出来的文件内容

# 场景三:服务端配置被改坏 → 从时间戳备份还原
sudo cp -a /etc/exports.bak.<时间戳> /etc/exports && sudo exportfs -ra && sudo exportfs -v

恢复后的验证:按第 8 章对应场景的"验证"段执行;确认业务 Pod 不再报 I/O 错误(kubectl -n <ns> get pod 均 Running 且无新增重启)。

演练记录:快照恢复路径已于交付时实测通过(见 SOP.md 第 6 章 V7); 整机恢复与镜像级回滚尚未演练,属待补项。


7. 监控与告警

查看入口

# Grafana(监控面板;端口转发后浏览器访问 http://localhost:3000)
kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80

# Alertmanager
kubectl -n monitoring port-forward svc/kube-prometheus-stack-alertmanager 9093:9093

关键指标与查看方式

指标 怎么看 健康区间
后端空间使用率 df -h /srv/nfs/k8s /srv/nfs/k8s-hdd < 80%
宿主根分区使用率 df -h / < 80%(当前 25%)
/data 使用率 df -h /data < 80%(当前 7%)
HDD 争抢情况 iostat -x 1(看 %util、await) %util 长期 < 70%
NFS 客户端 RPC 延迟 节点上 cat /proc/self/mountstats,Grafana 中 node-exporter 的 NFS 指标 OPEN/SETATTR 平均 < 5ms(SSD 预期 < 1ms)
CSI 组件存活 kubectl -n kube-system get pods \| grep csi-nfs 全部 Running 且重启计数不增长
卷挂载是否正常 kubectl get pvc -A 全部 Bound
服务端 nfsd 线程 nfsconf --get nfsd threads 32

日志位置

来源 命令
服务端 sudo journalctl -u nfs-server --since "1 hour ago"
CSI 控制面 kubectl -n kube-system logs -l app=csi-nfs-controller -c nfs --tail=200
CSI 数据面 kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=200 --prefix
业务侧现象 kubectl -n <ns> describe pod <名>、业务自身日志

建议设置的告警(当前尚未配置,属待办)

告警 阈值 动作
后端空间不足 /srv/nfs/k8s 或 /srv/nfs/k8s-hdd 使用率 > 80% 见第 10 章扩容
宿主根分区不足 / 使用率 > 80% 清理或扩镜像容量
CSI 组件异常 csi-nfs-* Pod 非 Running 持续 2 分钟 见第 8 章场景 1、2
服务端不可达 2049 端口探测失败 见第 8 章场景 4

8. 故障处置

场景 1:PVC 一直 Pending,不 Bound

症状:kubectl get pvc -n <ns> 显示 Pending,相关 Pod 卡在 ContainerCreating 或 Pending。

诊断

# 1) 看 PVC 事件(最常见原因直接写在这里)
kubectl -n <ns> describe pvc <pvc名> | sed -n '/Events/,$p'
# 预期:正常应有 Provisioning → ProvisioningSucceeded;报错会给出原因

# 2) 供给器是否存活(Provisioning 由它执行)
kubectl -n kube-system get pod -l app=csi-nfs-controller        # 预期 1 个 5/5 Running
kubectl -n kube-system logs -l app=csi-nfs-controller -c nfs --tail=100     # 预期无 "failed to create subdirectory"

# 3) 服务端是否还能写(controller 需挂载 NFS 建目录)
sudo exportfs -v | grep -E 'srv/nfs/k8s'                        # 预期导出存在且为 async
systemctl is-active nfs-server                                  # 预期 active

处置

  • 事件提示 failed to provision 且日志显示权限/挂载错误 → 先修服务端(导出、服务状态),再删除该 PVC 重建: bash kubectl -n <ns> delete pvc <pvc名> # 高危:未 Bound 的 PVC 无数据,删除安全 kubectl apply -f <原清单>
  • 事件提示 storageclass 不存在或名字写错 → kubectl get sc 核对(应为 nfs-csi 或 nfs-csi-hdd)。
  • controller Pod 不健康 → 见场景 2 的组件排查;必要时重启组件: bash kubectl -n kube-system rollout restart deploy/csi-nfs-controller

验证:kubectl get pvc -n <ns> <pvc名> 显示 Bound,且 kubectl get pv 中对应 PV 的 volumeHandle 指向正确后端。

场景 2:Pod 挂载失败或读写报 I/O error

症状:Pod 事件报 MountVolume.SetUp failed、failed to mount;或业务日志出现 Input/output error、卡住无响应。

诊断

# 1) 挂载失败的具体原因(node 插件才有挂载动作)
kubectl -n <ns> describe pod <pod名> | sed -n '/Events/,$p'
kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=150 --prefix

# 2) 该 Pod 调度到的节点上,服务端是否可达
NODE=$(kubectl -n <ns> get pod <pod名> -o jsonpath='{.spec.nodeName}')
NODE_IP=$(kubectl get node "$NODE" -o jsonpath='{.status.addresses[0].address}')
ssh root@"$NODE_IP" 'timeout 5 bash -c "</dev/tcp/192.168.31.249/2049" && echo REACHABLE || echo UNREACHABLE'
# 预期:REACHABLE

# 3) 节点上实际挂载情况
ssh root@"$NODE_IP" 'findmnt -t nfs4 -o TARGET,SOURCE,OPTIONS | head'
# 预期:能看到对应 PV 的挂载点,OPTIONS 含 vers=4.2,nconnect=4

处置

  • 服务端不可达 → 见场景 4。
  • 挂载点存在但业务报 I/O 错误、且服务端正常 → 让使用方重新挂载: bash kubectl -n <ns> rollout restart statefulset/<名> # 高危:重启业务,逐个进行
  • 生产事故中优先止血:确认服务端恢复后,重启受影响业务即可自愈(hard 挂载会随后续 I/O 恢复)。

验证:Pod Running 且无新增重启;进入容器读写测试:

kubectl -n <ns> exec <pod名> -- sh -c 'touch /data/.probe && ls -l /data/.probe && rm -f /data/.probe'
# 预期:无报错,能看到文件

场景 3:快照创建失败,或快照落到了 SSD 上

症状:kubectl get volumesnapshot -A 显示 READYTOUSE=false;或宿主机 /srv/nfs/k8s 下出现了 snapshot-* 目录(说明分流失效)。

诊断

# 1) 快照对象状态
kubectl get volumesnapshot -A
kubectl describe volumesnapshot <名> -n <ns> | sed -n '/Events/,$p'
kubectl get volumesnapshotcontent -o custom-columns='NAME:.metadata.name,READY:.status.readyToUse,ERR:.status.error.message'

# 2) 快照类是否仍指向 HDD(helm 升级后最易丢)
kubectl get volumesnapshotclass csi-nfs-snapclass -o jsonpath='{.parameters}{"\n"}'
# 预期:{"server":"192.168.31.249","share":"/data/nfs-k8s-snap"}

# 3) 归档实际位置
sudo find /data/nfs-k8s-snap -maxdepth 2 -name '*.tar.gz' | head
sudo ls -d /srv/nfs/k8s/snapshot-* 2>/dev/null && echo "警告:快照落到了 SSD"

处置

  • parameters 为空或指向 SSD → 恢复分流配置后重试: bash kubectl apply -f nfs-storage/manifests/02-volumesnapshotclass.yaml kubectl delete volumesnapshot <名> -n <ns> # 高危:删除失败/错位的快照对象 kubectl apply -f <快照清单>
  • 已错落在 SSD 的归档 → 搬到 HDD 后清理(先确认无快照对象引用,否则直接删对象让它重建): bash sudo mv /srv/nfs/k8s/snapshot-xxxx /data/nfs-k8s-snap/ # 高危:确认该目录不被其它快照引用
  • 空间不足导致失败 → 见第 10 章。

验证:kubectl get volumesnapshot <名> -n <ns> 显示 READYTOUSE=true;sudo find /data/nfs-k8s-snap -maxdepth 2 -name '*.tar.gz' 能看到新归档;ls /srv/nfs/k8s 无 snapshot-*。

场景 4:宿主机重启后存储未恢复

症状:宿主 reboot 后,业务 Pod 卡在 I/O;exportfs -v 无导出,或 findmnt 看不到后端挂载。

诊断

# 1) loop 后端是否恢复挂载(fstab 带 nofail,挂载失败不会阻塞启动,需人工确认)
findmnt -o SOURCE,TARGET,OPTIONS -t ext4 | grep -E 'srv/nfs/k8s'
# 预期:两行,OPTIONS 含 noatime,prjquota

# 2) 服务是否起来
systemctl is-active nfs-server && nfsconf --get nfsd threads      # 预期 active 与 32
sudo exportfs -v                                                  # 预期四处导出(含 Slurm)

# 3) 若镜像未挂载,看失败原因
sudo fdisk -l /kvm/nfs/k8s-120g.img 2>/dev/null | head -3
sudo journalctl -u nfs-server -b --since "10 min ago" | tail -30

处置

# 缺挂载 → 手工补齐(幂等)
sudo mount -a && findmnt -t ext4 | grep nfs
# 服务未起 → 启动(不影响已恢复的客户端)
sudo systemctl start nfs-server && sudo exportfs -ra && sudo exportfs -v
# 仍失败 → 按 SOP 第 5.1 步骤 3、4 逐条排查(fstab 行、镜像文件是否存在)

验证:四处导出可见、nfsconf --get nfsd threads 为 32;节点侧 findmnt -t nfs4 有挂载;业务 Pod 自动恢复(若不恢复则逐个重启);Slurm 侧状态目录可读写。

场景 5:HDD 后端读写明显变慢

症状:Prometheus/OpenLDAP 等 HDD 卷业务出现查询慢、写入超时;元数据操作 RTT 从亚毫秒升到几十毫秒。

诊断

# 1) 物理盘是否被抢(HDD 上有 VM qcow2、Slurm 状态、HDD 卷三方共用)
iostat -x 1 5 | grep -E 'Device|sdb'
# 预期:%util 长期 < 70%;await 明显升高说明排队严重

# 2) 谁在写
sudo iotop -o -b -n 3 2>/dev/null | head -20

# 3) 服务端线程是否被改回去
nfsconf --get nfsd threads && sudo cat /proc/fs/nfsd/threads      # 预期均为 32

# 4) 导出是否仍是 async
sudo exportfs -v | grep srv/nfs/k8s-hdd                            # 预期含 async

处置

  • 线程数回落 → printf '32\n' | sudo tee /proc/fs/nfsd/threads && sudo nfsconf --set nfsd threads 32。
  • 导出被改回 sync → 评估后改回(注意 R2:async 有掉电丢数据风险,改前确认业务可接受)。
  • 确认是 VM/Slurm 抢盘 → 属于资源竞争,短期只能错峰;长期方案是把 HDD 卷迁移到独立盘。
  • 需要临时缓解 → 把延迟不敏感的负载从 HDD 卷迁到 SSD 卷(改 PVC 的 SC 需重建 PVC,见第 10 章)。

验证:iostat -x 1 的 %util/await 回落;节点侧 cat /proc/self/mountstats 中 OPEN/SETATTR 平均 RTT 回到毫秒级以下。

场景 6:后端空间写满(ENOSPC)

症状:业务报 No space left on device;df -h /srv/nfs/k8s 或 /srv/nfs/k8s-hdd 显示 100%。

诊断

df -h /srv/nfs/k8s /srv/nfs/k8s-hdd /data /
sudo du -sh /srv/nfs/k8s/* /srv/nfs/k8s-hdd/* 2>/dev/null | sort -h | tail -10   # 谁占得最多
sudo du -sh /data/nfs-k8s-snap/* 2>/dev/null | sort -h | tail -5                  # 快照占用
kubectl get pvc -A -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,REQ:.spec.resources.requests.storage' | grep nfs-csi

处置

# 1) 先清理无用快照(可回收大量空间)
kubectl get volumesnapshot -A                                     # 找出不再需要的
kubectl delete volumesnapshot <名> -n <ns>                        # 高危:删除后归档立即回收

# 2) 清理僵尸卷目录(PVC 已删但目录残留的情况)——先确认无 PVC 引用再删
kubectl get pv -o jsonpath='{range .items[*]}{.spec.csi.volumeHandle}{"\n"}{end}' | grep /srv/nfs/k8s
sudo ls -d /srv/nfs/k8s/pvc-*                                     # 与上一步结果比对,多出来的即为残留
sudo rmdir /srv/nfs/k8s/pvc-<确认无引用>                          # 高危:目录非空时不要强删

# 3) 扩容后端(详见第 10 章)

验证:df -h 使用率回落;业务写入恢复正常;kubectl get pvc -A | grep nfs-csi 全部 Bound。


9. 安全与权限

访问控制

项 现状 说明
导出范围 192.168.31.0/24 仅该网段客户端可挂载;改网段需同步 /etc/exports
导出权限 rw,async,no_subtree_check,no_root_squash no_root_squash 意味着该网段任意主机以 root 身份可读写全部数据,属内网信任模型
数据可见性 挂载整个导出即可看到所有 PVC 目录 无租户隔离;误删会影响对应业务 Pod
集群侧权限 kubernetes-admin(cluster-admin) 无更细 RBAC 划分
节点登录 root 免密公钥 密钥在 ~/.ssh/,勿外传

风险与改进方向

  • no_root_squash 是 fsGroup 能正常工作的前提(改为 root_squash 会导致挂载失败),故不宜随意收紧; 如需收紧,应改为 all_squash,anonuid/anongid 并让 Pod 用 runAsUser/runAsGroup 对齐(属变更,走 SOP 流程)。
  • 建议限制宿主机 SSH 来源、并把 192.168.31.0/24 内的客户端纳管清点。

证书与轮换:本服务不使用 TLS 证书;NFS 采用 sec=sys(AUTH_SYS,基于 UID/GID 信任)。 凭据轮换:无静态凭据需轮换;SSH 密钥与 kubeconfig 的轮换沿用集群既有制度。

审计:当前无独立审计日志;服务端操作痕迹在 journalctl -u nfs-server,集群侧在 CSI 组件日志与 API Server 审计(若开启)。


10. 容量与性能

当前水位基线(2026-09-19 实测)

对象 容量 已用 使用率 备注
/srv/nfs/k8s(SSD 后端) 118G 212M 1% loop 上限 120GiB
/srv/nfs/k8s-hdd(HDD 后端) 492G 1.1G 1% loop 上限 500GiB
/data/nfs-k8s-snap(快照) 与 /data 共池 — — 弹性占用
/data(HDD 本体) 1.8T 116G 7% 含 VM qcow2、HDD 后端镜像、快照、Slurm 状态
/(宿主 SSD) 220G 52G 25% 含 SSD 后端镜像(按需增长)、3 个 etcd raw

重要特性:容量不被强制

VCS 层面的请求值(PVC storage)在 NFS 后端不被强制,它只是记账:

# 看某个卷实际占了多少
PVC_DIR=$(kubectl get pv <pv名> -o jsonpath='{.spec.csi.volumeHandle}' | awk -F'#' '{print $2"/"$3}')
sudo du -sh "/${PVC_DIR}"          # 注意 volumeHandle 里的 share 是以 / 开头形式的相对路径

后端已启用 prjquota(项目配额),需要硬限额时手工设置:

sudo setquota -P <projectID> <软块上限> <硬块上限> 0 0 /srv/nfs/k8s   # projectID 与目录需先建立映射
sudo repquota -P /srv/nfs/k8s

扩容触发条件与操作

触发:任一后端使用率 > 80%,或 / > 80%,或业务报 ENOSPC。

# 以 HDD 后端扩到 800GiB 为例(高危:改动 loop 镜像与文件系统)
df -h /data                                  # 先确认 /data 余量足够
sudo truncate -s 800G /data/nfs/k8s-hdd-500g.img      # 稀疏扩展文件(按需增长,不会立即占满)
sudo losetup -c /dev/loop19 2>/dev/null || sudo blockdev --rereadpt /dev/loop19   # 让 loop 识别新大小
sudo resize2fs /dev/loop19                            # 在线扩容文件系统(挂载状态下可执行)
findmnt -no SOURCE,SIZE /srv/nfs/k8s-hdd              # 预期容量变大
# SSD 后端同理,注意同时受宿主根分区余量约束(当前可用约 157G)

注意:StorageClass 里的 allowVolumeExpansion 只影响能否修改 PVC 请求值, 与后端真实容量无关——扩容必须按上面步骤操作后端。

已知瓶颈与优化方向

瓶颈 说明 方向
HDD 与 VM 抢盘 HDD 卷、快照归档、Slurm 状态、4 台 VM 的 qcow2 共用一块物理盘 迁移到独立盘;或把 IO 敏感业务放 SSD 版 SC
loop 叠加 后端为文件 + loop,随机 IO 比裸设备差 生产环境考虑独立块设备
单点无冗余 服务端与全部计算同宿主 将 NFS 服务端迁至独立主机
容量不强制 单卷可写穿共享池,影响同后端其它卷 用 prjquota 对关键卷设限额
async 丢数据风险 宿主掉电可能丢最近写入 数据库所在导出可单独改回 sync(按导出粒度)

11. 附录

术语表

术语 含义
StorageClass (SC) 存储类,PVC 通过它选择后端与参数
PV / PVC 持久卷 / 持久卷声明,PVC 是应用的申请,PV 是实际分配的对象
CSI 容器存储接口,K8s 与存储驱动之间的标准协议
volumeHandle PV 上的后端标识,格式 <server>#<share>#<subdir>,据此定位宿主上的目录
provision 动态供给:由驱动自动创建后端目录并绑定 PV
fsGroup Pod 声明的属组,kubelet 会据此改卷内文件属组(本方案依赖导出 no_root_squash)
nfsd threads 服务端处理 RPC 的线程数
nconnect 同一挂载使用多条 TCP 连接并行 RPC

相关文档与交付物

路径(相对项目根) 说明
nfs-storage/RUNBOOK.md 本文件
nfs-storage/SOP.md 变更实施、验证与回滚
nfs-storage/backup/README.md 备份台账(含原位 .bak 的位置与可删条件)
nfs-storage/values-nfs-csi.yaml 核心实现体:客户端参数与两个 StorageClass
nfs-storage/manifests/ CRD、Secret、快照落盘分流清单
nfs-storage/examples/ 四个验证用例(PVC / 跨节点 / 快照恢复 / HDD)
nfs-mount-tuning/docs/SOP.md 挂载参数调优的变更记录

修订记录

日期 修改人 变更内容
2026-09-19 运维 首版:基于 SOP.md 当前状态提炼运行手册,含 6 个故障场景、水位基线与建议告警

待补项

  1. 宿主 reboot 后的自愈验证(对应 SOP 第 6 章 V11)尚未执行,需约定窗口。
  2. 监控告警规则尚未落地(第 7 章建议表)。
  3. 整机恢复与镜像级回滚尚未演练。

来源:/home/rain/Documents/k8s_ins/nfs-storage/RUNBOOK.md | 最后更新:2026-09-19 16:28(由 build/sync.py 同步,请勿在站点侧修改)