乐于分享
好东西不私藏

一次阿里云 CSI 插件 NFS 挂载 OOM 的排查实录

一次阿里云 CSI 插件 NFS 挂载 OOM 的排查实录

一个 ingress-nginx 的 geoip 文件,揭开 CSI 插件与 Alinux4 的兼容性问题

背景

将新采买的节点 alinux-node 加入到集群后,ingress-nginx-controller Pod 一直处于 ContainerCreating 状态。查看事件,发现挂载 NAS 卷失败。

查看 CSI 插件日志:

E0528 10:16:41.907584  136527 utils.go:101] GRPC error:Nas, Mount Nfs error: Failed to run cmd: mount -t nfs -o vers=3,nolock,tcp,noresvport<nas-server>.cn-beijing.nas.aliyuncs.com:/ingress/geoip/var/lib/kubelet/pods/.../mount,with out: , with error: exit status 255

在继续排查之前,先简单了解下相关组件的作用。

CSI 插件架构速览

集群中与存储相关的组件分为两大块:

组件
部署方式
职责
csi-plugin
DaemonSet(每节点一个)
在节点上执行挂载/卸载操作
csi-provisioner
Deployment(全局 2 副本)
调用阿里云 API 创建/删除云资源

一个 csi-plugin Pod 包含 4 个容器:

csi-plugin Pod(DaemonSet,每节点一个)├── csi-plugin(主容器)          ← 核心 CSI 驱动,负责 mount/umount├── disk-driver-registrar          ← 向 kubelet 注册 disk 插件├── nas-driver-registrar           ← 向 kubelet 注册 nas 插件└── oss-driver-registrar           ← 向 kubelet 注册 oss 插件

csi-plugin 主容器 的核心职责:

  • NodePublishVolume:当 Pod 要使用 PV 时,负责在节点上执行实际的挂载操作
    • NAS:mount -t nfs ...
    • 云盘:attach 云盘 → 格式化 → mount
    • OSS:通过 ossfs 挂载
  • NodeUnpublishVolume:Pod 删除时,卸载对应挂载点
  • 健康检查:监听 /healthz 端口
  • 初始化:部署 csiplugin-connector 到宿主机、安装 nfs-utils 等依赖

这次报错就是 csi-plugin 容器在执行 NAS 挂载时失败的日志。


回到问题本身,挂载 NFS 返回了 exit status 255,stderr 为空。更诡异的是:

  • 宿主机上手动 mount 正常
  • 其他节点的 CSI 插件正常
  • 只有新采买的节点 alinux-node(使用阿里云最新镜像)有问题

看起来是一个"薛定谔的 Bug"——在宿主机上能挂,进了容器就不行。

第一阶段:常规排查

1. 确认容器环境

$ kubectl exec -it csi-plugin-xxx -n kube-system -c csi-plugin -- sh# mount.nfs 存在吗?ls -la /sbin/mount.nfs-rwsr-xr-x 1 root root 196560 Aug 3  2022 /sbin/mount.nfs     ✅# 内核支持 NFS 吗?cat /proc/filesystems | grep nfsnodev  nfsnodev  nfs4      ✅# NFS 内核模块加载了吗?lsmod | grep nfsnfsv3   69632  0nfs     610304  1 nfsv3        ✅# SELinux 开启了吗?cat /sys/fs/selinux/enforce(文件不存在,SELinux 关闭)     ✅# Pod 特权模式?securityContext:  privileged: true  allowPrivilegeEscalation: true   ✅

看起来一切正常,那为什么 mount 会失败?

2. 在容器内手动测试

sh-4.4# mount -t nfs -o vers=3,nolock,tcp,noresvport \  <nas-server>.cn-beijing.nas.aliyuncs.com:/ingress/geoip /tmpsh-4.4# echo $?255

exit 255,没有任何错误输出,就像被黑洞吞掉了一样。

第二阶段:关键线索

上面 mount -t nfs 是把参数传给 mount 命令,mount 再去找 /sbin/mount.nfs 来干活。我们绕过 mount,直接调用 mount.nfs

