NFS 存储类(StorageClass)交付 SOP¶
交付目录:
nfs-storage/变更类型:C 类生产变更(宿主机 NFS 服务端 + 集群侧 CSI 驱动与存储类) 首次交付:2026-09-17 状态基准:本文档已按 2026-09-19 挂载参数调优后的当前生效状态收敛,可直接作为重建、日常运维与回滚的依据 实现方式声明:本方案按用户明确指令未采用 Python 部署工程,实现体为nfs-storage/values-nfs-csi.yaml与nfs-storage/manifests/下的清单。第 5 章的 shell 代码块属文档呈现,用于阅读、复核与手工复现, 真正决定最终状态的是实现体,以实现体为准。 边界声明:挂载参数调优由独立项目nfs-mount-tuning/完成(其实现体为 Python 工程,过程见该项目docs/SOP.md)。 本文档只记录其最终生效值,不重复其变更过程;两者若出现不一致,以本文件第 4 章实况核对为准并回查该项目日志。 与 RUNBOOK 的分工:本文只回答"这次改了什么、怎么改、怎么验、怎么退"。 服务的日常使用与排障(速查命令、故障场景处置、监控入口、容量扩缩操作)见nfs-storage/RUNBOOK.md,本文不重复。
目录¶
1. 背景¶
- 集群
k8s_ins(v1.36.4,4 节点:1 control-plane + 3 worker)在本次交付前没有任何 StorageClass、PV、PVC, 有状态应用无法落地;早先规划的 GlusterFS 方案因上游停止维护被放弃(集成层已被 K8s 移除、Heketi 归档)。 - 历史经验:早期 GlusterFS 集群长期不可用,实际承担共享存储的一直是 NFS。故本环境的真实需求是 一个稳定、可 RWX、可动态供给的 StorageClass,而非分布式块存储。
- 宿主机
dell-R620(Ubuntu,32 线程 / 117G 内存)是 4 台 k8s VM 与 openwrt 的 KVM 宿主, 有独立 SSD 系统盘与 1.8T HDD 数据盘,具备提供 NFS 服务端的条件。 - 交付后按实际使用演进过两轮,构成本文档的当前状态:
- 快照落盘分流:驱动默认把快照归档写到源卷所在 share,会与卷数据抢 SSD 的 120GiB 预算,故将归档分流到 HDD;
- 挂载参数调优:HDD 后端在
sync导出下元数据 RTT 高达 60–80ms,改async+nfsvers=4.2+nconnect=4后降至 0.2–0.4ms 量级(详见第 6 章性能基线)。 - 触发条件:为测试与学习场景提供默认 StorageClass,并保留快照/恢复能力,同时满足元数据延迟敏感性要求。
2. 目标¶
变更完成后应持续维持以下可验证状态:
| 编号 | 目标状态 | 验证方式 |
|---|---|---|
| G1 | 宿主机存在三处服务于本方案的导出:/srv/nfs/k8s(SSD)、/srv/nfs/k8s-hdd(HDD)、/data/nfs-k8s-snap(快照),均为 async |
第 6 章 V1 |
| G2 | nfsd 线程数为 32,且重启后仍生效 | 第 6 章 V2 |
| G3 | 两个 loop 后端挂载含 noatime,prjquota,容量上限分别为 120GiB / 500GiB |
第 6 章 V3 |
| G4 | 集群存在 nfs-csi(默认, SSD) 与 nfs-csi-hdd(非默认, HDD) 两个 StorageClass |
第 6 章 V4 |
| G5 | 客户端挂载参数为 nfsvers=4.2, nconnect=4, hard, timeo=600, retrans=3, noatime,且 SC / Secret / 已有 PV 三处一致 |
第 6 章 V5、V6 |
| G6 | PVC 可自动供给并 Bound,跨节点 RWX 读写正常 | 第 6 章 V8、V9 |
| G7 | 支持快照与从快照恢复,且归档落在 HDD,不占用 SSD 预算 | 第 6 章 V7 |
| G8 | 删除 PVC 时后端子目录被回收 | 第 6 章 V10 |
| G9 | 宿主机重启后 loop 挂载、导出、nfsd 线程数自动恢复 | 第 6 章 V11(尚未验证) |
| G10 | 全部操作幂等:重复执行零变更;且不影响服务端上的共存业务(Slurm) | 第 6 章 V12 |
3. 前置条件¶
权限
- 宿主机需 root,且
sudo -n免密可用(当前已确认)。 - 集群需
cluster-admin(当前上下文kubernetes-admin@kubernetes)。
工具与依赖
| 依赖 | 位置 | 当前版本 | 说明 |
|---|---|---|---|
| helm | 宿主机 | v3.16.3(/usr/local/bin/helm) |
本次交付时安装 |
| kubectl | 宿主机 | v1.36.4 | 已有 |
| nfs-kernel-server | 宿主机 | 1:2.6.4-3ubuntu5.1 | 本次交付时安装 |
| csi-driver-nfs chart | 本地固化 | 4.13.4 | nfs-storage/charts/csi-driver-nfs/ |
| 节点侧 nfs 客户端 | 无需安装 | — | 驱动镜像基于 debian-base 且显式安装 mount nfs-common(官方 Dockerfile v4.13.4),挂载由容器执行 |
执行前检查项
- 宿主系统盘可用空间充足:
sda3220G,实测可用约 160G(120GiB 镜像为按需增长)。 /data(HDD)已挂载、可用约 1.7T;其上同时有运行中的 VM qcow2、本方案的 HDD 后端与快照目录、 以及 Slurm 状态导出。禁止对/data本体做任何umount操作。- 集群节点可拉取
registry.k8s.io镜像(节点经 openwrt 旁路,实测可直连)。 - 集群内快照 CRD 由本方案以
kubectl独立管理,确认无第二套实现重复注册。 - 变更服务端共享配置(导出参数、
/etc/nfs.conf的 nfsd 线程、nfs-server服务)前, 须确认 Slurm 与本方案全部业务的影响面(见第 4 章"共存服务与共享资源")。 - 涉及导出参数或挂载参数变更时,先停写敏感业务(当前为
server/mariadb、server/minio),流程见nfs-mount-tuning/docs/SOP.md。
4. 影响范围¶
主机与组件
| 对象 | 角色 | 影响 |
|---|---|---|
dell-R620 sda3/SSD(192.168.31.249) |
NFS 服务端:SSD 卷后端 | 120GiB 镜像 /kvm/nfs/k8s-120g.img + loop 挂载 /srv/nfs/k8s;占用系统盘空间(按需增长) |
dell-R620 /data/HDD |
NFS 服务端:HDD 卷后端 + 快照归档 | 500GiB 镜像 /data/nfs/k8s-hdd-500g.img + loop 挂载 /srv/nfs/k8s-hdd;快照目录 /data/nfs-k8s-snap |
dell-R620 /etc/exports、/etc/nfs.conf、nfs-server 服务 |
共享资源 | 本方案与 Slurm 导出共用:改导出、改 nfsd 线程、启停服务会同时影响 Slurm |
| k8s-master-01 / node-01/02/03 | CSI node 插件(DaemonSet) | 每节点常驻 3 个容器(含 master,其 tolerations 容忍全部污点),约 10m CPU / 20Mi 起 |
| kube-system | 驱动组件 | controller Deployment、snapshot-controller Deployment、SA / ClusterRole / Secret |
| 集群存储对象 | 本方案对象 | 两个 StorageClass、CSIDriver nfs.csi.k8s.io、3 个 snapshot CRD、VolumeSnapshotClass |
是否需要停服
- 常态运维(日常增删卷、查日志、改 values 中与参数无关的项):不需要停服。
- 变更导出参数(
sync/async)或客户端挂载参数:需要先停写敏感业务,并按nfs-mount-tuning/docs/SOP.md的静默流程执行;已完成卷需逐个 patch PV 并重启使用方(见第 5 章 5.3 与风险 R11)。 - 重启
nfs-server服务:会同时中断本方案与 Slurm 的存储访问,非必要不做(nfsd 线程数支持热调整,无需重启)。
窗口期:无强制窗口,建议低峰执行。/data 全程不做 umount,运行中的 VM 不受影响。
在跑的业务(2026-09-19 实况,共 11 个 NFS PV)
| 命名空间 | PVC | StorageClass | 容量 | 使用方 |
|---|---|---|---|---|
| server | minio-data | nfs-csi | 50Gi | MinIO(GitLab 对象存储) |
| server | storage-mariadb-0 | nfs-csi | 20Gi | MariaDB(数据库) |
| monitoring | kube-prometheus-stack-grafana | nfs-csi | 10Gi | Grafana |
| monitoring | alertmanager-…-db-…-0 | nfs-csi | 10Gi | Alertmanager |
| monitoring | prometheus-…-db-…-0 | nfs-csi-hdd | 100Gi | Prometheus |
| server | conf-openldap-0 / data-openldap-0 / backups-openldap-0 | nfs-csi-hdd | 1/8/8Gi | OpenLDAP(server 一套) |
| ldap | conf-openldap-0 / data-openldap-0 / backups-openldap-0 | nfs-csi-hdd | 1/8/8Gi | OpenLDAP(ldap 一套,与 server 并存,疑为迁移残留,待确认) |
此外 GitLab 本体的 gitaly/postgres/redis 使用手工 PV + 集群既有的 local-ssd(非本方案引入)。
任何对
/srv/nfs/k8s、/srv/nfs/k8s-hdd、/data/nfs-k8s-snap、/etc/exports、/etc/nfs.conf或nfs-server服务的操作,都会直接波及上表业务;执行前须确认其 PVC 处于 Bound 且无写入高峰。
共存服务与共享资源(Slurm)
- 服务端上还有第四处导出
/data/nfs/slurm-state(HDD 裸目录,非 loop 镜像),供 Slurm 集群存放slurmctld-state(uid/gid 64030)。该导出不属于本方案的交付与回滚范围,但与本方案共享以下资源:
| 共享资源 | 共享方式 | 影响 |
|---|---|---|
nfs-server 服务 |
同一进程同时对外提供全部导出 | 启停服务会同时中断 Slurm 与本方案 |
/etc/exports 文件 |
同一文件承载全部导出行 | 编辑、重载、全局撤销导出都会影响 Slurm |
/etc/nfs.conf 的 [nfsd] threads |
同一参数服务所有导出 | 调低线程数会同时拖慢 Slurm 的 NFS 访问 |
- 因此本方案第 7 章的回滚只按导出粒度操作,不使用
exportfs -ua、不stop nfs-server(详见 7.2)。
单机提示:存储服务端与全部计算节点同宿主,无冗余。宿主故障 = 存储与计算同时中断, 故第 6、7 章必须写实、可执行。
5. 分步操作¶
对应实现体:
nfs-storage/values-nfs-csi.yaml(客户端参数与两个 SC)、nfs-storage/manifests/*.yaml(Secret、CRD、快照类)。宿主机侧无脚本实现体,命令序列以本节为准。 高危动作已标注「高危」并给出对应备份命令。以下序列按当前生效参数编写,幂等可重复执行。 关键步骤后给出预期输出;若输出不符,先看该步注释里的判断依据,跨步骤问题转RUNBOOK.md第 8 章对应场景。 文中的<节点IP>、<pv名>、<命名空间>等占位符取值来源在各处就地说明。
5.1 宿主机侧(服务端)¶
# ---- 步骤 0:备份(高危动作前置,必须先做)----
sudo cp -n /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d)" # 保护启动挂载
sudo cp -n /etc/exports "/etc/exports.bak.$(date +%Y%m%d)" # 保护导出;首次执行时该文件可能不存在
sudo cp -n /etc/nfs.conf "/etc/nfs.conf.bak.$(date +%Y%m%d)" # 保护 nfsd 线程等全局配置
# ---- 步骤 1:安装 NFS 服务端(高危:装系统包)----
dpkg -s nfs-kernel-server >/dev/null 2>&1 || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nfs-kernel-server
# ---- 步骤 2:创建后端镜像并格式化(高危:占用磁盘空间)----
# 2.1 SSD 卷后端:120GiB,位于系统盘 sda3
sudo mkdir -p /kvm/nfs
[ -f /kvm/nfs/k8s-120g.img ] || sudo fallocate -l 120G /kvm/nfs/k8s-120g.img
sudo blkid /kvm/nfs/k8s-120g.img >/dev/null 2>&1 || \
sudo mkfs.ext4 -F -O project -L k8s-nfs /kvm/nfs/k8s-120g.img # -O project 是 prjquota 的前提
# 2.2 HDD 卷后端:500GiB,位于 /data(sdb1)
sudo mkdir -p /data/nfs
[ -f /data/nfs/k8s-hdd-500g.img ] || sudo fallocate -l 500G /data/nfs/k8s-hdd-500g.img
sudo blkid /data/nfs/k8s-hdd-500g.img >/dev/null 2>&1 || \
sudo mkfs.ext4 -F -O project -L k8s-nfs-hdd /data/nfs/k8s-hdd-500g.img
# 2.3 快照归档目录:HDD 裸目录(非镜像,弹性占用 /data 剩余空间)
[ -d /data/nfs-k8s-snap ] || sudo mkdir -p /data/nfs-k8s-snap
# ---- 步骤 3:挂载并持久化(高危:改 fstab)----
# 3.1 SSD 后端
sudo mkdir -p /srv/nfs/k8s
findmnt -no SOURCE /srv/nfs/k8s >/dev/null 2>&1 || sudo mount -o loop,prjquota /kvm/nfs/k8s-120g.img /srv/nfs/k8s
grep -qF '/kvm/nfs/k8s-120g.img' /etc/fstab || printf '%s\n' \
'/kvm/nfs/k8s-120g.img /srv/nfs/k8s ext4 noatime,loop,prjquota,nofail,x-systemd.device-timeout=10 0 2' | sudo tee -a /etc/fstab
sudo tune2fs -m 1 /kvm/nfs/k8s-120g.img # 保留块 5%→1%,释放约 4.7G
# 3.2 HDD 后端
sudo mkdir -p /srv/nfs/k8s-hdd
findmnt -no SOURCE /srv/nfs/k8s-hdd >/dev/null 2>&1 || sudo mount -o loop,prjquota /data/nfs/k8s-hdd-500g.img /srv/nfs/k8s-hdd
grep -qF '/data/nfs/k8s-hdd-500g.img' /etc/fstab || printf '%s\n' \
'/data/nfs/k8s-hdd-500g.img /srv/nfs/k8s-hdd ext4 noatime,loop,prjquota,nofail,x-systemd.device-timeout=10 0 2' | sudo tee -a /etc/fstab
sudo tune2fs -m 1 /data/nfs/k8s-hdd-500g.img
# 3.3 若后端已挂载但缺少 noatime,用 remount 补齐(不卸载,不影响在用文件)
findmnt -no OPTIONS /srv/nfs/k8s | grep -q noatime || sudo mount -o remount,noatime /srv/nfs/k8s
findmnt -no OPTIONS /srv/nfs/k8s-hdd | grep -q noatime || sudo mount -o remount,noatime /srv/nfs/k8s-hdd
sudo systemctl daemon-reload && sudo mount -a
# ---- 步骤 4:配置导出(高危:对外提供存储;会写入与 Slurm 共用的 /etc/exports)----
# 注意:仅追加/确保本方案的三行,不要重写整个文件,避免破坏 /data/nfs/slurm-state 的导出行
grep -qF '/srv/nfs/k8s ' /etc/exports || printf '%s\n' '/srv/nfs/k8s 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports
grep -qF '/srv/nfs/k8s-hdd' /etc/exports || printf '%s\n' '/srv/nfs/k8s-hdd 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports
grep -qF '/data/nfs-k8s-snap' /etc/exports || printf '%s\n' '/data/nfs-k8s-snap 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)' | sudo tee -a /etc/exports
sudo exportfs -ra # 重载全部导出(幂等,不影响已有导出)
sudo exportfs -v # 确认本方案三处 + Slurm 导出均在列且为 async
# 预期输出(四处,顺序不限):
# /srv/nfs/k8s 192.168.31.0/24(async,...,rw,...,no_root_squash,...)
# /srv/nfs/k8s-hdd 192.168.31.0/24(async,...)
# /data/nfs-k8s-snap 192.168.31.0/24(async,...)
# /data/nfs/slurm-state 192.168.31.0/24(async,...) <- Slurm 的,不得消失
# 不符时:缺少本方案任一行 → 检查上一条 tee 是否写入成功;缺少 Slurm 行 → 立即用
# /etc/exports.bak.* 还原(见 7.2),切勿继续后续步骤
# ---- 步骤 5:nfsd 线程数(热调整,无需重启服务)----
printf '32\n' | sudo tee /proc/fs/nfsd/threads # 运行时生效
sudo cat /proc/fs/nfsd/threads # 应为 32
# 持久化到 /etc/nfs.conf(幂等)。必须用 nfsconf 写入,它会把键放进正确的 [nfsd] 段;
# 不要用 tee -a 追加:该文件末尾是 [svcgssd] 段,追加会写错段,导致重启后不生效
sudo nfsconf --set nfsd threads 32
nfsconf --get nfsd threads # 解析确认:应输出 32
# 预期输出:32
# 不符时:检查是否误写入其它段——用 `nfsconf --dump -` 查看 [nfsd] 段内容,修正后用同样命令重设
sudo systemctl enable --now nfs-server # enable 保证开机自启(不重启运行中的服务)
# ---- 步骤 6:本机自测(确认导出可用再动集群)----
sudo mkdir -p /mnt/nfs-probe
sudo mount -t nfs -o vers=4.2,nconnect=4,hard,timeo=600,retrans=3 192.168.31.249:/srv/nfs/k8s /mnt/nfs-probe
head -c 1048576 /dev/urandom > /tmp/probe.bin && sudo cp /tmp/probe.bin /mnt/nfs-probe/probe.bin
sudo md5sum /mnt/nfs-probe/probe.bin /tmp/probe.bin # 两个 md5 必须一致
sudo rm -f /mnt/nfs-probe/probe.bin && sudo umount /mnt/nfs-probe && sudo rmdir /mnt/nfs-probe
5.2 集群侧¶
# ---- 步骤 7:安装 helm(宿主机,已装则跳过)----
command -v helm >/dev/null || { curl -fsSL -o /tmp/helm.tgz https://get.helm.sh/helm-v3.16.3-linux-amd64.tar.gz && \
tar -xzf /tmp/helm.tgz linux-amd64/helm && sudo install -m 0755 linux-amd64/helm /usr/local/bin/helm; }
# ---- 步骤 8:固定 chart 版本到本地(便于离线重建与版本锁定)----
cd nfs-storage
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm pull csi-driver-nfs/csi-driver-nfs --version 4.13.4 --untar --untardir charts
# ---- 步骤 9:先装 snapshot CRD(顺序不可颠倒,否则 Helm 无法解析 VolumeSnapshotClass)----
helm template nfs-csi charts/csi-driver-nfs -n kube-system -f values-nfs-csi.yaml \
--show-only templates/crd-csi-snapshot.yaml | kubectl apply --server-side=true -f -
# ---- 步骤 10:挂载参数 Secret(DeleteVolume / DeleteSnapshot 需要,且须与 SC 参数一致)----
kubectl apply -f manifests/00-mount-options-secret.yaml
# ---- 步骤 11:部署驱动与两个 StorageClass(高危:部署特权 DaemonSet)----
helm upgrade --install nfs-csi charts/csi-driver-nfs -n kube-system \
-f values-nfs-csi.yaml --wait --timeout 6m
# 预期输出:末行 STATUS: deployed(首次为 Installing it now 后接 deployed)
kubectl -n kube-system get pods | grep -E 'csi-nfs|snapshot'
# 预期:csi-nfs-controller-* 5/5、csi-nfs-node-*(4 个,含 master-01)各 3/3、snapshot-controller-* 1/1
# 不符时:ImagePullBackOff → 检查节点到 registry.k8s.io 的连通性;
# controller CrashLoopBackOff → 确认 controller.replicas 为 1(附录 A.2),或看日志端口冲突
# ---- 步骤 12:快照归档分流到 HDD ----
# chart 模板不支持透传 VolumeSnapshotClass 的 parameters,只能以对象方式维护。
# 重要:每次 helm upgrade 之后都必须重跑本步(原因见 R9)。
kubectl apply -f manifests/02-volumesnapshotclass.yaml
kubectl get volumesnapshotclass csi-nfs-snapclass -o jsonpath='{.parameters}{"\n"}' # 应输出 server 与 share
# ---- 步骤 13:核对两个 SC 与默认值 ----
kubectl get sc
# 预期输出:
# nfs-csi (default) nfs.csi.k8s.io Delete Immediate true
# nfs-csi-hdd nfs.csi.k8s.io Delete Immediate true
# 不符时:只有 nfs-csi → 核对 values 中 storageClasses 列表是否被删;
# 两个都带 (default) → 立即去掉其中一个的默认注解,否则省略 storageClassName 的 PVC 会绑定失败
5.3 变更挂载参数时的追加操作(已有卷生效路径)¶
# 参数固化在 PV 对象上,改 SC 不会自动作用到已有 PV,必须逐个 patch 并重新挂载
kubectl get pv -o custom-columns='NAME:.metadata.name,SC:.spec.storageClassName,MOUNT:.spec.mountOptions' # 找出未达标的 PV
# <pv名> 取自上一行的 NAME 列
kubectl patch pv <pv名> --type=merge -p \
'{"spec":{"mountOptions":["nfsvers=4.2","nconnect=4","hard","timeo=600","retrans=3","noatime"]}}'
# 高危:重启使用方以重新挂载(nconnect 仅在重新 stage 时生效),逐个滚动、不要同时
# <命名空间>/<StatefulSet名> 由该 PV 反查:
# kubectl get pv <pv名> -o jsonpath='{.spec.claimRef.namespace}{" "}{.spec.claimRef.name}'
kubectl -n <命名空间> rollout restart statefulset/<StatefulSet名>
# 复核:<节点IP> 取自 kubectl get node <节点名> -o jsonpath='{.status.addresses[0].address}'
ssh root@<节点IP> 'findmnt -t nfs4 -o TARGET,OPTIONS' # 应含 vers=4.2 与 nconnect=4
6. 验证¶
| 编号 | 命令 | 预期结果 | 实测 |
|---|---|---|---|
| V1 | sudo exportfs -v |
本方案三处导出均为 async,且 Slurm 导出仍在列 |
通过(四处导出并存) |
| V2 | sudo cat /proc/fs/nfsd/threads、nfsconf --get nfsd threads |
均为 32 |
通过(运行态 32,配置解析 32) |
| V3 | findmnt -no OPTIONS /srv/nfs/k8s /srv/nfs/k8s-hdd、sudo blockdev --getsize64 <loop> |
含 noatime,prjquota;上限 128849018880 / 536870912000 字节 |
通过(rw,noatime,prjquota) |
| V4 | kubectl get sc |
nfs-csi(default) 与 nfs-csi-hdd,PROVISIONER 为 nfs.csi.k8s.io |
通过 |
| V5 | kubectl get sc -o jsonpath、kubectl -n kube-system get secret nfs-mount-options -o jsonpath |
两者参数完全一致 | 通过 |
| V6 | kubectl get pv |
11 个 NFS PV 全部为 nfsvers=4.2 nconnect=4 … |
通过 |
| V7 | 打快照后 sudo ls -l /srv/nfs/k8s 与 sudo find /data/nfs-k8s-snap -maxdepth 2 |
SSD 侧不得出现 snapshot-*;HDD 侧出现 snapshot-<uid>/<源卷 uuid>.tar.gz |
通过(HDD 侧归档 5244782 字节;删除快照后同步回收) |
| V8 | kubectl apply -f examples/01-pvc.yaml → kubectl get pvc demo-data |
Bound,ACCESS MODES=RWX |
通过 |
| V9 | kubectl apply -f examples/02-cross-node-pods.yaml(或 04 用于 HDD),跨节点写读 |
内容与 md5 一致 | 通过:node-01 写 → node-02 读一致;HDD 版 volumeHandle 含 /srv/nfs/k8s-hdd |
| V10 | 删除 PVC 后 ls /srv/nfs/k8s |
子目录被回收,仅剩 lost+found |
通过 |
| V11 | sudo reboot 宿主机(择机执行) |
重启后 findmnt 两个后端已挂载且含 noatime、四处导出恢复、nfsd threads=32、Pod 自动重挂 |
未执行(会中断全部业务与 Slurm,需约定窗口) |
| V12 | 复跑第 5 章宿主机检查项 + helm upgrade --install |
宿主机各项全 SKIP;helm 升级后 Pod 创建时间与重启计数不变 | 通过(宿主机检查项全 SKIP;复跑后 Pod creationTimestamp 未变) |
性能基线(nfs-mount-tuning 于 2026-09-19 实测,节点侧 /proc/self/mountstats per-op 平均 RTT)
| 节点/后端 | 操作 | 调优前 | 调优后 |
|---|---|---|---|
| node-02 / HDD(prometheus) | OPEN | 67.60 ms | 0.25 ms |
| node-02 / HDD(prometheus) | SETATTR | 79.76 ms | 0.23 ms |
| node-02 / SSD(alertmanager) | SETATTR | 3.22 ms | 0.38 ms |
| node-01 / SSD(mariadb) | SETATTR | 3.05 ms | 0.19 ms |
| node-03 / HDD(openldap) | SETATTR | 75.47 ms | 0.23 ms |
收益主要来自服务端
async消除了机械盘的 fsync 等待(宿主本地回环路径的 fio 指标几乎未变)。
已知且可接受的现象
- 首次部署时
csi-provisioner、csi-resizer、node-driver-registrar各有 1 次Error(exit 1)重启, 属 sidecar 与主容器的启动竞态,重启后计数不再增长,无需干预。
组件状态基线
kubectl -n kube-system get pods | grep -E 'csi-nfs|snapshot'
# csi-nfs-controller-* 5/5 Running
# csi-nfs-node-* 4 个(含 master-01)3/3 Running
# snapshot-controller-* 1/1 Running
7. 回滚¶
7.1 备份清单¶
备份文件的全量台账(位置、产生时间、因何产生、能回退什么、何时可删)见
nfs-storage/backup/README.md; 本节只列与本次回滚直接相关的项。服务端配置备份因需就近恢复、且部分被nfs-mount-tuning的回滚逻辑依赖, 按规范例外保持在原位(已在台账中登记)。
| 对象 | 备份方式 | 位置 / 现状 |
|---|---|---|
/etc/exports |
步骤 0 备份;历次变更另有时间戳备份 | /etc/exports.bak.20260917-pre-snapshot-share、…-pre-hdd-share、/etc/exports.bak.20260918_124543、/etc/exports.bak.20260919-133357 |
/etc/nfs.conf |
同上 | /etc/nfs.conf.bak.20260919-133357 |
/etc/fstab |
同上 | /etc/fstab.bak.20260917、…-pre-hdd-share、/etc/fstab.bak.20260919-133357 |
| 集群配置 | values 与清单留档 | nfs-storage/values-nfs-csi.yaml、nfs-storage/manifests/(同目录另有调优工程产生的 .bak.*,不要删除,回滚依赖该命名) |
| 数据 | SSD 镜像 /kvm/nfs/k8s-120g.img、HDD 镜像 /data/nfs/k8s-hdd-500g.img |
删除镜像文件即数据全失,回滚默认保留 |
7.2 回滚步骤¶
原则:只回滚本方案的三处导出与集群对象,不触碰
/data/nfs/slurm-state,不使用全局撤销或停服动作。
# ---- 集群侧(先做,避免残留 PV 指向已失效后端)----
kubectl get pvc -A # 确认使用两个 NFS SC 的业务已迁移或可接受中断
helm uninstall nfs-csi -n kube-system # 移除 driver / controller / 两个 SC / VolumeSnapshotClass
kubectl delete -f manifests/00-mount-options-secret.yaml
kubectl delete -f manifests/01-snapshot-crds.yaml # 高危:会连带删除集群内全部快照对象,有快照数据时跳过本行
# ---- 宿主机侧:按导出粒度撤销(不要用 exportfs -ua,会连带撤销 Slurm 导出)----
sudo exportfs -u 192.168.31.0/24:/srv/nfs/k8s # 逐个导出撤销,其余导出的客户端不受影响
sudo exportfs -u 192.168.31.0/24:/srv/nfs/k8s-hdd
sudo exportfs -u 192.168.31.0/24:/data/nfs-k8s-snap
sudo exportfs -v # 确认仅本方案三处消失,Slurm 导出仍在
# ---- 宿主机侧:卸载后端并清理持久化配置 ----
sudo umount /srv/nfs/k8s && sudo umount /srv/nfs/k8s-hdd
sudo sed -i.bak '/kvm\/nfs\/k8s-120g.img/d; /data\/nfs\/k8s-hdd-500g.img/d' /etc/fstab # 仅删本方案两行(自动留 .bak)
sudo nfsconf --unset nfsd threads # nfsd 线程数恢复系统默认(不改动其他段)
sudo systemctl daemon-reload
# 快照目录:先校验路径存在且为空,再移除(不使用通配删除)
[ -d /data/nfs-k8s-snap ] && [ -z "$(ls -A /data/nfs-k8s-snap)" ] && sudo rmdir /data/nfs-k8s-snap
# 镜像文件默认保留;确认不再需要时再执行(高危:数据不可恢复)
# sudo rm -f /kvm/nfs/k8s-120g.img /data/nfs/k8s-hdd-500g.img
# 注意:不要执行 systemctl stop/disable nfs-server —— Slurm 导出依赖同一服务;
# 仅当确认 Slurm 已退役时才可停服。
7.3 回滚后的验证¶
kubectl get sc不再出现nfs-csi/nfs-csi-hdd;kubectl -n kube-system get pods | grep csi-nfs无输出。sudo exportfs -v仅剩/data/nfs/slurm-state(Slurm 不受影响);findmnt /srv/nfs/k8s*无输出。nfsconf --get nfsd threads回到系统默认(8),/proc/fs/nfsd/threads相应回落。df -h /与df -h /data反映镜像释放情况(默认保留镜像时占用不减)。- Slurm 状态目录可正常读写,
kubectl get no全 Ready,既有 VM 无异常。
7.4 回滚触发判据(量化,不临场判断)¶
任一满足即回滚:
- PVC 创建后 60 秒内未 Bound;
- Pod 跨节点挂载失败,或 node 插件日志出现 mount 报错 / Pod 报
Input/output error; - 改造后 per-op 平均 RTT 相对同后端基线劣化超过 20%;
- 宿主
/使用率超过 90%,或/data使用率超过 90%; - 本方案变更后 Slurm 侧出现 NFS 访问异常(此时优先按
exportfs粒度回退本次改动,而非停服)。
8. 风险说明¶
| # | 风险 | 影响 | 缓解措施 |
|---|---|---|---|
| R1 | 存储与计算同宿主,宿主故障时存储与 4 台 VM 同时中断,重启顺序不可控 | 依赖 PV 的 Pod 全部挂载失败直至 NFS 恢复 | 驱动自动重试 + hard 挂载自动恢复;如要真冗余须把 NFS 服务端迁到独立主机 |
| R2 | async 导出丢失持久化语义:客户端 COMMIT 被直接确认而不落盘 |
宿主非正常掉电/内核崩溃时可能丢最近写入;承载数据库的导出有损坏可能 | 已按用户指令对三处导出统一 async;变更前须停写 mariadb/minio;如需更强保证,把数据库所在导出单独改回 sync(按导出粒度可分域) |
| R3 | 120GiB 镜像位于系统盘(sda3),按需增长;当前 / 已用约 49G |
与宿主自身增长、3 个 etcd raw(各 10G)争空间,写满会波及 VM 与 libvirt | 加 / 使用率 >80% 告警;镜像写满由 loop 上限(120GiB)兜底 |
| R4 | 容量不被强制:NFS 后端无 CSI 层配额,PVC 请求值仅记账 | 应用可能写到远超请求值,且多个卷共享同一后端容量 | 已启用 prjquota,必要时用 setquota -P 对子目录手工限额;容量规划不要依赖 PVC 请求值 |
| R5 | HDD 卷与 VM 的 qcow2 共用同一块物理盘,且后端叠加 loop 层 | HDD 卷的高 IO 与 VM(含 k8s 节点自身)、Slurm 状态目录争抢磁盘 | 定位为容量优先、IO 不敏感场景;用 iostat -x 1 观察 %util/await;不要在 HDD SC 上跑数据库类小随机 IO |
| R6 | 快照是 tar 归档(默认 .tar.gz)非 COW;恢复耗时与数据量成正比 |
归档占用 HDD 空间;RESTORESIZE 是归档体积而非逻辑容量 |
已分流至 /data/nfs-k8s-snap;大数据量可用 --set controller.enableSnapshotCompression=false(.tar 更快更占空间) |
| R7 | hard 挂载:服务端不可用时 Pod I/O 阻塞而非报错 |
Pod 表现为挂起,可能触发探针失败 | 保数据一致性的取舍;排查时先确认 NFS 可达,不建议改 soft |
| R8 | no_root_squash 导出:192.168.31.0/24 内任意主机以 root 可读写全部数据 |
内网横向风险;并影响本方案与 Slurm 的全部数据 | 收紧网段,或改 all_squash,anonuid/anongid + Pod runAsUser/runAsGroup;本环境为隔离测试网段 |
| R9 | helm upgrade 会重置 VolumeSnapshotClass:Helm 的 three-way merge 会删除模板中不存在的 parameters |
升级驱动后快照静默回落到源卷 share(SSD),可能撑满才发现 | 每次 helm upgrade 后重跑 kubectl apply -f manifests/02-volumesnapshotclass.yaml,并按 V7 复验 |
| R10 | 快照 CRD 由 kubectl 独立管理(customResourceDefinitions.enabled=false) |
升级 chart 不会同步 CRD 定义 | 刻意设计:避免 helm uninstall 连带删除 CRD 与全部快照对象;升级驱动时人工核对 CRD 版本 |
| R11 | mountOptions 变更不自动作用于已有 PV:参数固化在 PV 上,nconnect 仅在重新 stage 时生效 |
只看 SC 会误以为已生效,旧卷仍是旧参数 | 逐个 patch PV 并重启使用方(第 5.3 节),节点侧 findmnt -t nfs4 复核实际选项 |
| R12 | 启动竞态导致 sidecar 重启 1 次 | 首次部署短暂不可用 | 观察计数不再增长即可忽略;持续增长需查 node 插件日志 |
| R13 | master-01 也运行 node 插件(DaemonSet 容忍全部污点) | 控制面节点增加约 3 个常驻容器 | 资源请求很小(10m CPU/20Mi 起);如需排除,加 nodeSelector 或改 tolerations |
| R14 | 与 Slurm 共享服务端全局资源(nfs-server 服务、/etc/exports、[nfsd] threads) |
本方案的全局动作(停服、全局撤销导出、调低线程)会同时中断或拖慢 Slurm | 回滚只按导出粒度操作(7.2);改 [nfsd] threads 前后评估两侧负载;停服前确认 Slurm 状态 |
| R15 | 当前 /etc/nfs.conf 中 threads = 32 位于 [nfsd] 段尾行、紧邻 [statd] 之前(形态易被误读);且该文件末尾是 [svcgssd] 段,用 tee -a 追加会写进错误段 |
人工整理或追加写入时容易把键放进错误段,导致重启后线程数回落、nconnect 并发优势丧失 |
一律用 sudo nfsconf --set nfsd threads 32 维护、nfsconf --get nfsd threads 解析复核(不能只看文件内容);已加入 V2 |
| R16 | ldap 与 server 两个命名空间同时存在同名的 openldap PVC |
疑似迁移残留,误删会损数据 | 清理前先核实两个命名空间的实际使用方,确认无引用后再删 PVC |
附录 A:核心配置项说明与调整方法¶
客户端与集群侧配置集中在
nfs-storage/values-nfs-csi.yaml,改完执行helm upgrade --install nfs-csi charts/csi-driver-nfs -n kube-system -f values-nfs-csi.yaml --wait即可生效(注意:升级后必须重跑快照分流那一步,见 R9)。 宿主机侧配置为/etc/exports、/etc/nfs.conf、/etc/fstab,改后按 5.1 的重载命令生效。
A.1 驱动与权限¶
| 配置项 | 当前值 | 作用 | 怎么改 / 改了会怎样 |
|---|---|---|---|
driver.mountPermissions |
0 |
挂载后是否对挂载根目录 chmod;0 = 不动 |
保持 0。设为 0777 会让目录对所有 Pod 可写且不解决跨 GID 隔离,官方列为最后手段 |
feature.enableFSGroupPolicy |
true |
允许 kubelet 应用 Pod 的 fsGroup |
保持 true;前提是导出带 no_root_squash,否则表现为挂载失败而非写入失败 |
kubeletDir |
/var/lib/kubelet |
node 插件据此挂载 pods-mount-dir |
仅当 kubelet 数据目录非默认时修改 |
A.2 controller(供给与快照编排)¶
| 配置项 | 当前值 | 作用 | 怎么改 / 改了会怎样 |
|---|---|---|---|
controller.replicas |
1 |
供给器副本数 | 不要加。controller 用 hostNetwork 且 liveness-probe 占固定宿主端口 29652,同节点多副本会端口冲突 → CrashLoopBackOff |
controller.enableSnapshotter |
true |
启用 csi-snapshotter sidecar | 不需要快照可设 false |
controller.enableSnapshotCompression |
true |
快照以 .tar.gz 存储 |
数据已压缩或体量大时设 false(.tar 更快、更占空间) |
controller.defaultOnDeletePolicy |
delete |
PVC 删除时后端子目录处置 | 改 retain 可防误删,代价是空间不回收 |
controller.runOnControlPlane |
false |
controller 是否调度到控制面 | 控制面资源紧张时保持 false |
A.3 StorageClass¶
| 配置项 | 当前值 | 作用 | 怎么改 / 改了会怎样 |
|---|---|---|---|
storageClass.name / …is-default-class |
nfs-csi / "true" |
默认 SC(SSD 后端) | 有多个 SC 时只应一个为默认,否则省略 storageClassName 的 PVC 会绑定失败 |
parameters.server |
192.168.31.249 |
NFS 服务端地址 | 换成域名需保证集群 DNS 可解析 |
parameters.share |
/srv/nfs/k8s |
后端导出路径 | 必须与 /etc/exports 中的路径一致 |
parameters.subDir |
未设置 | 子目录命名规则,支持 ${pvc.metadata.name} 等变量 |
默认由驱动生成 pvc-<uuid>;改为可读名会让同名 PVC 重建时复用旧目录 |
parameters.uid / gid |
未设置 | CreateVolume 时对新建子目录 chown |
fsGroup 会覆盖 gid,两者不要设成不同值 |
mountOptions |
nfsvers=4.2, nconnect=4, hard, timeo=600, retrans=3, noatime |
客户端挂载参数,必须与 Secret 及已有 PV 三处一致 | 改动只对新建 PV 生效;已有 PV 需按 5.3 逐个 patch 并重挂 |
reclaimPolicy |
Delete |
删 PVC 时是否删 PV 与后端目录 | 改 Retain 后新建 PV 才生效 |
volumeBindingMode |
Immediate |
绑定时机 | NFS 无拓扑约束,保持 Immediate |
allowVolumeExpansion |
模板固定 true |
是否允许改 PVC 请求容量 | 仅影响能否改请求值,实际容量不受限(见 R4) |
附加 StorageClass(HDD 版):由 storageClasses(复数列表)定义,与单数的 storageClass 并存。
| 配置项 | 当前值 | 作用 | 怎么改 / 改了会怎样 |
|---|---|---|---|
storageClasses[].name |
nfs-csi-hdd |
SC 名称 | 改名后既有 PVC 仍绑旧 SC,需重建 PVC |
storageClasses[].parameters.share |
/srv/nfs/k8s-hdd |
指向 HDD 的 500GiB 镜像 | 换路径须先在宿主机建目录与导出,再 helm upgrade |
storageClasses[].mountOptions |
同 SSD 版 | 客户端挂载参数 | 两者不同则需各自配套 provisioner-secret(当前共用 nfs-mount-options) |
| 默认注解 | 未设置 | 保持默认仍是 SSD 版 | 不要给两个 SC 同时打默认注解 |
A.4 快照¶
| 配置项 | 当前值 | 作用 | 怎么改 |
|---|---|---|---|
externalSnapshotter.enabled |
true |
部署快照控制器(集群全局一份) | 若已有其它实现,设 false 避免重复 |
externalSnapshotter.customResourceDefinitions.enabled |
false |
是否由 Helm 创建 CRD | 保持 false(见 R10);CRD 用 kubectl apply -f manifests/01-snapshot-crds.yaml |
volumeSnapshotClass.name / deletionPolicy |
csi-nfs-snapclass / Delete |
快照类与删除策略 | 需保留快照时改 Retain |
VolumeSnapshotClass.parameters.server / share |
192.168.31.249 / /data/nfs-k8s-snap |
快照归档落盘位置;不填则继承源卷 share(落回 SSD) | 只能改对象、不能改 values(见 R9):编辑 manifests/02-volumesnapshotclass.yaml 后 kubectl apply -f |
归档路径规则(源码
pkg/nfs/controllerserver.gov4.13.4):<share>/<snapshot 名>/<源卷 uuid>.tar.gz;据此可在宿主机上直接定位某份快照的归档文件。 分流后:卷数据在/srv/nfs/k8s*/pvc-*/,快照归档在/data/nfs-k8s-snap/snapshot-*/; 从快照恢复时,新数据仍按目标 StorageClass 的 share 落回对应后端。
A.5 服务端关键参数¶
| 参数 | 当前值 | 作用 | 怎么改 / 注意 |
|---|---|---|---|
| 导出选项 | rw,async,no_subtree_check,no_root_squash |
async 换取元数据延迟,代价见 R2 |
按导出粒度可分域;改后 exportfs -ra |
[nfsd] threads |
32(原 8) |
服务端并发 RPC 线程,配合 nconnect=4 |
热调整运行时:printf '32\n' \| sudo tee /proc/fs/nfsd/threads;持久化:sudo nfsconf --set nfsd threads 32;复核:nfsconf --get nfsd threads(见 R15) |
| loop 挂载选项 | noatime,loop,prjquota,nofail |
抑制 atime 写、启用项目配额、镜像异常不阻塞启动 | 改后 mount -o remount 或更新 fstab |
| 保留块 | 1%(tune2fs -m 1) |
原始默认 5% 会浪费约 4.7G/25G | 已设置;新增镜像同样设置 |
A.6 日常运维速查¶
日常运维命令(服务状态、日志、容量查看、驱动升级、故障场景处置)统一维护在
nfs-storage/RUNBOOK.md 的第 3 章「日常速查表」与第 4 章「日常操作」,本文不重复。
此处只保留与配置调整直接相关的一致性核对——改完参数后,三处必须一致:
kubectl get sc -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.mountOptions}{"\n"}{end}' # ① SC 侧
kubectl -n kube-system get secret nfs-mount-options -o jsonpath='{.data.mountOptions}' | base64 -d # ② Secret 侧
kubectl get pv -o custom-columns='NAME:.metadata.name,MOUNT:.spec.mountOptions' | grep nfs-csi # ③ 已有 PV 侧
# 预期:三处参数完全相同(当前为 nfsvers=4.2,nconnect=4,hard,timeo=600,retrans=3,noatime)
# 不符时:① 与 ② 不一致 → 改 values 与 Secret 后重新 helm upgrade;
# ③ 落后 → 按第 5.3 节逐个 patch PV 并重启使用方
附录 B:交付物清单¶
| 路径(相对项目根) | 说明 |
|---|---|
nfs-storage/SOP.md |
本文件(变更实施、验证与回滚) |
nfs-storage/RUNBOOK.md |
服务运行手册:日常速查、故障场景处置、监控、容量扩缩(值班用) |
nfs-storage/backup/README.md |
备份台账:全部备份文件的位置、用途与可删条件 |
nfs-storage/values-nfs-csi.yaml |
核心实现体:客户端参数、两个 StorageClass、快照控制器配置 |
nfs-storage/manifests/00-mount-options-secret.yaml |
DeleteVolume / DeleteSnapshot 所需的挂载参数(须与 SC 一致) |
nfs-storage/manifests/01-snapshot-crds.yaml |
快照 CRD 固化清单(独立于 Helm 管理) |
nfs-storage/manifests/02-volumesnapshotclass.yaml |
快照落盘分流配置(指向 HDD),每次 helm 升级后需重新 apply |
nfs-storage/charts/csi-driver-nfs/ |
本地固化的 chart 4.13.4 |
nfs-storage/examples/01-pvc.yaml |
PVC 动态供给验证(SSD 默认 SC) |
nfs-storage/examples/02-cross-node-pods.yaml |
跨节点 RWX 读写验证 |
nfs-storage/examples/03-snapshot-and-restore.yaml |
快照与恢复验证 |
nfs-storage/examples/04-pvc-hdd.yaml |
HDD 存储类跨节点读写验证 |
nfs-storage/backup/README.md |
备份台账说明(解释同目录 .bak.* 为何保留原位) |
nfs-mount-tuning/docs/SOP.md |
挂载参数调优的独立变更记录(本文档当前参数的来源) |
范围外但同服务端共存:/data/nfs/slurm-state(Slurm 集群状态导出),由 slurm 相关部署管理,
不属本方案的交付、验证与回滚范围;本方案任何全局动作前须评估其影响(见 R14)。
来源:
/home/rain/Documents/k8s_ins/nfs-storage/SOP.md| 最后更新:2026-09-19 16:29(由build/sync.py同步,请勿在站点侧修改)