LabHub
学习 学习路径 课程

CPU 与内存泄漏的判定

磁盘与文件描述符

在 LabHub 中继续学习

一句话总结

df 显示还有空间却无法写入,说明要么 inode 已耗尽,要么有人仍打开着已经删除的文件。 这两种情况都无法通过 df -h 看出来。

概念图: 要么 inode 已耗尽,要么有人仍打开着已经删除的文件。 · inode 耗尽。 · 文件已删除,但仍处于打开状态。 · 提高上限只能争取时间,并不等于修复问题。

为什么需要理解这些现象

磁盘相关报障中,最常见的是下面两种情况。

inode 耗尽。 每个文件都会占用一个 inode。存在数百万个小文件时,即使容量仍有剩余,inode 也可能率先用完。只有通过 df -i 才能看到。

文件已删除,但仍处于打开状态。 如果用 rm 删除日志文件时,某个进程仍然打开着它,Linux 只会删除链接,并不会立即释放实际数据块。此时 df 仍显示磁盘已满,du 却找不到对应文件。这种差异就是决定性线索。

# du 와 df 가 다르면 이걸 본다
ls -l /proc/*/fd 2>/dev/null | grep deleted

解决方法是让进程重新打开文件。如果是日志,可以使用 logrotatecopytruncate,或向守护进程发送 SIGHUP;也可以重新启动进程。

它是如何工作的

文件描述符泄漏会让系统缓慢走向死亡。

ls /proc/<PID>/fd | wc -l              # 지금 몇 개 열었나
cat /proc/<PID>/limits | grep 'open files'   # 한도

如果这个数字随时间单调增长,代码中就存在没有关闭文件的路径。达到上限的一刻,所有请求都会以 Too many open files 失败。提高上限只能争取时间,并不等于修复问题。

常见误解

误以为 ulimit -n 无限制就一定安全。 容器的默认 RLIMIT_NOFILE 实际上几乎没有限制,达到十亿级别;但有些老旧守护进程会在启动时按照这个数字分配连接表,最终耗尽内存并退出。无限制并不总是好事。

判断磁盘是否缓慢的两个数字

iostat -x 1 有很多列,实际需要关注的只有两列。

Device  r/s   w/s  rkB/s  wkB/s  r_await  w_await  aqu-sz  %util
nvme0n1 120  380   4800  15200     0.31     0.52    1.24    38.2

不要相信 %util 这个值表示“至少有一个请求正在处理的时间比例”。 能够并行处理的 NVMe 即使达到 100%,也可能仍有余量。它是旋转磁盘时代的 指标。

哪个进程正在使用磁盘

# 실시간으로 I/O 상위 프로세스
iotop -oPa            # -o: 실제로 I/O 하는 것만, -a: 누적

# iotop 이 없을 때 (컨테이너에서 흔하다)
cat /proc/<PID>/io    # read_bytes, write_bytes 를 두 번 읽어 차이를 낸다

/proc/<PID>/io 中的 read_bytes 表示实际从磁盘读取的数据量,而 rchar 表示 通过读取系统调用传递的数据量。两者差异很大,说明页缓存工作良好,属于正面信号; 反过来,如果两者接近,则说明缓存没有生效,每次都在访问磁盘。

在容器中限制 I/O

与 CPU 和内存不同,Kubernetes 没有 I/O 限制字段。cgroup v2 虽然提供 io.max,却没有通过 Pod 规范暴露。因此,实际工作中的处理方式也不同。

ionice 只对 CFQ 与 BFQ 调度器有效。在现代 NVMe 默认使用的 none(多队列) 模式下,它完全不起作用,所以应先运行 cat /sys/block/nvme0n1/queue/scheduler 进行确认。

实际工作中真正重要的事

日志轮转配置中 createcopytruncate 的差别,正与这里的问题相关。

两种方式都不是没有代价。应用程序能够处理 SIGHUP 时使用 create;无法处理时使用 copytruncate,但必须清楚存在数据丢失的可能。