sh-4.4# /sbin/mount.nfs -o vers=3,nolock,tcp,noresvport \  <nas-server>.cn-beijing.nas.aliyuncs.com:/ingress/geoip /tmp/nfs-testsh: line 21253486 Killed    /sbin/mount.nfs ...echo $?137

exit 137!Killed!

这个信息极其重要:

Exit Code
含义
137
128 + 9 = SIGKILL
255
mount
 命令把子进程的 SIGKILL 包装成了通用错误

进程是被 SIGKILL 杀死的,而不是正常返回错误。在容器环境里,被 SIGKILL 只有一个可能——OOM Killer

第三阶段:确认 OOM

dmesg 查看内核日志:

$ dmesg | grep -i "oom\|killed"mount.nfs invoked oom-killer:gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=-997Memory cgroup out of memory:Killed process 1256913 (mount.nfs)total-vm:54535296kB     (虚拟内存 52GB)anon-rss:2084140kB      (物理内存 ~2GB)

破案了!mount.nfs 进程被 cgroup OOM Killer 杀死。

检查 Pod 的内存限制:

resources:  limits:    memory1024Mi    # 1GB

把内存限制调到 2Gi 后重试,仍然 OOM,只是 RSS 涨到了 ~2GB:

Memory cgroup out of memory: Killed process mount.nfsanon-rss:2084140kB      (2Gi 限制下涨到了 ~2GB)

这说明 mount.nfs 的内存分配是无上限的——给多少内存它就涨到多少,并不是"恰好需要 2GB"的问题。

第四阶段:为什么 mount.nfs 内存会无限上涨?

在容器内执行 mount.nfs -V 可以看到版本是 nfs-utils 2.3.3。当 mount.nfs 执行挂载时:

mount -t nfs -o vers=3 ...  └─ mount.nfs       └─ rpcinfo -p <NAS服务器>    ← 查询 RPC 服务注册信息            └─ 服务端返回了异常的 RPC 记录标记                 └─ 客户端 RPC 库不断分配内存读取数据                      └─ 虚拟内存暴涨到 52GB,RSS 无限上涨直到 OOM

即使你指定了 vers=3mount.nfs 仍然会先执行 RPC 探测再应用版本约束,在这个探测过程中就已经 OOM 了。

第五阶段:真正的根因

进一步排查发现:alinux-node 采用了最新的阿里云系统版本 Alibaba Cloud Linux 4(Alinux4)。而集群中的 CSI 插件版本是 v1.24.5,通过 docker inspect 可以看到该镜像基于 Alinux3 构建(org.label-schema.name: "Alinux3 Base Image")。宿主机是 Alinux4,容器内却是 Alinux3 版本的 nfs-utils,两个大版本的内核/RPC 栈接口存在差异,触发兼容性问题。

Alinux4(宿主机)+ 容器内 Alinux3 的 nfs-utils → mount.nfs OOM  ❌

其他节点没有升级系统,所以一直正常。alinux-node 新采买回来就带着 Alinux4,成为了第一个踩坑的节点。

最终解决方案

节点重装系统降级回 Alibaba Cloud Linux 3.2104 LTS 后重新加入集群,问题彻底解决。

根本上是因为:

  • 操作系统版本与 CSI 插件版本需要匹配:旧版 CSI 插件(Alinux3 基础镜像)的 nfs-utils 与 Alinux4 的新内核/RPC 栈交互时触发内存泄露式分配
  • 升级 CSI 插件到新版(v1.36.1,Debian 基础镜像)也可以解决,因为它使用宿主机 connector 执行 mount,不依赖容器内的 nfs-utils

经验总结

  1. exit code 137 = SIGKILL 一定是进程被杀了,在容器里先查 OOM
  2. OOM 查 dmesg,用户态日志看不到内核的 Kill 记录
  3. 直接调用子进程(/sbin/mount.nfs)而不是父命令(mount -t nfs,能暴露被包装的错误码
  4. 操作系统升级后,旧的 CSI 插件可能不兼容,升级 CSI 前先确认版本匹配
  5. 有时候不是"哪个节点坏了",而是"节点上的系统版本和组件版本配不上"

后记:重装系统降级版本后,业务恢复。那无限膨胀的内存,就是 mount.nfs 为了一次 RPC 探测付出的代价。