我做 Linux 运维这些年,最怕的不是 CPU 打满,而是监控面板上一切正常,服务却莫名其妙出事。CPU 空闲、内存充足、磁盘空间也够,可应用就是无响应;登进去一看,文件描述符爆了。这种问题你翻性能优化文档,大概率找不到标准答案——因为常规监控方案根本不会去采集这类指标。今天想聊的,就是这些你不知道自己需要监控的 Linux 暗坑:文件描述符、inode、熵池、连接状态、conntrack、僵尸进程、journal、CPU steal、GPU 和磁盘延迟。我会结合自己在生产环境踩过的坑,把每个暗坑从现象、原理到监控手段完整拆开,最后给出一份可以直接落地的指标清单和告警阈值。文章适合刚接手服务器的新人,也适合被线上故障反复折腾的运维、SRE,按图索骥就能避开大部分隐蔽故障。
1. 资源层的“假充裕”:文件描述符、inode 与熵池才是隐形炸弹
1.1 文件描述符耗尽:内存再多也无济于事
先从一个最常见的场景说起:一台高并发的 Java 应用服务器,CPU 和内存看起来都很健康,但突然所有接口都变慢,日志里疯狂报Too many open files。很多人的第一反应是连不上服务器、或者怀疑负载均衡出问题,但实际上问题出在进程的文件描述符用完了。
文件描述符是什么?你可以理解成进程打开文件、网络连接、管道等资源时的“门牌号”。一个进程每次打开一个 socket、读一个文件、监听一个端口,都要消耗一个 fd。Linux 对每个进程能持有的 fd 数量是有限制的,传统环境默认ulimit -n可能是 1024,很多系统调到了 65535,但在高并发、连接池泄漏、日志文件句柄不释放的情况下,这个数字很容易被打满。
最麻烦的是,常规监控默认只会看 CPU、内存、磁盘、网络带宽,几乎不会有模板去采集“进程级 fd 使用量”。所以你会看到一个荒诞的结果:监控面板上所有指标都在安全区间,服务却已经快挂了。我当时就是在一个深夜排查了三个小时,最后用一条命令发现问题:
cat /proc/sys/fs/file-nr这个文件输出三个数字:系统当前已分配的文件句柄数、未分配但已初始化的句柄数、系统最大文件句柄数。重点关注第一个和第三个的比例。想查某个进程的 fd 用量,更直接:
ls /proc/<pid>/fd | wc -l如果用量接近进程上限,再配合ulimit -n和 systemd 里的LimitNOFILE就能确认。
监控层面,Prometheus 的 node_exporter 默认暴露node_filefd_allocated和node_filefd_maximum;Zabbix 可以通过自定义 UserParameter 执行cat /proc/sys/fs/file-nr再解析。需要特别提醒:如果监控发现 fd 使用率在缓慢增长而不是一次性打满,大概率是代码里有连接或文件句柄泄漏,建议把 fd 增长速率也记录下来,这样能提前几天发现问题,而不是等半夜报警。
注意:fd 告警发出时,可能已经连 SSH 都进不去了,因为 SSH 会话也会消耗 fd。所以我们不仅要监控,最好在高风险服务上提前调大
LimitNOFILE,给排查留出时间窗口。
1.2 inode 耗尽:磁盘没满,文件却创建不了
另一个“假充裕”的典型是 inode 耗尽。磁盘空间明明还剩 20G,但创建文件时依然报No space left on device,很多人第一反应是磁盘满了,结果df -h一看还很空,就懵了。
inode 是文件系统里用来保存文件元数据的索引节点,每个文件、目录、硬链接都要占用一个 inode。如果一个分区里塞满了海量小文件,即使总容量没满,inode 也可能先耗尽。最常见的重灾区是/var/spool/postfix/maildrop、/tmp、容器镜像的 overlay2 目录,以及一些生成临时文件的构建系统。
判断命令很简单:
df -iIUse%达到 100% 就意味着 inode 耗尽。我当时是在一台 Jenkins 构建机上踩到这个坑的,持续集成每次生成几千个小文件,几个月下来把/var分区的 inode 吃干净了,任何新进程都创建不了临时文件,整个构建链路瘫痪。更坑的是,监控大盘只监控了磁盘空间使用率,完全没有采集 inode 数据。
监控方法也不复杂。Prometheus 的 node_exporter 会暴露node_filesystem_files和node_filesystem_files_free,相减就能算出使用率;Zabbix 有vfs.fs.inode监控项。阈值建议是 inode 使用率超过 80% 就进入关注列表,超过 90% 就要告警,因为清理小文件通常需要时间,等你看到 95% 再处理往往已经晚了。
顺带提一个排查技巧:如果 inode 使用率很高,但不知道是哪个目录搞的,可以用:
for d in /* /var/*; do echo "$d: $(find "$d" -xdev -type f 2>/dev/null | wc -l)"; done先粗筛一层,再逐层细查,通常很快就能定位到某个临时文件目录。
1.3 系统熵池耗尽:业务卡在等待随机数上
这个坑在物理机上不多见,但在虚拟机和嵌入式环境里非常常见。系统熵池是内核收集环境噪声生成随机数的来源,像 SSH 密钥生成、SSL/TLS 握手、数据库加密操作都会依赖随机数。
如果熵池不足,很多操作会阻塞等待。现象非常迷惑:应用 CPU 不高,网络也正常,但 HTTPS 握手异常缓慢,日志里也没有明确报错,因为程序可能只是卡在getrandom()系统调用上。
查看当前熵值:
cat /proc/sys/kernel/random/entropy_avail这个值在不同机器上差异很大,物理机通常几千,虚拟机可能只有一两百。如果持续低于 200,就很可能出现随机数等待。
我曾在虚拟化平台上部署过一批 HTTPS 网关,压测时发现握手 QPS 上不去,CPU 和内存都闲着,最后排查到熵池几乎被掏空。当时的临时办法是安装rng-tools或haveged,让系统利用更多噪声源;如果是 KVM/VMware 虚拟机,最好给虚拟机挂一个虚拟随机数设备,比如virtio-rng。
监控采集也很简单,写个脚本读entropy_avail,再通过 node_exporter 的 textfile collector 或 Zabbix 自定义 item 上报即可。这一条在嵌入式环境监控里尤其值得加——很多 ARM 板卡本身没有硬件随机数发生器,开机后 entropy 长期处于低位,一旦跑起加密服务就非常容易中招。
2. 网络层的隐疾:连接状态、重传率与 conntrack 表
2.1 TIME_WAIT 与 CLOSE_WAIT 堆积:连接状态比流量更诚实
网络监控如果只盯着带宽、包量、会话数,一定会漏掉很多信号。连接状态才是真正能反映业务健康度的指标,尤其是 TIME_WAIT 和 CLOSE_WAIT。
用ss -s可以快速看当前各种 TCP 状态的数量:
ss -sTIME_WAIT 是主动关闭方在发送最后一个 ACK 后进入的状态,用来保证旧连接的数据包不会串到新连接。高并发短连接场景下几千个 TIME_WAIT 是正常的,但如果net.ipv4.ip_local_port_range设置得过小,TIME_WAIT 过多可能导致本地端口耗尽,表现为新连接无法建立。
更值得警惕的是 CLOSE_WAIT。这个状态表示对端已经关闭连接,但本地应用没有调用close(),属于被动关闭方的“连接残留”。CLOSE_WAIT 数量持续增长,基本可以断定是应用代码有 socket 泄漏——比如读取完响应后没有关闭 body,或者数据库连接池没有正确释放连接。
查看具体连接状态分布:
ss -tan | awk '{print $1}' | sort | uniq -c我见过最典型的案例是网关服务依赖一个第三方 HTTP 客户端,客户端对响应体读取不完整导致连接没有归还连接池,CLOSE_WAIT 从几十涨到几千,最终后端连接表被占满。监控端一定要把 ESTABLISHED、TIME_WAIT、CLOSE_WAIT 分开采集。Prometheus 的 node_exporter 会暴露node_sockstat_TCP_*系列指标,Zabbix 也可以直接用ss命令自定义监控项。
对于 TIME_WAIT 的告警阈值不要拍脑袋定“超过多少就报警”。我建议先看ip_local_port_range:
sysctl net.ipv4.ip_local_port_range然后根据端口范围和连接数估算是否快触顶。CLOSE_WAIT 则相反,只要持续增长就要怀疑代码问题,不需要等一个绝对阈值。
2.2 TCP 重传率与网卡丢包:延迟抖动的前兆
网络稳定的时候,重传率通常非常低。一旦重传率明显上升,说明网络链路已经出现丢包,但业务可能只是表现为偶发延迟变高,并不会立刻不可用。如果监控系统只看带宽,这种劣化很难被发现。
在服务器上可以先看两个地方。一是协议栈统计:
netstat -s | grep -E "retransmit|retransmitted"二是网卡统计:
ethtool -S eth0 | grep -E "drop|discard" ip -s linkip -s link里的 RX errors、RX dropped、TX dropped 这些计数,一旦持续增长,基本能说明网卡、驱动、交换机链路或虚拟化平台网络存在异常。
我有一次排查 RPC 超时,应用日志和数据库都正常,GC 曲线也平稳,最后发现ip -s link里 RX errors 在持续增加,联系网络团队检查后确认是物理链路光模块衰耗过大。如果当时没有采集网卡丢包和错误计数,这个问题可能要拖更久。
Prometheus 里对应的指标是node_network_receive_drop_total和node_network_transmit_drop_total,建议按接口分别监控。重传率最好通过netstat -s定时采集,或者从路由器、交换机侧抓取。经验阈值:重传率长期高于 1% 就要关注,高于 5% 基本能感知到业务质量下降。
2.3 conntrack 表满:新连接被静默丢弃
conntrack 是 netfilter 的连接跟踪机制,Linux 防火墙、iptables、部分 Kubernetes 网络方案都会用到。系统内核维护着一张连接跟踪表,默认上限可能只有 65536,高并发短连接或 NAT 场景下非常容易被打满。
打满后的现象很隐蔽:ping 能通,已经建立的连接也正常,但新连接建立不了,业务偶发超时;如果去看内核日志,会发现:
nf_conntrack: table full, dropping packet很多人不会去翻内核日志,所以这种故障经常被误判成应用层问题。更麻烦的是,常规监控模板不会采集 conntrack 计数,只有流量和连接数告警,流量看着不高,但连接表就是满了。
查看使用情况:
sysctl net.netfilter.nf_conntrack_count sysctl net.netfilter.nf_conntrack_max也可以用conntrack -S看到更详细的统计。监控时直接计算 count/max 的比例,超过 80% 就要告警。调优时可以增加net.netfilter.nf_conntrack_max,但要注意每多一个连接跟踪差不多要多占 300 字节内存,100 万连接大约多占 300MB,不要盲目拉满。
Kubernetes 环境的 NodePort 和 Service 转发场景特别容易踩这个坑,因为每个连接都会在节点上产生 conntrack 表项。我建议把所有节点都加上 conntrack 使用率监控,这个指标比 CPU 内存更能反映实际容量风险。
3. 进程与日志的隐性风险:僵尸、Journal 与关键进程守护
3.1 僵尸进程与 D 状态进程:不占 CPU 却堵住 PID 和 IO
进程状态也是监控盲区。很多监控系统只采集“进程是否在运行”,但进程在运行不代表它健康。
僵尸进程是已经退出但父进程没有回收的子进程,ps里会显示为Z状态。少量僵尸进程不用太紧张,但如果数量持续增长,说明父进程有问题——可能是没有调用wait(),也可能是父进程自身卡死。大量僵尸进程会占满 PID 空间,最终导致新进程无法创建,表现就是服务“突然不能 fork 了”。
统计僵尸进程:
ps -eo stat,comm | awk '$1 ~ /^Z/ {print $2}' | sort | uniq -cD 状态是“不可中断睡眠”,通常是进程在等待底层 IO 完成,比如磁盘、存储网络卡住。D 状态进程多了,load average 会飙升,但 CPU 使用率可能很低,这个组合很容易让人误判为机器负载高。我见过一台数据库服务器 load 到 50,CPU 却只有 5%,一查全是 D 状态的 IO 等待,最后确认是存储阵列控制器故障。
监控建议:不要只看 load average,要分别统计各状态进程数。Prometheus 的 node_exporter 有node_processes_state指标,Zabbix 可以用脚本定期统计ps -eo stat。对数据库、消息队列这类 IO 密集型服务,可以把 D 状态进程数和磁盘延迟放在一个告警规则里,同时触发时大概率是存储层出问题。
3.2 systemd journal 与日志文件:监控磁盘的隐形杀手
磁盘监控基本都会做,但很多人只监控分区使用率,不监控具体目录的日志增长。systemd 的 journal 就是一个典型的隐形杀手。
默认情况下,journald把日志存在/var/log/journal,如果没做任何限制,某些服务疯狂报错时,journal 文件会在几小时内涨到几个 GB,直接把/var分区写满。更气人的是,分区空间告警出来时,你第一反应是查应用日志,结果发现应用日志没多少,反而被 systemd journal 占满了。
查看 journal 占用:
journalctl --disk-usage也可以直接对目录做统计:
du -sh /var/log/journal预防办法是在/etc/systemd/journald.conf里设置大小和保留时间:
SystemMaxUse=500M MaxRetentionSec=7d但预防归预防,监控还是得有。建议单独采集/var/log/journal目录大小,并做环比增长告警——如果一小时内 journal 大小增长超过 500MB,基本能确定有服务在疯狂写错误日志。把所有日志用logrotate统一管理也很重要,否则即使 journald 限制了,/var/log下其他文件也可能撑爆分区。
3.3 监控关键进程和“前台进程”:存活不等于健康
很多人提到进程监控就说“用 systemd 自动重启不就行了”。但进程自动重启只能解决“退出”的问题,解决不了“假死”的问题。一个进程还活着、端口还在监听、但已经不响应请求,这种状态比进程退出更难发现。
所以我对关键业务的监控从来不做三层:第一层检测进程是否存在;第二层检测端口是否监听;第三层做真实健康检查,比如 HTTP 探活、数据库连接探活。
最简单的脚本思路是判断 PID 和端口:
#!/bin/bash PROC=java PORT=8080 pid=$(pgrep -f "$PROC" | head -1) if [ -z "$pid" ]; then echo "process down" exit 1 fi if ! timeout 3 bash -c "echo > /dev/tcp/127.0.0.1/$PORT" 2>/dev/null; then echo "port not listening" exit 2 fi echo ok这个脚本可以放到 crontab,也可以接入 Zabbix 自定义 key。Prometheus 则推荐用 blackbox_exporter 的 TCP/HTTP probe,能检测延迟和状态码。
还有一个容易忽略的是“前台进程”这个词。在 systemd 管理服务时,如果你用ExecStart=/opt/app/run.sh启动一个前台脚本,但只要脚本不是真正的常驻进程,systemd 可能认为服务 active,实际上业务已经死了。所以用systemctl is-active判断服务健康并不可靠,一定要结合健康检查接口或端口探测。
4. 硬件与虚拟化暗坑:CPU steal、GPU 与磁盘延迟
4.1 CPU steal:你的虚拟 CPU 正被邻居偷走
在虚拟机和云主机上,CPU 指标里有个容易被忽略的字段叫steal,也叫 CPU 偷取时间。它表示 hypervisor 抢占你的虚拟 CPU 去运行其他虚拟机的时间百分比。你可以在top里看到st列,或者用mpstat -P ALL 1查看:
mpstat -P ALL 1 4如果%steal长期超过 10%,说明宿主机资源争抢已经很严重。这时候你调应用、调内核参数、加连接池都没用,因为 CPU 时间是被宿主机拿走的,不是你自己代码消耗的。
Prometheus 里对应的指标是node_cpu_seconds_total{mode="steal"},可以通过 rate 计算这段时间的 steal 百分比。Zabbix 也有 CPU steal 相关的监控项,但很多默认模板没开启,需要手动加上。
我经历过一次云主机促销场景,业务流量没怎么涨,但接口 RT 突然翻倍,查了半天发现%steal飙到 30%。后来把实例迁移到新的宿主机,RT 立刻恢复正常。这个案例说明:云上业务如果对 CPU 敏感,除了看自身利用率,还要看 steal 的走势;它才是虚拟化资源争抢的真相。
4.2 GPU 监控:不只是算法团队的事
服务器装了 GPU,但监控系统只覆盖 CPU 和内存的情况很常见。GPU 一旦出问题,通常很突然:显存溢出导致训练任务被杀,驱动异常导致 CUDA 不可用,或者温度过高引发降频。
先明确要监控哪些指标:GPU 利用率、显存占用、温度、功耗,以及 ECC 错误计数。对意外卡故障,ECC 错误是最重要的预警信号,一旦出现 ECC error 且持续增加,说明显存颗粒大概率在劣化。
查看方式:
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,temperature.gpu,power.draw --format=csv持续监控可以用 DCGM exporter 接 Prometheus,Zabbix 则写脚本定时执行nvidia-smi并解析输出。注意:容器里跑 GPU 任务,宿主机和容器侧都要能访问 nvidia-smi,否则监控会漏掉容器视角的显存占用。
我之前遇到过集群里几张卡训练速度越来越慢,排查后发现机房空调故障导致 GPU 温度超过 95°C,触发降频保护。加了温度监控后,再做任务调度时会优先绕过温度异常的节点,问题就少了很多。
4.3 磁盘 IO 延迟与 SMART 健康:故障前最后一根稻草
磁盘监控如果只看“容量是否满了”,那基本等于没监控。真正的故障前兆藏在 IO 延迟和 SMART 属性里。
用iostat -x 1看磁盘,重点不是%util,而是r_await、w_await和aqu-sz(平均队列长度)。%util到 100% 并不一定说明磁盘慢,有些 NVMe 磁盘可以 100% 利用率但延迟很低;反过来,%util不高但r_await很高,说明 IO 请求被卡住了。
iostat -x 1再看磁盘健康状态:
smartctl -A /dev/sda关注Reallocated_Sector_Ct(重映射扇区计数)、Current_Pending_Sector(当前待映射扇区)、Offline_Uncorrectable(离线不可修正错误)。这几个计数器只要非零,就说明磁盘已经在悄悄坏道,等到分区只读或 IO 卡死就晚了。
监控工具方面,smartctl_exporter可以把 SMART 指标推到 Prometheus,Zabbix 也有现成模板。对于 SSD/NVMe,还要关注温度和磨损指标,比如Media_Wearout_Indicator;机械盘则重点看温度和重映射扇区。数据库服务器上,r_await超过 50ms 就应该告警,而不是等业务反馈“查询很慢”。
5. 补全监控盲区的落地清单与选型建议
5.1 暗坑速查表:直接对着加监控
下面是浓缩版的排查和监控参考表。如果你现在不知道从哪里入手,先按表里加:
| 暗坑 | 核心命令/查看方式 | 参考阈值 |
|---|---|---|
| 文件描述符 | cat /proc/sys/fs/file-nr | 使用率 >70% 关注,>90% 告警 |
| inode 耗尽 | df -i | 使用率 >80% 关注,>90% 告警 |
| 熵池不足 | cat /proc/sys/kernel/random/entropy_avail | <200 检查 |
| TIME_WAIT/CLOSE_WAIT | ss -tan | CLOSE_WAIT 持续增长必须处理 |
| 网卡丢包/重传 | ip -s link/netstat -s | 重传率 >1% 关注 |
| conntrack 表满 | sysctl net.netfilter.nf_conntrack_count | 使用率 >80% 告警 |
| 僵尸/D 状态进程 | ps -eo stat | 僵尸持续增长需要处理 |
| journal 日志膨胀 | journalctl --disk-usage | 单目录 >2GB 或短时暴增告警 |
| CPU steal | mpstat -P ALL 1 | 持续 >10%,持续 10 分钟 |
| GPU 温度/ECC | nvidia-smi --query-gpu=... | 温度 >85°C,ECC >0 |
| 磁盘延迟/SMART | iostat -x 1/smartctl -A | r_await >50ms 或 Pending Sector >0 |
这个表是给监控系统的“补丁清单”,每加一个指标前,先确认手动执行命令能稳定拿到数据,再接入监控平台。
5.2 用 Zabbix 还是 Prometheus:因地制宜扩展
很多团队已经跑着 Zabbix 或 Prometheus,但发现漏监控时往往犹豫要不要换平台。我的观点是:优先在现有平台补指标,别为了一个指标推翻已有监控体系。
Zabbix 的传统优势是覆盖物理机和网络设备,模板成熟,agent 安装简单。如果你要新增一个自定义指标,比如熵池,可以在 agent 配置里加:
UserParameter=entropy_avail,cat /proc/sys/kernel/random/entropy_avail然后在 Zabbix 前端创建监控项,添加触发器。注意开启UserParameter后要重启 agent,并且对自定义参数做好权限控制,避免暴露高危命令。
Prometheus 更适合云原生和弹性环境。node_exporter 已经覆盖大量系统指标,外加 blackbox_exporter 做探测。对于非标准指标,最方便的是 textfile collector:写脚本把结果输出成.prom文件,再让 node_exporter 定期读取。
echo entropy_avail $(cat /proc/sys/kernel/random/entropy_avail) > /var/lib/node_exporter/textfile/entropy.prom无论用哪个平台,关键都是先把指标补全,再谈告警。没有数据源,模板再漂亮也是空转。
5.3 告警阈值与误报控制:少而准才是王道
暗坑指标加完之后,最容易出现的问题是告警轰炸。比如熵池,本来就是波动的,瞬间低一下不代表业务受影响;如果阈值设得太敏感,半夜一两点就会连续收到无关告警,第二天人就麻木了。
我一般遵循几个原则:
- 使用百分比而不是绝对值。不同规格的机器,文件描述符上限、conntrack 上限差异很大,用百分比才能通用。
- 连续 N 次或持续 N 分钟才触发。例如“conntrack 使用率 >80% 持续 5 分钟”才告警,避免瞬时抖动。
- 告警内容里附带排查方向。比如触发 CLOSE_WAIT 告警时,提示对方去查连接池和响应体关闭逻辑,而不要只给一个数字。
- 新指标先记录一段时间基线。先观察两周再定阈值,尤其重传率、磁盘延迟这类和环境相关性强的指标。
- 宁可暂时不加,也不要批量加一堆没验证的告警。告警疲劳比没有监控更危险。
有一次我一次性给所有机器加了 SMART 告警,结果几台年久失修的机器当晚就开始报 pending sector,虽然都是真实问题,但告警节奏没控制好,运维同事反而抱怨。后来改成“预警告警”和“严重告警”两个级别,把偶发坏道和持续增长分开处理,才舒服很多。
说实话,监控系统的尊严不是靠告警数量撑起来的,而是靠“故障发生前给到干预窗口”。少而准的告警,比满屏的黄色块有价值得多。
最后说一个我自己的习惯:每次接手一台新服务器,我都会先把/proc/sys/fs/file-nr、df -i、nf_conntrack_count、ss -tan、entropy_avail、journal 占用这几个值记下来,再根据服务类型补充 GPU 或 SMART。不是要建多豪华的监控大屏,而是这些指标真的救过我太多次。上个月线上支付服务半夜抽风,最后定位到 conntrack 表被打满,而监控面板上带宽、CPU、内存全都正常。如果当时没有把 conntrack 纳入监控,估计又是一夜无眠。这些暗坑不会每天都在,但只要出现一次就足够让人长记性。希望这份清单能让你提前把坑填上,少熬几个夜。