Kubernetes 环境 NFS 卡死问题排查与解决纪要
一、事件背景
在 Kubernetes 集群运行过程中,xxxx 命名空间内多个业务 Pod 出现启动异常,部分 Pod 长时间处于 CreateContainerError 或 ContainerCreating 状态,重建 Pod、重启业务均无法恢复。
异常具有以下明显特征:
- 同一 Deployment 中,新创建的 Pod 启动失败,而旧 Pod 仍可运行
- 问题集中出现在 涉及 NFS 持久化存储的 Pod
- 重启 NFS 服务器后,所有异常 Pod 立即恢复正常
初步判断:
该问题并非应用自身故障,而是 底层 NFS 存储服务在高并发挂载场景下发生阻塞。
二、现象描述
1. Kubernetes 侧表现
-
同一 Deployment 中,部分 Pod 正常运行,部分 Pod 启动失败
-
失败 Pod 状态为
CreateContainerError -
kubectl describe pod的 Events 中出现:MountVolume.SetUp failedtimed out waiting for the condition
2. NFS 侧表现
- NFS 服务未完全崩溃,但客户端挂载请求阻塞
- 重启 NFS 服务后,K8s Pod 可立即恢复
三、环境信息
NFS 服务器硬件规格
- CPU:2 vCPU(KVM 虚拟机)
- 内存:3 GB
- 架构:x86_64
- 用途:Kubernetes 集群共享存储(PVC)
NFS 协议
- NFS v4
四、排查过程(时间顺序)
1. Kubernetes 侧初步排查
-
使用
kubectl get pod发现多个 Pod 状态为CreateContainerError -
通过
kubectl describe pod查看 Events,发现大量:MountVolume.SetUp failedtimed out waiting for the condition
结论:
Pod 在 Volume 挂载阶段阻塞,问题指向存储层。
2. 确认问题与 NFS 存储相关
- 排查失败 Pod 所使用的 PVC,确认均使用 NFS 后端
- 在 Kubernetes 节点执行
df -Th、ls等命令,发现 命令本身卡死无返回
结论:
NFS 挂载已在内核态阻塞,影响整个节点的 VFS 操作。
3. NFS 服务器侧日志排查
3.1 systemd / 服务日志
bash
journalctl -u nfs-server -u rpcbind -u nfs-idmapd
- 未发现启动失败类错误
- 出现 subtree_check 相关 warning(非致命)
3.2 内核日志分析(关键)
在 /var/log/messages 中发现:
text
rpc-srv/tcp: nfsd: got error -32 when sending ... shutting down socket
nfs-mountd.service: Main process exited, code=killed, status=9/KILL
结论:
NFS 服务在运行过程中出现 RPC 连接异常及 mountd 被强制终止 的情况,符合线程/资源耗尽导致的"假死"特征。
4. NFS 内核运行状态确认
bash
cat /proc/net/rpc/nfsd
关键输出:
text
th 8
proc4 ... 5701
分析:
- nfsd 工作线程仅 8 个(默认值)
- NFSv4 请求数量大
- 在 Kubernetes 并发 Pod 启动、同时挂载 NFS 的场景下,线程极易被打满
至此,基本锁定问题根因。
2. 查看系统日志(/var/log/messages)
发现关键错误:
text
rpc-srv/tcp: nfsd: got error -32 when sending ... shutting down socket
nfs-mountd.service: Main process exited, code=killed, status=9/KILL
含义说明:
error -32:Broken pipe,客户端连接异常status=9/KILL:nfs-mountd被强制杀死(疑似阻塞/资源问题)
3. 查看 NFS 内核运行状态
bash
cat /proc/net/rpc/nfsd
关键输出:
text
th 8 ...
proc4 2 0 5701
分析:
- nfsd 线程数只有 8(默认值)
- 大量 NFS v4 请求
- 在 K8s 并发 Pod 启动/挂载场景下极易被打满
4. 确认 systemd 服务配置
bash
systemctl cat nfs-server
发现:
ExecStart=/usr/sbin/rpc.nfsd- 未配置线程数参数
- 未引用
/etc/sysconfig/nfs
结论:
每次重启
nfs-server,线程数都会被重置为默认 8。
五、根因分析(最终结论)
根本原因
NFS 服务器规格较低(2C3G) + nfsd 默认线程数过小(8) + Kubernetes 并发挂载行为 → NFS 服务线程耗尽,导致挂载阻塞。
佐证证据链
- Pod 卡在 Volume Mount 阶段
- NFS 日志出现 socket error / mountd 被 kill
/proc/net/rpc/nfsd显示线程数仅 8- 重启 NFS 服务后立即恢复
六、处理过程与解决方案
1. 临时止血措施
- 重启 NFS 服务:
bash
systemctl restart nfs-server
效果:
- 阻塞的 RPC 请求被清空
- Kubernetes 节点可重新完成 NFS 挂载
- Pod 启动恢复正常
说明:
该措施只能临时恢复服务,无法根治问题。
2. 根因修复:提升 nfsd 线程数
2.1 确认 systemd 未配置线程数
bash
systemctl cat nfs-server
发现:
ExecStart=/usr/sbin/rpc.nfsd- 未指定线程数参数,服务每次重启都会回退到默认 8 线程
2.2 使用 systemd override 固化线程数(最终方案)
bash
mkdir -p /etc/systemd/system/nfs-server.service.d
vim /etc/systemd/system/nfs-server.service.d/override.conf
内容:
ini
[Service]
ExecStart=
ExecStart=/usr/sbin/rpc.nfsd 32
生效:
bash
systemctl daemon-reload
systemctl restart nfs-server
验证:
bash
cat /proc/net/rpc/nfsd | grep '^th'
结果:
nfsd 线程数固定为 32,不再随服务重启回退。
3. /etc/exports 配置优化(配合性修复)
- 明确指定
no_subtree_check - 移除无必要的
insecure - 统一 K8s 场景导出参数
该调整进一步降低了 NFS 在高并发挂载时的风险。
2. 优化 /etc/exports(K8s 推荐)
bash
/data/mnt/k8s-storageclass *(rw,sync,no_root_squash,no_subtree_check)
应用配置:
bash
exportfs -rv
七、效果验证
- NFS 线程数固定为 32,不再被重启重置
- Kubernetes Pod 启动、扩容、重启时不再出现 NFS 挂载卡死
- 未再出现
CreateContainerError相关 NFS 错误
八、经验总结
- Kubernetes + NFS 默认配置不可直接用于并发场景
nfsd线程数必须根据 CPU 核数和并发量调整- 重启可恢复的问题,通常是资源/线程耗尽而非功能性错误
- systemd override 是比临时 echo 更可靠的生产做法
九、Kubernetes 挂载 NFS 的建议与最佳实践(补充)
为避免类似问题再次发生,在 Kubernetes 场景中使用 NFS 时,建议从 挂载参数、使用方式、发布策略 三个层面进行规范。
1. NFS 挂载参数建议(非常关键)
1.1 推荐使用 NFS v4
在 Kubernetes 中,强烈建议统一使用 NFS v4:
- 减少对 rpcbind / mountd / statd 的依赖
- 状态管理更清晰,锁问题更少
- 更适合容器化与动态调度场景
PVC / PV 中推荐配置:
yaml
mountOptions:
- vers=4
- noatime
- hard
- timeo=600
- retrans=5
说明:
retrans必须设置上限,避免无限阻塞导致节点被拖死。
1.2 hard / soft 的选择建议
| 场景 | 建议 | 说明 |
|---|---|---|
| 关键业务数据 | hard + retrans | 保证一致性,同时避免无限等待 |
| 日志、缓存、可重试业务 | soft | 避免 I/O 阻塞拖垮节点 |
2. Kubernetes 使用方式建议
2.1 优先使用 PV / PVC,而非直接 nfs volume
推荐:
- 使用 PersistentVolume / PersistentVolumeClaim
- 统一挂载参数,便于集中治理
不推荐:
- 在 Pod 中直接定义 nfs volume(不利于维护与审计)
2.2 避免大量 Pod 同时挂载同一 NFS
-
Deployment / StatefulSet 发布时控制并发
-
合理设置:
maxUnavailablemaxSurge
-
避免一次性 scale 大量 Pod
大量 Pod 同时 mount NFS,是触发 nfsd 线程耗尽的高风险行为。
3. 节点与运维层面的防护建议
3.1 避免运维命令被 NFS 拖死
- 查看磁盘时优先使用:
bash
df -Th -x nfs -x nfs4
- 或:
bash
findmnt -t nfs,nfs4
3.2 挂载异常时的处置建议
-
单节点出现 NFS 阻塞时,优先考虑:
- Pod 迁移
- Node 重启
-
避免在阻塞节点上反复执行
df / ls / du等命令
4. 架构层面的长期建议
-
若业务规模持续扩大,不建议长期依赖单点 NFS
-
可考虑:
- 升级 NFS 服务器规格(≥4C8G)
- 引入分布式存储(Ceph / Longhorn 等)
十、/etc/exports 配置问题分析与优化方案(补充)
1. 事发时原始配置
bash
/data 192.142.8.89(rw,sync,no_root_squash)
/data 192.142.8.88(rw,sync,no_root_squash)
/data 192.142.8.91(rw,sync,no_root_squash)
/data 192.142.8.92(rw,sync,no_root_squash)
/data 192.142.8.26(rw,sync,no_root_squash)
/data 192.142.8.27(rw,sync,no_root_squash)
/data 192.142.8.28(rw,sync,no_root_squash)
/data/mnt/k8s-storageclass/ *(insecure,rw,sync,no_root_squash)
2. 存在的问题分析
(1)未显式指定 no_subtree_check
- NFS 默认行为依赖 nfs-utils 版本
- K8s 场景下目录频繁变化(PVC 动态创建)
- 容易导致 mount 阶段阻塞甚至假死
- 日志中已出现多次相关 warning
(2)使用了 insecure 参数
- 允许客户端使用高端口访问
- 对 Kubernetes 节点无实际必要
- 增加攻击面,不利于安全收敛
(3)导出策略不统一
/data按 IP 精确控制/data/mnt/k8s-storageclass对所有主机开放- 在 NFSv4 状态管理下存在潜在一致性风险
3. 优化后的推荐配置(K8s 稳定版)
bash
/data 192.142.8.89(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.88(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.91(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.92(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.26(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.27(rw,sync,no_root_squash,no_subtree_check)
/data 192.142.8.28(rw,sync,no_root_squash,no_subtree_check)
/data/mnt/k8s-storageclass *(rw,sync,no_root_squash,no_subtree_check)
应用方式:
bash
exportfs -rv
4. 参数选择说明
| 参数 | 说明 | 选择原因 |
|---|---|---|
| rw | 读写 | K8s PVC 必需 |
| sync | 同步写入 | 稳定性优先,避免 IO 堆积 |
| no_root_squash | root 映射 | 容器内 root 访问需要 |
| no_subtree_check | 禁用子目录校验 | K8s 官方推荐 |
| insecure | ❌ 不使用 | K8s 不需要 |
5. 调整效果
- NFS 启动日志中不再出现 subtree 相关警告
- Kubernetes Pod 并发启动时 mount 行为明显更稳定
- 配合 nfsd 线程数提升,未再复现卡死问题