Linux 运维实战:进程、磁盘、网络与排查命令清单
本文目录
后端工程师迟早要在深夜登录一台"不太对劲"的服务器:服务响应慢、磁盘莫名其妙满了、连接数涨得离谱。这时候能救你的是一套固定的排查套路,而不是现场翻搜索引擎。这篇清单按"进程 → 内存 → 磁盘 → 网络 → 权限与文本处理 → 日志与防火墙 → 排查套路"的顺序,把生产环境最常用的命令和判断标准整理成可执行的动作。
进程管理:ps、信号与 kill 的正确姿势
先看进程在干什么。ps aux 是全景,ps -ef 能看到父进程关系,两者都要会用:
ps aux --sort=-%cpu | head -10 # CPU 最高的前 10 个进程
ps -ef --forest # 树状结构,看清进程是谁拉起来的
ps -o pid,ppid,etime,cmd -p 12345 # 单个进程:PID、父 PID、已运行时间、命令
杀进程不是只有 kill -9。信号是有语义的,选错信号是"进程被杀但资源没释放"的常见原因:
| 信号 | 编号 | 语义 | 应用响应 |
|---|---|---|---|
| SIGTERM | 15 | 请求退出(默认) | 可捕获:刷新缓冲、关闭连接后退出 |
| SIGKILL | 9 | 强制终止 | 不可捕获、不可阻塞,资源清理代码不会执行 |
| SIGHUP | 1 | 挂断(终端断开) | 常被守护进程复用为"重载配置" |
| SIGINT | 2 | Ctrl+C 中断 | 可捕获,等价于用户手动打断 |
| SIGQUIT | 3 | 退出并转储核心 | 可用于要求生成 core dump 再退出 |
生产纪律:先 kill <pid>(默认 SIGTERM),等 10–30 秒确认未退出,再 kill -9。SIGKILL 直接跳过应用的清理逻辑,数据库进程用它可能导致数据文件损坏。反向同理:应用代码必须处理 SIGTERM 实现优雅退出,否则滚动发布会变成连接风暴。
kill 12345 # 先礼:SIGTERM
sleep 20
kill -0 12345 2>/dev/null && kill -9 12345 # -0 只探测存活,不发信号
top 与负载:三个数字到底在说什么
top 第一行的 load average 是 1、5、15 分钟三个值。Linux 的 load 不只统计 CPU 需求,还统计不可中断 IO(D 状态)的进程,所以它衡量的是"排队等运行的 + 等磁盘的":
uptime # 快速看三个负载值,不用进 top
top -bn1 | head -20 # 无交互快照,适合脚本采集
# load average: 4.2, 2.1, 1.3 → 1 分钟涨得快,问题刚发生
判断标准:load 与 CPU 核数比。8 核机器 load 6 属于忙碌但健康,load 16 就是饱和。单个值没有绝对意义,必须除以核数(nproc 查核数)。
top 界面里有两个高频误读:
%Cpu(s)行的us(用户态)与sy(内核态)之外,wa(iowait)高说明瓶颈在磁盘不在 CPU,加核数救不了。- 单核
%CPU可以超过 100:top 显示的是占全部核的百分比,400% 表示吃满 4 个核。
load 高 + wa 高 → 查磁盘和慢 IO;load 高 + wa 低 → 查吃 CPU 的进程。这一步分流了后面所有的排查方向。
僵尸进程:见到先查父进程
僵尸进程是已退出但父进程没有 wait() 回收的进程,它在进程表占位但不占内存。少量无害,数量持续增长说明父进程代码有 bug(fork 后不收割子进程):
ps aux | awk '$8 ~ /Z/ {count++} END {print count" zombies"}'
ps -o pid,ppid,stat,cmd -p 12345 # 确认父进程是谁
处理顺序:kill 僵尸本身是无效的(它已经死了,kill 信号没有目标可送达),要处理的是它的父进程——让父进程正常退出(内核会把孤儿托给 init 回收),或修复父进程的 wait 逻辑。僵尸不是安全或内存问题,是代码质量问题,解药在代码里不在命令行里。
内存:free 的 buffer/cache 与 available
free -h 最容易被误读的是 free 列为 0:
total used free buff/cache available
Mem: 7.6G 3.1G 120M 4.4G 4.2G
Swap: 2.0G 300M 1.7G
- free 很小是正常的:内核把空闲内存拿去做文件缓存(buff/cache),应用需要时立刻归还。
- 看 available,不看 free:available 是"估算的不触发 swap 就能给应用的量"。available 紧张才是真的内存紧张。
- swap 用了不等于坏事:少量 swap(几百 MB)可能是夜间低频页被换出;要关注的是 swap 是否在快速增长。
真正确认 OOM 要看内核日志,这是判定的唯一依据:
dmesg -T | grep -i -E "oom|out of memory" # 找 "Killed process" 行
journalctl -k --since "1 hour ago" | grep -i oom
Killed process 12345 (node) 会写明被杀的进程和当时的内存占用。如果 OOM 的受害者是数据库或应用,通常根因是同机的别的进程(或容器 limit 配置)把内存挤占——被杀的不一定是凶手。
磁盘:df、du 与"消失"的空间
df -h 看文件系统整体水位,du 看目录实际大小。经典排查是 df 说 90% 满了,du 加起来只有一半——空间"消失"有两个常见去向:
去向一:被删但句柄未释放的文件。进程还在往一个已删除的文件写(典型:日志滚动后没 reload 应用),df 占着空间,du 看不到文件:
lsof +L1 # 列出"已删除但仍被打开"的文件,SIZE 列就是占用
# 处理:reload 或重启占用进程,不要直接 rm —— rm 已经做过了,这才是问题
去向二:inode 用尽。df 显示空间充足但创建文件报 No space left on device,查 inode:
df -i # IUse% 100% 而 Use% 很低,就是海量小文件吃光 inode
du --inodes -x / | sort -n | tail -20 # 找 inode 大户(coreutils 较新版本支持)
日常两个习惯:df -h 进巡检;du -sh /var/log/* | sort -h | tail -10 是"日志又满了"的标准第一步。du 在大目录上很慢(要遍历整个目录树),对生产盘先用 -x 限制单文件系统,避免跨挂载点重复统计。
网络:ss、连接状态与 curl 分层排查
ss 是 netstat 的现代替代,功能相同、速度快一个量级。先背下 TCP 状态的排查语义:
| 状态 | 含义 | 大量出现时说明 |
|---|---|---|
| LISTEN | 服务在监听端口 | 正常,确认是预期进程在监听 |
| ESTAB | 已建立的连接 | 正常,数量突增查客户端来源 |
| TIME-WAIT | 主动关闭方等待残留报文 | 高并发短连接的正常现象,几万内不用管 |
| CLOSE-WAIT | 对方已关,本方未关 | 代码忘了 close 连接,泄漏实锤 |
| SYN-RECV | 收到连接请求未完成握手 | 洪泛攻击或半连接积压 |
ss -s # 总览:各状态连接数统计
ss -tan state close-wait # CLOSE-WAIT 的具体连接和端口
ss -ltnp # 谁在监听哪个端口(含进程名,排"端口被占"必用)
ss -tn dst :5432 # 本机到数据库的现有连接
排"端口被占用":ss -ltnp | grep 3000 直接给出 PID 和进程名。比 netstat 好用的地方是它默认读内核 socket 表,不需要遍历 /proc 全量。
对外连通性排查,curl 的分层顺序比 ping 更贴近真实问题——ping 通不代表 HTTP 通:
curl -v --max-time 5 http://upstream.internal/healthz # -v 看 DNS、TCP、TLS、HTTP 每一步
curl -s -o /dev/null -w '%{http_code} %{time_connect} %{time_starttransfer}\n' \
https://api.example.com # 计时:连接耗时 / 首字节耗时
curl -w '%{remote_ip}\n' -s -o /dev/null https://api.example.com # 确认 DNS 解析到哪
-w 的两个时间戳区分了"连不上"(time_connect 大)和"连上了但服务端慢"(starttransfer 减 connect 大),这个区分决定下一步是查网络还是查应用。
文件权限:rwx、umask 与特殊权限
权限用数字表达时每位是 r=4、w=2、x=1 的和:
| 数字 | rwx | 对文件 | 对目录 |
|---|---|---|---|
| 6 | rw- | 可读写 | 不能列目录内容 |
| 5 | r-x | 可读可执行 | 可进入、可列举 |
| 7 | rwx | 完全控制 | 可增删文件 |
关键认知:目录的 x 是"进入权",没有 x 就算有 r 也无法访问目录内文件;对目录有 w 就能删里面的文件,删除文件的权限看目录不看文件本身。
umask 决定新建文件的默认权限:umask 022 时新建文件 644(666 减 022)、目录 755(777 减 022)。应用以不同用户部署时权限错乱,十有八九是 umask 不一致(root 的 0022 vs 普通用户的 0002)。
三个特殊权限:
- SUID(4000):执行时以文件属主身份运行,如
passwd借 root 身份改影子文件。给脚本加 SUID 是大坑,很多 shell 直接忽略。 - SGID(2000):对目录生效,新文件继承目录属组,团队共享目录用它。
- Sticky bit(1000):
/tmp的典型:所有人可写,但只能删自己的文件。
ls -ld /tmp # drwxrwxrwt 里的 t 就是 sticky
chmod 2775 /srv/shared # 共享目录:SGID + 属组可写
查找与文本处理:find、grep、awk、sed
find 的正确打开方式带 -print0,因为文件名可以包含空格和换行:
find /var/log -name '*.log' -mtime +30 -print0 | xargs -0 rm -f # 安全删除 30 天前的日志
find . -type f -size +100M -print0 | xargs -0 du -h # 找大文件
管道里裸 xargs rm 遇到"my file.log"这类名字会把参数劈开,-print0 / -0 用空字符分隔文件名,一劳永逸。反过来,find -delete 处理海量文件比管道更高效且原子。
文本三件套,覆盖 80% 的日志现场:
# grep:过滤 + 计数 + 上下文
grep -c 'ERROR' app.log # 计数
grep -B2 -A10 'OOMKilled' app.log # 前两行后十行,看现场
grep -E '5[0-9]{2}' access.log # 扩展正则找 5xx
# awk:按列统计,命令行里的简易报表
awk '{sum[$1]+=1} END {for (ip in sum) print sum[ip], ip}' access.log | sort -rn | head
awk -F'"' '{print $6}' access.log | sort | uniq -c # 自定义分隔符切字段
# sed:流式替换,处理日志不要 -i 原地改
sed -n '100,120p' huge.log # 只看 100-120 行,比编辑器开大文件稳
sed 's/level=[0-9]*/level=**/g' app.log # 输出时脱敏,不动源文件
原则:分析类用 awk,改文件用 sed -i(先备份),临时看用 sed -n。sed -i 没有确认环节,先 cp file file.bak 再操作,这两个习惯值一次事故的学费。
journalctl:systemd 日志的正确打开方式
systemd 系的服务日志用 journalctl 查,按 unit 过滤远比翻文件快:
journalctl -u blog-api --since "30 min ago" -n 200 # 某服务最近半小时,最后 200 行
journalctl -u nginx -f # 跟踪日志(等价 tail -f)
journalctl -p err -S today # 只看今天 err 级别
journalctl _PID=12345 --no-pager # 按进程追历史输出
journald 的磁盘占用有上限但默认值偏大,主动控制:
journalctl --disk-usage # 当前占用
journalctl --vacuum-size=500M # 收缩到 500M
journalctl --vacuum-time=14d # 只保留两周
持久化要看 /etc/systemd/journald.conf 的 Storage=persistent——默认部分发行版是 auto 且 /var/log/journal 不存在时日志重启即丢。排障"昨天还看得到"的日志消失问题,先查这里。
防火墙:ufw 与 iptables 的概念
ufw 是 iptables 的前端,先学概念再用工具:iptables 有表(filter/nat)、链(INPUT/OUTPUT/FORWARD)和规则(匹配条件 + 动作)。ufw 把这些封装成"放行端口"的直觉操作:
ufw status verbose
ufw default deny incoming # 默认拒绝入站,白名单思路
ufw allow from 10.0.0.0/8 to any port 22 proto tcp # 仅内网段可 SSH
ufw allow 443/tcp # 对外只开 443
ufw enable
生产纪律三条:默认拒绝(default deny)+ 显式放行;改防火墙规则前先加一条定时自动回滚(sleep 300 && ufw reset 挂在后台),防止把自己锁在门外;规则变更纳入变更管理,"临时放开 3306 给外网调试"是勒索事件的经典起手式。数据库和内部服务的端口永远不应对公网 LISTEN,ss -ltn 检查监听地址是 127.0.0.1 或内网 IP,而不是 0.0.0.0 加防火墙硬扛。
排查套路:从 load 高到根因的固定路径
把前面的命令串成一条决策路径,遇到"服务慢"照着走:
- load 高是真的吗?
uptime确认,除以nproc得到饱和度。 - CPU 还是 IO 瓶颈?
top看wa:wa 高走 IO 分支,wa 低走 CPU 分支。 - 定位到进程:
top按 CPU/内存排序(P/M键),或ps aux --sort=-%cpu | head。 - IO 分支:
iotop -o看哪个进程在猛读写;iostat -x 1看%util和await判断磁盘本身是否饱和。 - CPU 分支:对可疑 PID 用
strace -p <pid> -c统计系统调用分布——read堆积是等文件,futex堆积是锁竞争;strace -f跟线程。strace 会显著拖慢目标进程,生产上抓几秒钟就要停。 - 网络嫌疑:
ss -s看状态分布,CLOSE-WAIT 多查代码泄漏,TIME-WAIT 多查连接复用配置。 - 落到应用:拿到 PID 后对照部署记录(
ps -o lstart -p <pid>看启动时间),回到日志与 APM,用 trace_id 切回业务层。
套路的价值不在单个命令,而在固定顺序消除了"哪里痒挠哪里"的随机性。建议把这套流程写成团队的 runbook,每次事故按步执行并记录在哪一步停下来,几次之后排查时间会从小时级压到分钟级。
注意事项与边界
- 本文命令以 GNU coreutils / procps / iproute2 为准,BSD/macOS 的同名命令参数不同(如
ps aux在 macOS 可用但输出列不同),跨平台脚本要探测。 kill -9不能杀 D 状态(不可中断 IO)进程——通常等 IO 返回,等不到则属于内核/硬件问题,需要评估重启窗口。lsof、iotop、strace、iostat不一定预装:lsof需要-p找句柄,iostat来自 sysstat 包,排障之前先确认工具在,不要到深夜才发现没有包管理器权限。du/find全盘扫描对 IO 敏感的服务器本身就是负载,先限定目录和文件系统(-x),再跑。- journalctl 的 vacuum 是删除旧的,不影响正在写的;但
Storage配置错误导致的"日志没了"无药可救,部署后必须验证/var/log/journal存在。 chmod -R 777是权限问题的"创可贴",每次都在扩攻击面;正确做法是理顺属主属组和 umask。- 本文示例中的 IP 均为文档专用地址段,替换为你的实际网络规划时注意 CIDR 写法,
ufw规则写错网段等于没防。
总结
Linux 排查的核心是先分类再深挖:load average 除以核数定饱和度,wa 分流 CPU 与 IO 两条路径,ss 的状态分布直接指向网络层的问题类型,df/du/lsof +L1/df -i 覆盖磁盘的所有"假满"形态。命令清单可以背,但更有价值的是把"从 load 到根因"的固定路径练成肌肉记忆——生产事故不等人,套路才救急。
参考
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。