运维故障排查实战 ②:磁盘爆满 · 端口占用 · CPU飙高 · DNS 异常
运维故障排查实战 ②:磁盘爆满 · 端口占用 · CPU飙高 · DNS 异常
标签:运维
开篇说明
本文记录从第③个场景开始的运维实战学习内容,承接"502/504"和"缓存不生效"两篇笔记。
前置概念:进程基础知识
在进入具体场景前,先搞清几个核心概念——这些是排查所有运维问题的基础。
进程的状态(STAT)
用 ps aux 可以看每个进程的状态(STAT 列):
| 状态 | 含义 | 说明 |
|---|---|---|
R |
Running | 正在运行或在运行队列中排队 |
S |
Sleeping | 休眠等待中——大部分正常进程都是这个状态 |
D |
Uninterruptible Sleep | 不可中断睡眠——通常是在等磁盘 IO,如果持续不恢复则异常 |
Z |
Zombie | 僵尸进程——已结束但父进程没回收资源 |
T |
Stopped | 已暂停——被 STOP 信号或 Ctrl Z 暂停了 |
进程的信号(Signal)
kill 命令本质是给进程发送一个"信号",让它做出响应:
| 信号 | 编号 | 含义 | 进程反应 |
|---|---|---|---|
SIGTERM |
15 | 请正常结束 | 进程收到后可以做清理工作再退出(默认) |
SIGINT |
2 | 中断 | 相当于键盘按 Ctrl C |
SIGKILL |
9 | 强制杀死 | 进程无法忽略,直接结束,没机会做清理 ⚠️ |
SIGSTOP |
19 | 暂停 | 相当于 Ctrl Z,进程暂停执行 |
SIGCONT |
18 | 继续 | 让被暂停的进程继续运行 |
实际工作中的正确操作顺序:
kill PID # SIGTERM - 先好好说"请退出"
# 等几秒...
kill -2 PID # SIGINT - 再给个 Ctrl C
# 等几秒...
kill -9 PID # SIGKILL - 最后手段才强制杀
为什么不能一上来就用 kill -9?
进程被 SIGKILL 时来不及做清理——比如没关闭文件句柄、没写回缓存数据,可能导致数据损坏或下次启动异常。
查看进程信息的三大工具
ps —— 查看进程快照
ps 显示的是执行瞬间的快照,不是实时更新的。
# 最常用——看所有进程
ps aux
# 只看某个程序
ps aux | grep nginx
# 看特定 PID 的详细信息
ps -p <PID>
# 只显示你关心的列
ps -p <PID> -o pid,ppid,user,%cpu,%mem,stat,start,time,command
# 按内存使用排序(谁吃内存最多)
ps aux --sort=-%mem | head -10
# 按 CPU 使用排序(谁吃 CPU 最多)
ps aux --sort=-%cpu | head -10
# 显示进程树状关系(父→子)
ps auxf
输出字段详解:
| 字段 | 含义 |
|---|---|
USER |
进程所有者 |
PID |
进程 ID(唯一编号) |
%CPU |
CPU 使用率百分比 |
%MEM |
内存使用率百分比 |
STAT |
进程状态(见上表) |
START |
启动时间 |
TIME |
累计占用 CPU 的时间 |
COMMAND |
启动命令(含参数) |
lsof —— 查看进程打开的文件
lsof 常用参数组合:
| 命令 | 用途 |
|---|---|
lsof -i -P -n \| grep :8080 |
查谁占用了 8080 端口 |
lsof -i -P -n -p <PID> |
查某个进程连了哪些网络 |
lsof \| grep deleted |
查"幽灵文件"(删了但空间没释放) |
lsof /var/log/nginx/access.log |
查谁在写这个日志文件 |
lsof 输出示例:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
tail 2050966 root 3r REG 253,1 14 8389077 /tmp/demo_tail.log
tail —— 查看和跟踪文件尾部内容
tail /var/log/nginx/access.log # 看最后 10 行(默认)
tail -n 50 /var/log/nginx/access.log # 看最后 50 行
tail -f /var/log/nginx/access.log # 实时跟踪(-f = follow)
tail -f -n 100 /var/log/nginx/access.log # 从最后 100 行开始实时跟踪
重点理解 tail -f:
tail -f 启动后,进程持续占用该文件的句柄(file descriptor)——就像一只手一直抓着这个文件不放。即使文件被删除,只要这个进程还在,文件占用的磁盘空间就不会释放(这就是"幽灵文件"的成因)。
三命令对比总结
| 命令 | 看什么 | 何时用 | 特点 |
|---|---|---|---|
ps |
进程快照(瞬间状态) | 查进程有没有在跑、状态是什么、CPU/内存占多少 | 静态的,执行瞬间的信息 |
lsof |
打开的文件/网络 | 查端口被谁占了、文件被谁打开了 | 看的是"资源关联关系" |
tail |
文件尾部内容 | 看日志、实时跟踪输出 | 操作的是"文件内容",不是进程本身 |
三者的关联:
ps 看到进程 PID → 想知道这进程在连啥网络 → lsof -i -P -n -p <PID>
ps 看到进程 PID → 想知道它在写什么文件 → lsof -p <PID>
ps 看到进程 PID → 想跟踪它的实时日志 → tail -f /var/log/xxx
/proc// —— 进程的"档案文件夹"
每个进程在 /proc 下都有一个数字命名的目录,里面装着这个进程的所有信息。
这是 Linux 最强大的排查手段之一,因为不需要额外工具,cat 和 ls 就能看全部信息。
# 最常用的几个 /proc 条目:
cat /proc/<PID>/cmdline | tr '\0' ' ' # 完整启动命令(比 ps 更准)
ls -l /proc/<PID>/cwd # 工作目录(在哪启动的)
ls -l /proc/<PID>/exe # 可执行文件路径(程序本体在哪)
cat /proc/<PID>/environ | tr '\0' '\n' # 环境变量(包含大量线索)
cat /proc/<PID>/status # 进程状态详情
ls -l /proc/<PID>/fd/ # 打开的所有文件描述符
场景③:磁盘爆满(Disk Full)
排查流程四步走
第一步:看全局——df
df -h
输出示例:
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 90G 43G 47G 48% /
重点关注 Use% 列——超过 90% 就要告警了。
第二步:定位一级目录——du
du -sh /* 2>/dev/null | sort -rh | head -15
| 部分 | 含义 |
| /* | 根目录下所有一级目录 |
| 2>/dev/null | 过滤权限报错 |
| sort -rh | 按大小倒序(最大的排上面) |
| head -15 | 只看前 15 条 |
第三步:逐层深入
# 看 /var 下谁最大
du -sh /var/* 2>/dev/null | sort -rh | head -10
# 看 /var/log 下谁最大
du -sh /var/log/* 2>/dev/null | sort -rh | head -10
第四步:快速定位所有大文件——find
find /var -type f -size 100M -exec ls -lh {} \; 2>/dev/null
参数拆解:
| 部分 | 含义 |
| -exec ls -lh {} \; | 对每个找到的文件执行 ls -lh |
| 2>/dev/null | 过滤权限报错 |
-exec 语法:
find ... -exec 要执行的命令 {} \;
│ │ │ └─ 结束标记(分号转义)
│ │ └──── 占位符,代表当前找到的文件路径
│ └─────────────────── 对每个结果执行的操作
└────────────────────────────── 参数开始
注意:\; 不能写成 ;,否则 bash 会把分号当作自己的语句结束符,导致 find: missing argument to '-exec' 报错。
快速扫全盘大文件的变种:
幽灵文件(删了但没释放空间)
这是磁盘清理最常踩的坑。
现象
df -h # 显示磁盘还是满的
du -sh /* # 加起来的空间比 df 显示的小
原因
一个进程正在使用某个文件,你 rm 了它,目录里看不到了,但进程的文件句柄还在,内核不会释放磁盘空间。
# 查找这些"幽灵文件"
lsof | grep deleted
# 输出示例:
# COMMAND PID USER FD TYPE SIZE NODE NAME
# python3 1234 root 3w REG 500M 12345 /var/log/app.log (deleted)
正确处理流程
# ① 找到大文件
find / -type f -size 500M -exec ls -lh {} \; 2>/dev/null
# ② 删掉(但空间可能还没释放)
rm bigfile.log
# ③ df -h 检查,空间没变?
df -h
# ④ lsof | grep deleted 看有没有进程占着
lsof | grep deleted
# ⑤ 重启对应的服务或 kill 进程
systemctl restart xxx
# 或
kill <PID>
# ⑥ 再 df -h 确认空间释放
df -h
安全删除建议
不确定能不能删的文件,先 mv 而不是 rm:
场景④:端口被占用(Port Conflict)
现象
启动服务时看到报错:
Error: listen tcp :8080: bind: address already in use
排查流程
第一步:查谁占了端口——ss
ss -tlnp | grep :8080
第二步:查进程身份——决定能不能杀
方法 1:ps 看基本信息
ps -p 2053039 -o pid,ppid,user,%cpu,%mem,start,command
| 看到什么 | 推断什么 |
|---|---|
ppid=1 |
父进程是 init/systemd,可能是孤儿进程被收养 |
command=python3 -m http.server 8080 |
手动启动的测试服务 |
start=15:28:13 |
刚启动不久 |
方法 2:/proc 看完整信息
cat /proc/2053039/cmdline | tr '\0' ' ' # 完整命令
ls -l /proc/2053039/exe # 可执行文件路径
ls -l /proc/2053039/cwd # 工作目录
cat /proc/2053039/environ | tr '\0' '\n' # 环境变量
cat /proc/2053039/status # 状态详情
方法 3:看进程树
ps auxf | grep 2053039 # 进程树格式
实际工作中的判断依据:
# ① 是不是 systemd 管理的服务?
systemctl status <PID>
# ② 是不是重要业务进程?
ps -p <PID> -o command
# ③ 有没有其他依赖?
# 独立进程 → 可杀。有子进程/父进程相关 → 要谨慎
第三步:释放端口——kill
kill <PID> # SIGTERM (15) — 请正常退出
kill -2 <PID> # SIGINT (2) — 相当于 Ctrl C
kill -9 <PID> # SIGKILL (9) — 强制杀死(最后手段)
判断能不能杀的总结:
| 场景 | 结论 |
|---|---|
| 手动启动的测试服务,不依赖其他进程 | ✅ 安全可杀 |
| systemd 管理的正式服务 | 用 systemctl restart xxx 而不是 kill |
| 数据库、消息队列等中间件 | 谨慎,走正规重启流程 |
| 不认识的进程 | 先查网络连接→确认不是挖矿→再 kill |
场景⑤:CPU 飙高(CPU Spike)
排查方法
第一步:top 看全局
top
top 交互快捷键:
| 按键 | 作用 |
|---|---|
P |
按 CPU 使用率排序(大写) |
M |
按内存使用率排序 |
c |
显示完整命令行 |
1 |
看每个 CPU 核分别的使用率 |
k |
杀进程(会提示输入 PID) |
q |
退出 |
# 非交互模式(适合脚本或快速查看)
top -b -n 1 -o %CPU | head -17
# 显示完整命令
top -b -n 1 -o %CPU -c | head -17
第二步:解读 top 输出
第一行:系统概况
top - 15:37:45 up 80 days, 5 users, load average: 1.00, 0.64, 0.32
↑ ↑ ↑ ↑ ↑ ↑
当前时间 开机天数 在线用户数 1分钟负载 5分钟 15分钟
load average(负载均值)怎么看?
数值代表排队等待 CPU 的任务数量。以 1 核机器为例:
| 数值 | 含义 |
|---|---|
0.5 |
CPU 有一半空闲 ✅ |
1.0 |
CPU 刚好满负荷 ⚠️ |
2.0 |
超载一倍,任务在排队 ❌ |
第三行:CPU 构成(最重要的行)
%Cpu(s): 51.6 us, 9.5 sy, 0.0 ni, 38.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
| 字段 | 全称 | 含义 | 高了说明什么 |
|---|---|---|---|
| us | user | 用户程序占用 | 应用代码有问题(死循环、慢查询) |
| sy | system | 内核/系统调用占用 | 系统调用太频繁(IO、网络、锁竞争) |
| ni | nice | 低优先级程序占用 | 一般不用管 |
| id | idle | 空闲比例 | 这个越低 CPU 越忙 |
| wa | iowait | 等待 IO | ⚠️ 磁盘是瓶颈 |
| hi | hard IRQ | 硬件中断 | 硬件问题 |
| si | soft IRQ | 软件中断 | 网络软中断过高 |
| st | steal | 被宿主机偷走(VM) | ⚠️ 云服务器超卖 |
实战口诀: us 高→查应用;sy 高→查内核/系统调用;wa 高→查磁盘;st 高→找云服务商
第三步:确认进程——看进程列表
PID USER %CPU %MEM COMMAND
2057576 root 100.0 0.2 python3 ← 单个进程占满一个核
第四步:查进程在干什么——strace
# 统计模式(看系统调用分布),跑几秒按 Ctrl C 停止
strace -p <PID> -c
输出解读:
| 看到大量 | 结论 |
|---|---|
read/write |
磁盘 IO 瓶颈 |
epoll_wait/poll |
网络等待 |
open/close |
频繁打开文件 |
| 几乎没有系统调用 | CPU 在纯计算(死循环、math 运算等) |
CPU 处理流程总结
发现 CPU 100%
│
├─ 是已知服务?
│ ├─ 是 → 看日志 → strace → 重启 / 优化代码
│ └─ 否 → 查网络连接 → 确认不是挖矿 → kill
│
└─ 想快速止血?
└─ kill <PID> 先恢复,再查日志根因
场景⑥:DNS 异常
Linux 域名解析流程(按顺序)
你输入 www.baidu.com
│
▼
① /etc/hosts ← 手写的"小电话本"(优先级最高)
└─ 有记录?→ 直接返回 IP
│ 没有
▼
② DNS 缓存 ← 之前查过的记录会记住
└─ 有缓存?→ 快速返回
│ 没有
▼
③ /etc/resolv.conf ← 配置着 DNS 服务器的 IP
└─ 去问 DNS 服务器
│
▼
拿到 IP,返回
示例输出:
127.0.0.1 localhost localhost.localdomain
127.0.0.1 VM-0-17-opencloudos
作用:
| 用途 | 示例 |
|---|---|
| 本地开发测试 | 127.0.0.1 myapp.test |
| 屏蔽网站 | 127.0.0.1 ads.example.com |
| 内网互访 | 192.168.1.10 db-server |
⚠️ 常见故障: 有人不小心在 hosts 里写了 127.0.0.1 baidu.com,然后你的应用就永远连不上百度了。
修改 /etc/hosts:
- 直接编辑文本文件(vim /etc/hosts 或 echo "ip domain" >> /etc/hosts)
- 修改后立即生效,不需要重启
/etc/resolv.conf —— 配置"去问谁"
cat /etc/resolv.conf
核心内容只有一行最重要的:
nameserver 183.60.83.19
nameserver 183.60.82.98
| 内容 | 含义 |
|---|---|
nameserver |
指令:指明 DNS 服务器的 IP |
183.60.83.19 |
腾讯云内网 DNS 服务器 |
常见 DNS 服务器:
| DNS 服务器 | IP | 说明 |
|---|---|---|
| 腾讯云内网 DNS | 183.60.83.19 | 腾讯云服务器默认 |
| 阿里云内网 DNS | 100.100.2.136 | 阿里云服务器默认 |
| 114 DNS | 114.114.114.114 | 国内通用 |
| Google DNS | 8.8.8.8 | 全球通用,国内可能慢 |
| Cloudflare DNS | 1.1.1.1 | 快,注重隐私 |
nameserver 有数量限制吗?
有,最多 3 个。第 4 个会被忽略。
工作方式: 按顺序一个一个试,第一个超时了才问第二个,不是同时问。
/etc/hosts 和 /etc/resolv.conf 里 IP 的区别
这是初学者最容易混淆的概念:
| 文件 | 里面的 IP 是什么 | 角色 | 类比 |
|---|---|---|---|
/etc/hosts |
目的地 IP | "这就是答案" | 通讯录里存的号码 |
/etc/resolv.conf |
DNS 服务器 IP | "帮我去问他" | 查号台 114 的号码 |
dig —— 直接查 DNS 服务器
dig 最大的特点:跳过 /etc/hosts,直接去问 DNS 服务器。
# 简洁模式(只看 IP 地址)
dig short baidu.com
# 完整模式(看到所有细节)
dig baidu.com
第二段:问题部分——你问了什么
;; QUESTION SECTION:
;baidu.com. IN A
↑ ↑
网络类型 记录类型
A 记录是什么?
| DNS 记录类型 | 含义 | 示例 |
|---|---|---|
| A | 域名 → IPv4 | baidu.com → 110.242.74.102 |
| AAAA | 域名 → IPv6 | baidu.com → 2400:da00::666 |
| CNAME | 别名 | www.baidu.com → www.a.shifen.com |
| MX | 邮件交换记录 | 指定邮件服务器 |
| TXT | 文本记录 | 域名验证等 |
第三段:答案部分——DNS 回了什么
;; ANSWER SECTION:
baidu.com. 30 IN A 111.63.65.103
↑ ↑ ↑ ↑ ↑
域名 TTL 类型 IP地址
TTL(Time To Live) = 缓存时间(秒)。
| TTL 值 | 含义 | 常见场景 |
|---|---|---|
30 |
30 秒后过期 | 大型网站(百度、Google),方便 IP 快速切换 |
300 |
5 分钟 | CDN 服务 |
86400 |
1 天 | 小型站点,解析变化少 |
第四段:统计信息
;; Query time: 1 msec ← 1 毫秒,非常快
;; SERVER: 183.60.83.19#53 ← 哪个 DNS 回复的,#53 是 DNS 端口
;; WHEN: Sun Jul 05 16:26:41 ← 查询时间
;; MSG SIZE rcvd: 91 ← 回复包大小(91 字节)
getent hosts —— 系统实际用的解析结果
getent hosts baidu.com
getent = "get entries",从系统配置的解析库中获取条目。
和 dig 的核心区别:
dig → 直接问 DNS 服务器(跳过 /etc/hosts)
getent hosts → 按系统配置的顺序查(/etc/hosts → 缓存 → DNS 服务器)
DNS 异常排查核心方法
两条命令对比法
# 两条命令同时跑,对比结果:
dig short baidu.com # "这是正确答案"
getent hosts baidu.com # "系统实际用了什么"
| dig 结果 | getent 结果 | 结论 |
|---|---|---|
| 正常 IP | 正常 IP | ✅ DNS 正常 |
| 正常 IP | 不同 IP | ❌ /etc/hosts 被人改了 |
| 查不到 | 查不到 | ❌ DNS 服务器或网络问题 |
| 查不到 | 正常 | ❌ 罕见,缓存异常 |
选 DNS 服务器查
# 用 8.8.8.8 来查(@ 指定 DNS 服务器)
dig @8.8.8.8 short baidu.com
# 用 114.114.114.114 来查
dig @114.114.114.114 short baidu.com
| 原 DNS 查不到 | @8.8.8.8 查得到 | 结论 |
|---|---|---|
| ❌ | ✅ | 原 DNS 服务器挂了,改 resolv.conf |
| ❌ | ❌ | 域名本身问题或网络不通 |
完整排查流程
# ① 能不能 ping 通域名?
ping baidu.com
# ② ping 不通?查 DNS
dig short baidu.com
# ③ dig 也查不到?查 DNS 配置
cat /etc/resolv.conf
# ④ 换个 DNS 试试
dig @8.8.8.8 short baidu.com
# ⑤ 对比看是不是 hosts 问题
getent hosts baidu.com
附录:命令速查表
磁盘排查
| 步骤 | 命令 | 作用 |
|---|---|---|
| 全局 | df -h |
看磁盘使用率 |
| 定位 | du -sh /* \| sort -rh |
找最大的一级目录 |
| 钻入 | du -sh /var/log/* \| sort -rh |
逐层深入 |
| 大文件 | find / -size 100M -exec ls -lh {} \; |
找所有大文件 |
| 幽灵文件 | lsof \| grep deleted |
找删了但没释放的 |
端口排查
| 步骤 | 命令 | 作用 |
|---|---|---|
| 查占用 | ss -tlnp \| grep :8080 |
谁占的端口 |
| 查进程 | ps -p <PID> -o pid,ppid,command |
是什么进程 |
| 释放 | kill <PID> |
杀掉进程 |
CPU 排查
| 步骤 | 命令 | 作用 |
|---|---|---|
| 全局 | top |
看 CPU 排行 |
| 细节 | cat /proc/<PID>/cmdline |
看完整命令 |
| 跟踪 | strace -p <PID> -c |
看系统调用 |
| 上下文 | cat /proc/<PID>/status \| grep ctxt |
看 CPU 争抢程度 |
DNS 排查
| 命令 | 作用 |
|---|---|
dig short 域名 |
直接问 DNS |
dig 域名 |
完整 DNS 响应信息 |
dig @8.8.8.8 short 域名 |
指定 DNS 服务器来查 |
getent hosts 域名 |
系统级解析结果 |
cat /etc/hosts |
看本地电话本 |
cat /etc/resolv.conf |
看 DNS 配置 |
进程信息三件套对比
| 命令 | 全称 | 看什么 | 典型用途 |
|---|---|---|---|
ps |
process status | 进程快照(瞬间状态) | PID / CPU% / MEM% / 状态 |
lsof |
list open files | 进程打开的文件和网络 | 端口占用 / 谁在用文件 |
tail |
— | 文件尾部内容 | 看日志 / 实时跟踪输出 |
一句话记:
- ps 看进程本身(活着没、吃了多少资源)
- lsof 看进程的资源(占着谁的端口、写着哪个文件)
- tail 看进程写的内容(日志里到底写了什么)