NFS 存储(k8s_ins)运行与使用手册 RUNBOOK¶
适用对象:接手 k8s_ins 集群 NFS 存储的日常运维与值班人员 读者假设:没接触过这套存储,凌晨被叫起来也能照着做完 关联变更档案:
docs/SOP.md(2026-09-19 挂载参数优化)、nfs-storage/SOP.md(2026-09-17 存储类交付) 最近更新:2026-09-19
目录¶
1. 服务概览¶
干什么用的:为 k8s_ins 集群提供可 RWX、可动态供给、支持快照的共享存储。 集群里的 minio、mariadb、Prometheus、Grafana、Alertmanager、openldap 都跑在它上面。
由哪些组件构成
| 组件 | 位置 | 作用 |
|---|---|---|
NFS 服务端(nfs-kernel-server) |
宿主机 dell-R620(192.168.31.249) |
导出三个目录,是本服务唯一的存储后端 |
| 后端存储 | 两个 loop 镜像文件 + 一个 HDD 目录 | 见下表「三个后端」 |
CSI 驱动(csi-driver-nfs 4.13.4) |
集群 kube-system |
csi-nfs-controller(1 副本,管创建/删除/快照)+ csi-nfs-node(DaemonSet,4 节点,管挂载/卸载) |
快照控制器(snapshot-controller) |
集群 kube-system |
处理 VolumeSnapshot / VolumeSnapshotContent 对象 |
| StorageClass | 集群级 | nfs-csi(默认,走 SSD 后端)、nfs-csi-hdd(HDD 后端) |
三个后端(不要混)
| 后端路径 | 承载 | 底层 | 当前水位 |
|---|---|---|---|
/srv/nfs/k8s |
SSD 卷数据(SC nfs-csi) |
loop 镜像 /kvm/nfs/k8s-120g.img(上限 120GiB,位于系统盘 sda3) |
212M / 118G(1%) |
/srv/nfs/k8s-hdd |
HDD 卷数据(SC nfs-csi-hdd) |
loop 镜像 /data/nfs/k8s-hdd-500g.img(上限 500GiB,位于 /data) |
1.1G / 492G(1%) |
/data/nfs-k8s-snap |
快照归档(tar.gz),刻意与卷数据分开 | /data 目录 |
见第 10 章 |
/etc/exports里还有一条/data/nfs/slurm-state,不属于本服务(更早的 Slurm 交付),不要去动它。
依赖谁:宿主机的 sdb1(/data)与系统盘;宿主机上的 4 台 k8s VM 反过来又跑在 /data 上
——存储与计算同宿主,无冗余。
异常时的业务影响
| 异常 | 影响 |
|---|---|
| 宿主机宕机 | 存储与全部计算节点同时中断,所有 Pod 的卷挂载失败,直至宿主恢复 |
nfs-server 停 |
已挂载的 Pod 因 hard 挂载而 I/O 阻塞(不是报错),表现为服务挂起;新 Pod 无法挂载 |
| HDD 后端变慢 | nfs-csi-hdd 上的 Prometheus、openldap 明显变慢(该盘与 VM 的 qcow2 争抢) |
| 系统盘写满 | nfs-csi(SSD 卷)与宿主自身、etcd 一起受影响,风险最高 |
重要性等级:高。它是集群所有有状态业务的底座,且单点无冗余。
2. 接入与凭据¶
地址与端口
| 项 | 值 |
|---|---|
| NFS 服务端地址 | 192.168.31.249(宿主 dell-R620) |
| NFS 端口 | 2049/tcp(NFSv4);20048(mountd)、111(rpcbind,v3 兼容用) |
| 可访问网段 | 192.168.31.0/24(在 /etc/exports 中按网段授权) |
| 协议与认证 | NFSv4.2,sec=sys(AUTH_SYS,无 Kerberos、无账号密码) |
凭据
| 用途 | 存放位置 | 取用命令 |
|---|---|---|
| 集群操作 | ~/.kube/config(宿主机 rain 用户) |
kubectl config current-context → 预期 kubernetes-admin@kubernetes |
| NFS 挂载 | 无凭据,按 IP 网段授权(no_root_squash) |
无需取用 |
| Grafana 登录 | Secret monitoring/grafana-admin |
kubectl -n monitoring get secret grafana-admin -o jsonpath='{.data.admin-user}' \| base64 -d(密码把 admin-user 换成 admin-password) |
| 宿主机提权 | sudo 免密(用于改 /etc/exports、/etc/fstab、/proc/fs/nfsd/threads) |
sudo -n true && echo ok → 预期 ok |
客户端接入示例
宿主机或任何 192.168.31.0/24 内的机器手工挂载(演示用,不要挂到 /data 上):
sudo mkdir -p /mnt/nfs-test
sudo mount -t nfs -o vers=4.2,nconnect=4,hard,timeo=600,retrans=3,noatime \
192.168.31.249:/srv/nfs/k8s /mnt/nfs-test
findmnt -no OPTIONS --target /mnt/nfs-test
# 预期:rw,noatime,vers=4.2,rsize=1048576,wsize=1048576,hard,proto=tcp,nconnect=4,timeo=600,retrans=3,sec=sys…
sudo umount /mnt/nfs-test
在集群里用(PVC 片段,可直接粘贴):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-data
namespace: default
spec:
storageClassName: nfs-csi # 要 HDD 后端就写 nfs-csi-hdd
accessModes: [ReadWriteMany]
resources:
requests:
storage: 5Gi # 仅记账,实际容量不受限(见第 10 章)
3. 日常速查表¶
最高频的 10 条,先看这里。
# 1) 三处导出是否正常(含 sync/async 状态)
sudo exportfs -v
# 2) nfsd 工作线程数(当前应为 32)
sudo cat /proc/fs/nfsd/threads
# 3) 三个后端的容量水位
df -h /srv/nfs/k8s /srv/nfs/k8s-hdd /data
# 4) 每个 PVC 子目录的实际占用
sudo du -sh /srv/nfs/k8s/* /srv/nfs/k8s-hdd/* 2>/dev/null
# 5) 集群存储对象一览
kubectl get sc && kubectl get pv && kubectl get pvc -A
# 6) CSI 组件是否健康
kubectl -n kube-system get pods -l app=csi-nfs-node && kubectl -n kube-system get pods -l app=csi-nfs-controller
# 7) 服务端日志(先定位再过滤,不要整篇拉)
sudo journalctl -u nfs-server --since "1 hour ago" | tail -50
# 8) 重载导出(改完 /etc/exports 后执行,幂等)
sudo exportfs -ra && sudo exportfs -v
# 9) 看某节点上 NFS 挂载的**实际参数**(把 Pod 名换成该节点上的 csi-nfs-node)
kubectl -n kube-system exec csi-nfs-node-<后缀> -c nfs -- nsenter -t 1 -m findmnt -t nfs4 -o TARGET,OPTIONS --noheadings
# 10) 本工程:预演与幂等复核
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --dry-run # 只看不改
.venv/bin/python main.py --config config/tune.yaml --skip-measure # 当前状态应全部 skipped
4. 日常操作¶
4.1 状态检查¶
# 服务端进程与导出
systemctl is-active nfs-server # 预期:active
sudo exportfs -v # 预期:三处导出各一行,含 async,wdelay,rw,no_root_squash
# CSI 组件(4 个 node 插件 + 1 controller + 1 snapshot-controller)
kubectl -n kube-system get pods | grep -E 'csi-nfs|snapshot'
# 预期:csi-nfs-controller-* 5/5 Running;csi-nfs-node-* 4 个 3/3 Running;snapshot-controller-* 1/1 Running
# helm release
helm -n kube-system list | grep nfs-csi # 预期:STATUS=deployed,CHART=csi-driver-nfs-4.13.4
4.2 启动 / 停止 / 重启 NFS 服务¶
# 高危:停止或重启 nfs-server 会让所有客户端 I/O 阻塞(hard 挂载),务必先确认无写入高峰
sudo systemctl status nfs-server
sudo systemctl restart nfs-server # 预期:无输出;随后 systemctl is-active nfs-server 为 active
# 重启后必须复核,threads 由 /etc/nfs.conf 恢复为 32
sudo cat /proc/fs/nfsd/threads # 预期:32(若为 8,见 8.5)
sudo exportfs -v | grep -c async # 预期:4(三处本服务导出 + 1 条 slurm-state)
4.3 日志查看¶
sudo journalctl -u nfs-server --since "30 min ago" | tail -50 # 服务端服务日志
sudo journalctl -k --since "30 min ago" | grep -i nfsd | tail -30 # 内核 nfsd 相关
kubectl -n kube-system logs -l app=csi-nfs-controller -c nfs --tail=100 # 供给与快照编排
kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=100 # 各节点挂载/卸载
tail -50 /home/rain/Documents/k8s_ins/nfs-mount-tuning/logs/tune.log # 参数变更工程的日志
4.4 进入容器或主机排障¶
# 宿主机(就是本机)
ssh rain@192.168.31.249 # 或直接在本机操作
# 在节点上查看所有 NFS 挂载(借用 node 插件的 hostPID/hostMount)
kubectl -n kube-system get pods -l app=csi-nfs-node -o wide # 找到目标节点对应的 Pod
kubectl -n kube-system exec csi-nfs-node-<后缀> -c nfs -- \
nsenter -t 1 -m findmnt -t nfs4 -o TARGET,OPTIONS --noheadings
# 在业务 Pod 里看卷内容(示例:minio)
kubectl -n server exec minio-0 -- ls -l /data | head
4.5 扩缩容与卷操作¶
# 扩容 PVC(改了请求值,但 NFS 后端不强制容量,见第 10 章)
kubectl -n <ns> patch pvc <name> -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
# 新增一个导出供新的 StorageClass 使用(顺序不可颠倒)
sudo mkdir -p /srv/nfs/k8s-new
echo '/srv/nfs/k8s-new 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports
sudo exportfs -ra && sudo exportfs -v
# 再在 nfs-storage/values-nfs-csi.yaml 的 storageClasses 列表里加一项并 helm upgrade
# 删除卷(SC 的 reclaimPolicy=Delete,会自动删后端子目录,高危)
kubectl -n <ns> delete pvc <name> # 预期:pv 与后端子目录随后被回收
sudo ls /srv/nfs/k8s # 核对对应 pvc-<uuid> 目录已消失
5. 配置管理¶
5.1 配置项位置¶
| 层 | 文件 / 对象 | 关键内容 |
|---|---|---|
| 服务端导出 | /etc/exports |
三处导出的选项:rw,async,no_subtree_check,no_root_squash |
| 服务端服务 | /etc/nfs.conf 的 [nfsd] 段 |
threads = 32(重启后恢复线程数) |
| 服务端挂载 | /etc/fstab |
两个 loop 镜像:loop,prjquota,noatime,nofail,x-systemd.device-timeout=10 |
| 客户端参数(权威来源) | nfs-storage/values-nfs-csi.yaml |
两个 StorageClass 的 mountOptions |
| 删除卷所需参数 | nfs-storage/manifests/00-mount-options-secret.yaml |
mountOptions 字符串,必须与上一条一致 |
| 快照落盘位置 | nfs-storage/manifests/02-volumesnapshotclass.yaml |
指向 HDD 的 parameters(每次 helm upgrade 后要重新 apply) |
| 参数变更实现体 | nfs-mount-tuning/config/tune.yaml |
期望状态(导出选项、nfsd 线程、挂载参数、目标工作负载) |
5.2 修改流程(改挂载参数)¶
# 1) 改期望状态
vi /home/rain/Documents/k8s_ins/nfs-mount-tuning/config/tune.yaml
# 2) 预演,确认将变更的内容
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --dry-run
# 3) 执行(会按需静默业务、备份、改配置、patch PV 并重新挂载)
.venv/bin/python main.py --config config/tune.yaml
# 4) 复核幂等
.venv/bin/python main.py --config config/tune.yaml --skip-measure # 预期:全部 skipped
5.3 生效方式与易错点¶
| 易错点 | 说明 |
|---|---|
| 改了 SC 但旧卷没变 | mountOptions 固化在 PV 上,只对新建 PV 生效;已有卷要 patch PV 后重启使用方(本工程会自动做) |
nconnect 改了没效果 |
连接级参数只在重新挂载时生效,仅仅重启容器不够,必须让卷重新 stage(本工程通过重启控制器实现) |
| 两处 Secret/values 不一致 | 删除卷时参数不一致会失败;改一处必须同步另一处(本工程自动同步) |
helm upgrade 后快照落回 SSD |
Helm 会重置 VolumeSnapshotClass 的 parameters,每次升级后必须重新 kubectl apply -f nfs-storage/manifests/02-volumesnapshotclass.yaml,并用 kubectl get volumesnapshotclass csi-nfs-snapclass -o jsonpath='{.parameters}' 复核 |
改 /etc/exports 后忘记重载 |
必须 sudo exportfs -ra;只改文件不重载不生效 |
直接重启 nfs-server |
会瞬时中断所有客户端,属高危;优先用 exportfs -ra(导出变更)与 echo N > /proc/fs/nfsd/threads(线程变更),二者都无需重启服务 |
6. 备份与恢复¶
6.1 备份策略与位置¶
| 对象 | 备份方式 | 位置 |
|---|---|---|
/etc/exports、/etc/nfs.conf、/etc/fstab |
每次变更前由实现体自动 cp -a 并带时间戳 |
原位同目录 *.bak.<YYYYmmdd-HHmmss>(实现体 --rollback 依赖此约定) |
nfs-storage/values-nfs-csi.yaml、00-mount-options-secret.yaml |
同上 | nfs-mount-tuning/backup/nfs-storage/… |
| 台账 | 手工维护 | nfs-mount-tuning/backup/README.md(每份备份的来源、回滚对象、可删时机) |
| 卷数据本身 | 无自动备份 | 只有快照(tar.gz 归档到 /data/nfs-k8s-snap) |
重要认知:NFS 后端没有副本、没有 RAID、没有定时备份;三处导出共用同一台宿主的磁盘。 真正意义上的数据保护只有「打快照」,且快照是 tar 归档、恢复耗时与数据量成正比。
6.2 手工备份命令¶
# 配置文件
sudo cp -a /etc/exports "/etc/exports.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)"
sudo cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d-%H%M%S)"
# 打一份卷快照(示例:server/minio-data)
kubectl -n server get pvc minio-data # 先拿到 PVC 名与状态,预期 Bound
# 参考 nfs-storage/examples/03-snapshot-and-restore.yaml 的写法创建 VolumeSnapshot
kubectl get volumesnapshot -A # 预期:READYTOUSE=true
sudo find /data/nfs-k8s-snap -maxdepth 2 -name '*.tar.gz' -exec ls -lh {} \; # 归档落在 HDD
6.3 恢复步骤(从配置备份回滚)¶
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --rollback # 预期:三行「已回滚 … <- …」,最后「回滚成功」
sudo exportfs -v && sudo cat /proc/fs/nfsd/threads # 复核:导出与线程数回到旧值
集群侧配置的恢复见 docs/SOP.md 第 7.2 节(含 values / Secret 的还原与 helm 升级)。
6.4 演练记录¶
| 日期 | 演练内容 | 结果 |
|---|---|---|
| 2026-09-19 | 变更前采集基线、变更后复测;四阶段幂等复跑 | 通过(详见 docs/SOP.md 第 6 章) |
| — | 从快照恢复整卷到新 PVC | 未演练,建议安排 |
7. 监控与告警¶
关键指标与查看入口
| 指标 | 查看方式 | 关注阈值 |
|---|---|---|
| 三处后端使用率 | df -h /srv/nfs/k8s /srv/nfs/k8s-hdd /data |
/srv/nfs/k8s 使用率 > 80% 需处理(它与宿主 / 同盘) |
| 单个 PVC 占用 | sudo du -sh /srv/nfs/k8s/* /srv/nfs/k8s-hdd/* |
单目录接近上限即需清理或迁移 |
| nfsd 线程数 | sudo cat /proc/fs/nfsd/threads |
应为 32;变为 8 说明 /etc/nfs.conf 未生效或被改回 |
| 导出状态 | sudo exportfs -v |
应有三处本服务导出且含 async |
| 元数据延迟 | 见下方「延迟自测」 | 同后端较基线劣化 > 20% 需排查 |
| 磁盘压力(HDD) | iostat -x 1 观察 /data 所在盘的 %util、await |
%util 长期 > 90% 说明 HDD 是瓶颈 |
| CSI 组件健康 | kubectl -n kube-system get pods \| grep csi-nfs |
有非 Running 或重启数持续增长 |
延迟自测(不装任何东西)
# 重新跑一次度量,与 logs/measure-before-*.json、measure-after-*.json 对比
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --stages measure-after
# 报告落在 logs/measure-after-*.json;看 fio 的 p50/p99 与 mountstats 的 avg_rtt_ms
日志位置:服务端 /var/log/syslog 与 journalctl -u nfs-server;集群侧见 4.3。
Grafana / Prometheus 入口(均为 ClusterIP,用端口转发访问)
kubectl -n monitoring port-forward svc/kube-prometheus-stack-grafana 3000:80 # 浏览器 http://localhost:3000
kubectl -n monitoring port-forward svc/kube-prometheus-stack-prometheus 9090:9090 # http://localhost:9090
kubectl -n monitoring port-forward svc/kube-prometheus-stack-alertmanager 9093:9093
kubectl -n kube-system port-forward svc/headlamp 8080:80 # 集群面板
这套监控自身也跑在本存储上:Grafana/Alertmanager 在
nfs-csi,Prometheus 在nfs-csi-hdd。 存储故障时监控很可能同时不可用,此时以宿主机命令行(本章与第 3 章)为主要手段。
8. 故障处置¶
场景 1:Pod 无法挂载 NFS 卷 / 容器写不进卷¶
症状:Pod 卡在 ContainerCreating;kubectl describe pod 里有 FailedMount;
或容器启动后报 Permission denied。
诊断
kubectl -n <ns> describe pod <pod> | grep -A5 -iE 'failedmount|mount failed' # 预期:能看到具体挂载报错
kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=50 | grep -iE 'error|fail|denied'
sudo exportfs -v | grep <对应后端路径> # 预期:导出存在且含 rw
sudo systemctl is-active nfs-server # 预期:active
处置
- 导出缺失 → 检查
/etc/exports是否有该路径一行,补上后sudo exportfs -ra。 - 服务端不可达 → 从节点侧验证:
kubectl -n kube-system exec csi-nfs-node-<后缀> -c nfs -- nsenter -t 1 -m timeout 5 bash -c 'cat < /dev/null > /dev/tcp/192.168.31.249/2049' && echo ok(预期ok)。 - 写权限问题:本存储依赖导出带
no_root_squash,并由 kubelet 应用 Pod 的fsGroup改写 GID。 若改了导出参数(去掉no_root_squash)会出现「挂载成功但写不进」,需改回并exportfs -ra。 - 单个 Pod 异常而其它正常 → 删除该 Pod 让其重建:
kubectl -n <ns> delete pod <pod>。
验证:kubectl -n <ns> get pod <pod> 为 Running,kubectl -n <ns> exec <pod> -- touch /<挂载点>/.write-test && echo ok 返回 ok。
场景 2:Pod 访问卷时「卡住不动」(I/O hang)¶
症状:容器内 ls / 读写命令长时间无返回;探针失败但 Pod 状态仍是 Running;
日志里没有明显报错。
诊断
systemctl is-active nfs-server # 预期:active(若是 failed/inactive 即找到原因)
sudo exportfs -v | wc -l # 预期:4 行(导出存在)
kubectl -n kube-system exec csi-nfs-node-<后缀> -c nfs -- \
nsenter -t 1 -m findmnt -t nfs4 -o TARGET,OPTIONS --noheadings | head # 能看到挂载,说明是服务端阻塞而非卸载
sudo journalctl -u nfs-server --since "10 min ago" | tail -30
处置
- 这是
hard挂载的设计行为:服务端不可用时 I/O 阻塞而不是报错,以避免数据损坏。 因此优先恢复服务端(sudo systemctl restart nfs-server),而不是改客户端参数。 - 服务端恢复后 Pod 会自动继续(通常无需重启 Pod)。
- 若长时间无法恢复且业务必须中断,才考虑临时改
soft(不推荐,会带来数据损坏风险), 且必须在事后改回并重启使用方。
验证:容器内 ls 恢复正常;kubectl -n kube-system logs -l app=csi-nfs-node -c nfs --tail=20 无新报错。
场景 3:删除 PVC 后后端目录没回收 / 空间不下降¶
症状:kubectl delete pvc 成功,PV 已消失,但 df -h 没有变化,后端还留着 pvc-<uuid> 目录。
诊断
kubectl -n <ns> get pvc <name> # 预期:NotFound(已删)
kubectl get pv | grep pvc-<uuid> # 预期:无(PV 已回收)
sudo ls -ld /srv/nfs/k8s/pvc-<uuid> # 预期:目录不存在;若仍存在即为残留
kubectl -n kube-system logs -l app=csi-nfs-controller -c nfs --tail=100 | grep -iE 'delete|error'
sudo du -sh /srv/nfs/k8s/* | sort -h | tail -5 # 找出实际占空间的大目录
处置
- 先确认目录确实属于已删除的 PV(对照
kubectl get pv已无该 uuid),不要凭猜删除。 - 残留通常来自删除时挂载参数不一致(SC 与 Secret 的
mountOptions不同步)导致驱动无法挂载后端清理。 复核:kubectl -n kube-system get secret nfs-mount-options -o jsonpath='{.data.mountOptions}' | base64 -d与kubectl get sc nfs-csi -o jsonpath='{.mountOptions}'是否一致。 - 确认无误后手工清理(高危,路径必须精确,禁止通配):
sudo ls -ld /srv/nfs/k8s/pvc-<uuid> # 再确认一次目标
sudo rm -rf /srv/nfs/k8s/pvc-<uuid> # 高危:删除后端卷目录,不可恢复
- 若删除的是快照归档,位置在
/data/nfs-k8s-snap/snapshot-<uid>/。
验证:sudo du -sh /srv/nfs/k8s 数值下降;df -h /srv/nfs/k8s 可用空间增加。
场景 4:nfs-csi-hdd 上的应用(Prometheus/openldap)极慢¶
症状:Prometheus 查询卡顿、抓取超时;openldap 认证偶发超时;nfs-csi 上的应用却正常。
诊断
findmnt -no SOURCE,OPTIONS --target /srv/nfs/k8s-hdd # 确认后端与挂载参数(应含 noatime)
iostat -x 1 3 | grep -A1 '^Device' | tail -5 # 预期:/data 所在盘 %util 高、await 大
sudo du -sh /srv/nfs/k8s-hdd/* | sort -h | tail -5 # 找出占用大的卷
uptime # 宿主负载(4 台 VM 也在这台机器上)
处置
- 这是已知瓶颈:
/data那块 HDD 同时承载 4 台 k8s VM 的 qcow2 与 HDD 卷数据,机械盘随机 IO 天生差。 - 缓解手段(按优先级):把 IO 敏感的业务迁到
nfs-csi(SSD);降低 Prometheus 保留期/抓取频率; 确认已启用noatime(本服务默认已启用)。 - 不要在 HDD 上跑数据库类小随机 IO。
验证:iostat -x 1 的 await、%util 回落;应用侧延迟恢复。
场景 5:快照落到了 SSD,撑满 120GiB 预算¶
症状:打了快照后 /srv/nfs/k8s 出现 snapshot-* 目录;df -h 显示 SSD 后端快速上涨。
诊断
kubectl get volumesnapshotclass csi-nfs-snapclass -o jsonpath='{.parameters}{"\n"}' # 预期:含 server/share=/data/nfs-k8s-snap
sudo ls -l /srv/nfs/k8s | grep snapshot # 预期:无输出(若有,说明 parameters 被重置)
sudo find /data/nfs-k8s-snap -maxdepth 2 | head
处置
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/manifests/02-volumesnapshotclass.yaml
kubectl get volumesnapshotclass csi-nfs-snapclass -o jsonpath='{.parameters}{"\n"}' # 复核已指回 HDD
若 SSD 侧已有归档:确认新快照已分流后,手工清理 SSD 上的 snapshot-* 目录(高危,路径精确)。
验证:新打一个快照,确认归档出现在 /data/nfs-k8s-snap/snapshot-<uid>/ 而非 SSD。
场景 6:nfsd 线程数变成 8 / 导出变成 sync¶
症状:重启宿主机或他人改过配置后,sudo cat /proc/fs/nfsd/threads 显示 8;
或 sudo exportfs -v 里三处导出回到 sync。
诊断
grep -A3 '^\[nfsd\]' /etc/nfs.conf | grep -v '^#' # 预期:threads = 32
grep -E '/srv/nfs/k8s|nfs-k8s-snap' /etc/exports # 预期:三行都含 async
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning && .venv/bin/python main.py --config config/tune.yaml --dry-run
# 预期:若有漂移,会列出「检测到状态漂移: …」
处置
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --stages server # 收敛回期望状态(幂等)
验证:sudo cat /proc/fs/nfsd/threads 为 32;sudo exportfs -v 三处含 async;
main.py --skip-measure 复跑全部 skipped。
9. 安全与权限¶
账号与权限模型
| 层 | 模型 |
|---|---|
| NFS 导出授权 | 按 IP 网段 192.168.31.0/24 授权,sec=sys(AUTH_SYS)——无账号、无密码、无 Kerberos |
no_root_squash |
已启用:网段内任意主机的 root 可读写全部导出数据。这是 fsGroup 能生效的前提 |
| 集群 RBAC | 使用 cluster-admin(kubeconfig /home/rain/.kube/config) |
| 宿主提权 | sudo 免密 |
风险与当前取舍
no_root_squash+ 网段授权 = 内网横向风险:网段内任何机器都能以 root 读写所有卷。 当前环境为隔离测试网段,接受此取舍。若要收紧:改/etc/exports为all_squash,anonuid=<uid>,anongid=<gid>并为 Pod 设置runAsUser/runAsGroup,改后需sudo exportfs -ra并重启使用方。- 无传输加密:
sec=sys下数据在网内明文。跨不可信网络必须改用sec=krb5*或走加密隧道。 async导出:如第 10 章所述会放大掉电丢数据风险。- 凭据一律不写入本文档;Grafana 等凭据按第 2 章的取用命令从 Secret 读取。
证书与密钥:本服务不使用 TLS/证书(NFS 层无证书)。集群侧组件(Prometheus、Alertmanager)的 TLS Secret 与
monitoring 命名空间内其它 Secret 由 kube-prometheus-stack 管理,轮换遵循该 chart 的方式,与 NFS 无关。
10. 容量与性能¶
当前水位基线(2026-09-19 实测)
| 对象 | 大小 | 已用 | 可用 |
|---|---|---|---|
/srv/nfs/k8s(SSD 卷) |
118G | 212M(1%) | 112G |
/srv/nfs/k8s-hdd(HDD 卷) |
492G | 1.1G(1%) | 485G |
/data(宿主 HDD,含快照归档与 VM qcow2) |
1.8T | 116G(7%) | 1.6T |
各卷占用(2026-09-19 实测)
| 后端 | 目录 | 占用 | 归属 |
|---|---|---|---|
/srv/nfs/k8s |
pvc-4323db6c-… |
159M | mariadb |
/srv/nfs/k8s |
pvc-5ca538f8-… |
53M | grafana |
/srv/nfs/k8s |
pvc-467e1cc4-… |
704K | minio |
/srv/nfs/k8s |
pvc-3fac44ea-… |
8.0K | alertmanager |
/srv/nfs/k8s-hdd |
pvc-c44a8887-… |
1.1G | prometheus |
/srv/nfs/k8s-hdd |
其余 6 个 pvc-* |
各 64K–164K | openldap 系列 |
扩容触发条件与操作
- 触发:
df -h /srv/nfs/k8s使用率 > 80%(它与系统盘sda3同盘,同时影响宿主与 etcd); 或某卷目录占用接近上限。 - 操作:受 loop 镜像上限约束,扩容需要放大镜像文件:
sudo truncate -s <新大小> /kvm/nfs/k8s-120g.img && sudo resize2fs /kvm/nfs/k8s-120g.img(高危,会短暂影响导出,须在窗口内执行并先备份/确认空间充足)。 - 也可把新业务直接指向
nfs-csi-hdd(500GiB 上限、空间充裕,代价是 IO 慢)。
已知瓶颈与优化方向
| 瓶颈 | 说明 | 方向 |
|---|---|---|
| PVC 请求值不生效 | 后端无 CSI 层配额,PVC 的 5Gi 只是记账,可写满整个导出 |
需要真限额时用 ext4 prjquota 手工给子目录设配额;容量规划按「导出总量」而非「PVC 请求和」 |
| HDD 后端 IO 差 | 与 4 台 VM 的 qcow2 争抢同一块盘,且叠加 loop 层 | IO 敏感业务放 SSD;不要在 HDD 上跑数据库 |
| 单点无冗余 | 存储与计算同宿主,宿主故障即全断 | 学习环境可接受;要真冗余需把 NFS 服务端迁到独立主机 |
async 的持久化代价 |
服务端不落盘即确认,掉电可能丢最近写入 | 数据库类如需要强持久化,按导出粒度改回 sync 并 exportfs -ra |
| 快照是 tar 归档 | 非 COW,创建/恢复耗时与数据量成正比 | 数据量大时可关闭压缩(--set controller.enableSnapshotCompression=false),换取更快但占更多空间 |
11. 附录¶
术语表¶
| 术语 | 含义 |
|---|---|
| StorageClass(SC) | 集群里的「存储类型」模板,PVC 通过它动态申请卷 |
| PV / PVC | 实际的卷对象(PV)与用户的申请(PVC) |
| RWX / RWO | 多节点读写 / 单节点读写;NFS 原生支持 RWX |
nconnect |
同一挂载使用多条 TCP 连接并行发 RPC,提升并发元数据性能 |
vers / nfsvers |
协议版本;配置里写 nfsvers=,内核回显为 vers= |
hard 挂载 |
服务端不可用时 I/O 阻塞而非报错,优先数据一致性 |
sync / async 导出 |
服务端是否在回复前落盘;async 更快但有掉电丢数据风险 |
no_root_squash |
客户端 root 保持 root 身份,是 fsGroup 生效的前提 |
| loop 镜像 | 用文件当作块设备挂载(/kvm/nfs/k8s-120g.img 等),容量上限即文件大小 |
prjquota |
ext4 的目录级配额特性,本服务已启用但未强制 |
相关文档与交付物¶
| 路径(相对项目根) | 内容 |
|---|---|
nfs-mount-tuning/docs/SOP.md |
2026-09-19 挂载参数优化的变更档案(背景/目标/步骤/验证/回滚/风险) |
nfs-mount-tuning/docs/INCIDENT-20260919-mount-option-judgement.md |
变更期间一次判定误报的排障记录 |
nfs-mount-tuning/backup/README.md |
备份台账 |
nfs-mount-tuning/config/tune.yaml |
期望状态配置(日常调整改这里) |
nfs-storage/SOP.md |
2026-09-17 存储类交付档案(含初始参数与验证清单) |
nfs-storage/examples/ |
PVC 供给、跨节点读写、快照恢复、HDD 卷的验证清单 |
nfs-mount-tuning/logs/ |
参数变更运行日志与 before/after 度量报告 |
修订记录¶
| 日期 | 修改人 | 变更内容 |
|---|---|---|
| 2026-09-19 | 运维(AI 协助) | 首次建立 RUNBOOK;覆盖概览、接入、速查、日常操作、配置、备份、监控、6 个故障场景、安全、容量与性能 |
来源:
/home/rain/Documents/k8s_ins/nfs-mount-tuning/docs/RUNBOOK.md| 最后更新:2026-09-19 16:46(由build/sync.py同步,请勿在站点侧修改)