linux-incident-triage

Category: DevOps Risk: Medium risk niuwoai/skills CC-BY-4.0
shell_executionnetwork_accessfilesystem_access

name: linux-incident-triage
description: Linux 线上故障应急排查。当服务器负载高、内存吃紧、磁盘满、服务无响应、端口连不上、进程被杀、网络丢包时使用,用于快速定位并止血。触发词:服务器卡了、负载高、CPU 100%、内存爆了、OOM、磁盘满了、no space left、服务挂了、连不上、502、504、丢包、僵尸进程、应急、排查。不负责服务器初始化安全加固和长期容量规划。

Linux 线上应急排查

应急有两条铁律,比任何命令都重要:

  1. 先保留现场,再动手。 重启能解决问题,但也会销毁全部证据,下次照样挂。
  2. 止血优先于治本。 回滚比修复快,先让用户能用,再慢慢查。

零、先留证据(60 秒)

在做任何变更之前,把现场存下来。

TS=$(date +%F-%H%M%S); D=/var/tmp/incident-; mkdir -p 
{ uptime; echo; free -m; echo; df -h; echo; df -i; } > /basic.txt 2>&1
top -b -n1 | head -50            > /top.txt 2>&1
ps auxf                          > /ps.txt 2>&1
ss -antp                         > /socket.txt 2>&1
dmesg -T | tail -200             > /dmesg.txt 2>&1
journalctl -n 2000 --no-pager    > /journal.txt 2>&1
vmstat 1 5                       > /vmstat.txt 2>&1
iostat -x 1 5                    > /iostat.txt 2>&1
echo "现场已保存到 "

df -i 别漏。inode 耗尽和磁盘满的表现一模一样,但 df -h 显示还有空间,能让人查半小时。

一、五分钟通用体检

按这个顺序过一遍,通常前三条就能定位。

uptime                  # 1/5/15 分钟负载。和 CPU 核数比,nproc 看核数
free -m                 # 看 available 那一列,不是 free
df -h; df -i            # 空间和 inode
top -o %CPU             # 谁在吃 CPU
top -o %MEM             # 谁在吃内存
ss -s                   # 连接数概览
dmesg -T | tail -50     # OOM、磁盘错误、网卡异常都在这

判读负载:负载数字要除以核数看。8 核机器负载 8 是满载,负载 30 是严重排队。但负载高不等于 CPU 高,vmstat 1b 列(不可中断阻塞)大,说明是 IO 在拖。

二、按症状分诊

CPU 打满

top -H -p <pid>                        # 定位到线程
perf top -p <pid>                      # 看热点函数
cat /proc/<pid>/status | grep -i threads

Java 用 jstack,Go 用 curl localhost:<port>/debug/pprof/profile,Python 用 py-spy dump --pid <pid>

常见原因:死循环、正则回溯、GC 频繁、突然放大的流量、加密解密没走硬件指令。

内存吃紧 / OOM

dmesg -T | grep -i -E "out of memory|killed process"
cat /proc/<pid>/status | grep -E "VmRSS|VmSwap"
smem -rs uss | head           # 按实际独占内存排序,比 RSS 准

free -mbuff/cache 高是正常的,那是可回收的页缓存。真正要看 available

被 OOM Killer 干掉的进程,dmesg 里会写明分数和内存占用。容器场景要看 cgroup 限制:

cat /sys/fs/cgroup/memory.max          # cgroup v2
cat /sys/fs/cgroup/memory/memory.limit_in_bytes   # v1

容器里 OOM 通常是 --memory 设小了,或者 JVM/Node 的堆上限没跟着容器限制走。

磁盘满

df -h; df -i
du -x -h --max-depth=1 / 2>/dev/null | sort -h | tail -20
lsof +L1                     # 已删除但句柄未释放的文件,占空间但 du 看不见

lsof +L1 是磁盘满排查里最容易被忽略的一步。日志被 rm 了但进程还开着句柄,空间不会释放,必须重启进程或者 truncate -s 0 /proc/<pid>/fd/<n>

止血:先清 /var/log 下的旧日志和 journalctl --vacuum-size=200M,再补 logrotate 配置。所有应用日志都必须配轮转,没配的迟早会以磁盘满的形式提醒你。

服务无响应 / 端口连不上

systemctl status <svc>; journalctl -u <svc> -n 200 --no-pager
ss -lntp | grep <port>              # 端口在不在监听
curl -sv --max-time 5 http://127.0.0.1:<port>/healthz

分三层确认:进程活着吗 → 端口监听吗 → 本机能连通吗。三层都通还连不上,问题在防火墙、安全组或反向代理。

iptables -S; nft list ruleset       # 本机防火墙
ss -ant state syn-recv | wc -l      # 半连接堆积,可能是 SYN flood 或 backlog 太小
cat /proc/net/sockstat              # 连接数与内存

TIME_WAIT 堆积到几万很常见且通常无害,别急着调内核参数。真需要调之前,先确认你理解那个参数的含义。

网络慢 / 丢包

ping -c 20 <目标>
mtr -rwzc 50 <目标>            # 逐跳丢包,比 traceroute 有用得多
ss -ti                          # 看 rtt、重传
ethtool -S <网卡> | grep -i -E "drop|err"

mtr 中间跳丢包但最后一跳不丢,通常是中间路由的 ICMP 限速,不是真丢包。要看最后一跳的丢包率。

三、止血手段(按优先级)

  1. 回滚。 如果故障和最近一次发布时间吻合,先回滚,别查。恢复旧产物通常一两分钟。
  2. 摘流量。 从负载均衡摘掉异常节点,保留现场慢慢查。
  3. 限流降级。 关掉非核心功能,保住主链路。
  4. 扩容。 加机器是最贵但最快的解法,适合确认是容量问题时。
  5. 重启。 排最后。重启前务必已经完成第零步的现场保留。

四、常见误区

  • 一上来就 reboot 证据全没了,且不一定能好。
  • 看到 TIME_WAIT 多就改内核参数。 尤其是打开 tcp_tw_recycle,在 NAT 环境下会造成随机连接失败,且该参数在新内核已被移除。
  • kill -9 数据库或有状态服务。kill -15 等它自己收尾。
  • 磁盘满了直接 rm 大日志。 句柄不释放,空间不回来。用 truncate -s 0 或先停服务。
  • 在没有备份确认的情况下动数据。 任何 UPDATE / DELETE 都要先确认最近备份可恢复,且必须带 WHERE
  • 只修表象不记录。 故障处理完没有复盘,同一个坑会踩三次。

五、收尾

故障恢复后必须做三件事,缺一件这次故障就白挂了:

  1. 写复盘:影响范围、时间线、直接原因、根本原因、改进项。改进项必须带负责人和截止日期。
  2. 补监控:这次是靠用户报障发现的,还是监控发现的?靠用户报障说明监控有洞,补上。重点是 CPU、内存、磁盘空间与 inode 都要有 80% 阈值告警。
  3. 验证备份:顺手确认备份能恢复。没演练过的备份等于没有备份。