找回密码
立即注册
搜索
发回帖 发新帖

4830

积分

0

好友

610

主题
发表于 1 小时前 | 查看: 8| 回复: 0

【前情提要】Postfix 配好了,Alertmanager 能发邮件了。小林第一次收到告警邮件,兴奋得不行。但老周说:“光收邮件不够,你得知道告警来了之后该干什么。”当天凌晨,小林的手机响了——磁盘告警。

一、凌晨两点的邮件

手机震动把小林从睡梦里拽了出来。他迷迷糊糊摸过手机,屏幕亮得刺眼:

发件人:xiaolin@qq.com
主题:[FIRING:1] HighDiskUsage (warning)
时间:2026-09-14 02:13:47

内容:
  根分区使用率超过 85%
  根分区使用率已持续 5 分钟超过 85%,当前值 87.3%

小林盯着屏幕愣了五秒,脑子才慢慢转起来。

“磁盘满了?”

他翻了个身,想继续睡:“明天再说吧……”

然后他想起老周上周说的话:

“告警响了,你不处理,它就会一直响。而且明天可能就不是 87% 了,是 97%。到时候你连 SSH 都连不上,因为系统写不了日志、建不了临时文件。”

小林一个激灵坐起来,打开电脑,连上 SSH。

二、先看磁盘:df -h

“第一件事,看哪个分区满了。”小林自言自语,回忆老周教的命令。

df -h

输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   44G  3.5G  93% /
/dev/sda2       1.0G  100M  900M  10% /boot
tmpfs           2.0G     0  2.0G   0% /dev/shm

“根分区 93% 了。”小林说,“刚才告警是 87%,现在已经 93% 了。涨得这么快?”

他看了一眼时间:凌晨 2 点 20 分。距离告警触发才 7 分钟。

“什么东西涨这么快?”

三、找大目录:du -sh

“老周说过,df 看整体,du 看细节。”小林回忆。

sudo du -sh /* 2>/dev/null | sort-rh | head -10

输出:

12G     /var
8.5G    /usr
3.2G    /home
1.1G    /opt
...

“/var 最大,12G。”小林说,“进去看看。”

sudo du -sh /var/* 2>/dev/null | sort-rh | head -10

输出:

10G     /var/log
1.2G    /var/lib
...

“/var/log 10G。”小林说,“日志怎么这么大?”

sudo du -sh /var/log/* 2>/dev/null | sort-rh | head -10

输出:

8.5G    /var/log/nginx
800M    /var/log/syslog
300M    /var/log/journal
...

“Nginx 日志 8.5G。”小林惊了,“我博客才上线两周,哪来 8.5G 日志?”

sudo ls -lh /var/log/nginx/

输出:

-rw-r----- 1 www-data adm 8.3G Sep 14 02:20 access.log
-rw-r----- 1 www-data adm 200M Sep 14 02:20 error.log

“access.log 8.3G。”小林说,“而且还在涨。”

他看了一眼 error.log,200M,也不小,但跟 access.log 比,小巫见大巫。

四、看日志内容:是什么在刷

“先看看日志里是什么。”小林说。

sudo tail -20 /var/log/nginx/access.log

输出:

192.168.1.100 - - [14/Sep/2026:02:20:01 +0800] "GET /api/health HTTP/1.1" 200 2 "-" "curl/7.81.0"
192.168.1.100 - - [14/Sep/2026:02:20:01 +0800] "GET /api/health HTTP/1.1" 200 2 "-" "curl/7.81.0"
192.168.1.100 - - [14/Sep/2026:02:20:01 +0800] "GET /api/health HTTP/1.1" 200 2 "-" "curl/7.81.0"
...

“/api/health?”小林皱眉,“我博客是静态站点,没有 /api/health 这个路径啊。”

他又往下看:

192.168.1.100 - - [14/Sep/2026:02:20:02 +0800] "GET /api/health HTTP/1.1" 404 153 "-" "curl/7.81.0"
192.168.1.100 - - [14/Sep/2026:02:20:02 +0800] "GET /api/health HTTP/1.1" 404 153 "-" "curl/7.81.0"
...

“404 了。”小林说,“有人在用 curl 疯狂请求 /api/health。每秒好几个。”

他统计了一下:

sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort-rn | head -5

输出:

500000 192.168.1.100
1200 192.168.1.101
800 192.168.1.102

“50 万次请求,全是 192.168.1.100。”小林说,“这是我 Mac 的 IP。”

他愣住了。

“我 Mac 上有什么在跑?”

五、找到源头:ps 和 lsof

“先看 Mac 上有没有什么进程在发请求。”小林回到 Mac,打开终端。

ps aux | grep curl

输出:

xiaolin  12345  0.5  0.1  ... curl -s http://192.168.1.x/api/health

“有个 curl 进程。”小林说,“哪来的?”

他仔细看:

ps -p 12345 -o pid,ppid,command

输出:

  PID  PPID COMMAND
12345  12300 curl -s http://192.168.1.x/api/health

“父进程是 12300。”小林继续查:

ps -p 12300 -o pid,ppid,command

输出:

  PID  PPID COMMAND
12300  12200 /bin/bash /Users/xiaolin/scripts/health_check.sh

“health_check.sh?”小林说,“我什么时候写过这个脚本?”

他打开脚本:

cat ~/scripts/health_check.sh

内容:

#!/bin/bash
while true; do
    curl -s http://192.168.1.x/api/health
    sleep 1
done

“这……”小林想起来了。

上周他在练习写健康检查脚本,写了一个“每秒检查一次”的循环,测试完忘了关。终端关了,但脚本还在后台跑,一直请求 Ubuntu 的 /api/health。

“我写的脚本,把我自己的博客搞挂了。”小林哭笑不得。

六、应急处理:先止血

“先停掉脚本。”小林在 Mac 上执行:

kill12300
kill12345

再次检查:

ps aux | grep health_check

没有输出。

“停了。”小林说,“Ubuntu 上的日志应该不再涨了。”

他回到 Ubuntu,观察日志:

sudo tail -f /var/log/nginx/access.log

终端不再刷新。

“不涨了。”小林松了口气。

七、清理日志:truncate 而不是 rm

“现在清理日志。”小林说。

他想起老周说过:“清理正在被进程写入的日志,不要用 rm,用 truncate。”

“为什么?”

“rm 只是删掉目录项,文件本身还在,进程还在往里写,磁盘空间不会释放。truncate 把文件大小清零,进程继续写,但空间释放了。”

小林执行:

sudo truncate -s0 /var/log/nginx/access.log
sudo truncate -s0 /var/log/nginx/error.log

再次检查:

df -h

输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   36G   12G  75% /

“从 93% 降到 75%。”小林说,“释放了 8G。”

“但 truncate 是临时的。”小林自言自语,“明天日志还会涨。得配日志切割。”

他想起老周提过 logrotate,准备明天问。

八、排错挑战:rm 了日志,磁盘还是满的

第二天到工位,小林跟老周汇报:“周哥,昨晚磁盘告警,我处理了。”

“怎么处理的?”

“停了脚本,truncate 了日志。”

“不错。”老周点头,“但如果你当时用的是 rm 呢?”

小林想了想:“rm 之后,磁盘应该也释放了吧?”

“不一定。”老周说,“你试试。”

小林在 Ubuntu 上创建一个测试文件:

dd if=/dev/zero of=/tmp/test.log bs=1M count=100

“100M 的文件。”老周说,“现在用 tail -f 持续读它。”

tail -f /tmp/test.log &

“然后 rm 掉。”

rm /tmp/test.log

“现在看磁盘。”

df -h /tmp

小林看了一眼:“还是 100M 没释放。”

“对。”老周说,“因为 tail -f 还开着这个文件,文件句柄还在。rm 只是删了目录项,文件本身没删。你 rm 了日志,但 Nginx 还在写,空间不会释放。”

“那怎么找这种文件?”

“lsof +L1。”老周说,“列出所有被删除但还被进程占用的文件。”

sudo lsof +L1

输出:

COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NLINK    NODE NAME
tail    12345   xiaolin  3r   REG  8,1  104857600     0  123456 /tmp/test.log (deleted)

“看到了吗?”老周说,“(deleted) 标记,NLINK 是 0,说明文件被删了但还被占用。”

“那怎么释放?”

“重启进程,或者 truncate 这个文件。”

sudo truncate -s0 /proc/12345/fd/3

“或者直接杀掉进程。”

kill 12345

小林试了,磁盘释放了。

排错思路总结:

  1. rm 了日志,df 显示空间没释放,说明有进程还占着文件句柄。
  2. 用 sudo lsof +L1 找出被删除但被占用的文件。
  3. 输出中 (deleted) 和 NLINK=0 是标志。
  4. 解决:重启占用进程,或 truncate -s0 /proc/PID/fd/FD。
  5. 清理正在写入的日志,用 truncate,不用 rm。
  6. 更根本的解决:配 logrotate,让日志自动切割。

“记住,”老周说,“rm 删文件,df 不释放,先 lsof +L1。这个坑,每个运维都踩过。”

九、告警恢复:[RESOLVED] 邮件

处理完磁盘,小林等了几分钟,QQ 邮箱收到一封邮件:

主题:[RESOLVED] HighDiskUsage
内容:
  根分区使用率超过 85%(已恢复)
  当前值 75%

“恢复了。”小林说。

“对。”老周说,“你处理完,Prometheus 下次评估规则时,条件不满足,告警自动恢复,Alertmanager 发恢复邮件。”

“那如果我不处理呢?”

“它会一直发。”老周说,“repeat_interval 默认 1 小时,每小时提醒你一次,直到你处理。”

“那如果我处理了,但忘了关静默呢?”

“静默过期自动失效。”老周说,“但如果你静默了没处理,告警恢复后,静默还在,那就不会有恢复邮件。静默不是处理,只是暂时闭嘴。”

小林记下:

  • 告警恢复后,send_resolved: true 会发 [RESOLVED] 邮件
  • 未处理告警,按 repeat_interval 重复提醒
  • 静默只是暂时屏蔽,不是处理

十、复盘:这次故障的根因

“现在复盘。”老周说,“这次故障,根因是什么?”

小林想了想:“我写了个死循环脚本,一直在请求博客。”

“对,这是直接原因。但根本原因呢?”

“根本原因……我没配日志切割?”

“对了一半。”老周说,“还有两个。”

“哪两个?”

“第一,你没有监控日志增长速度。你监控了磁盘使用率,但没监控‘日志每秒增长多少’。如果配了,你会在日志涨到 1G 的时候就发现,而不是等到 8.5G。”

“第二,你的脚本没有超时和退出机制。你写了个 while true,没有 timeout,没有 max_retries。这种脚本,一旦忘了关,就是灾难。”

小林在笔记本上写下:

  • 直接原因:死循环脚本疯狂请求
  • 根本原因一:日志没有切割
  • 根本原因二:没有监控日志增长速度
  • 根本原因三:脚本没有超时和退出机制

“那怎么改?”

“三个改进。”老周说,“第一,配 logrotate,明天教你。第二,加一条告警:‘日志每秒增长超过 10M 持续 1 分钟’。第三,你以后写脚本,while 循环必须加 timeout 和退出条件。”

小林记下。

十一、练习

  1. 练习一:用 dd 创建一个 200M 的测试文件,用 tail -f 持续读它,然后 rm 掉。用 df -h 确认磁盘空间没释放,用 sudo lsof +L1 找到被占用的文件,用 truncate 释放空间。
  2. 练习二:在 Ubuntu 上模拟一次磁盘告警:用 dd 填充根分区到 85%,观察 Prometheus 告警从 inactive → pending → firing 的过程。收到邮件后,用 truncate 清理文件,观察告警恢复。
  3. 练习三:写一个脚本 ~/log_growth_check.sh,检查 /var/log/nginx/access.log 的大小,如果超过 1G,输出警告并记录到 ~/log_growth_warning.txt。用 cron 配置为每 5 分钟运行一次。

参考答案

练习一:

dd if=/dev/zero of=/tmp/test.log bs=1M count=200
tail -f /tmp/test.log &
rm /tmp/test.log
df -h /tmp
sudo lsof +L1 | grep test.log
sudo truncate -s 0 /proc/$(pgrep tail)/fd/3
kill $(pgrep tail)
df -h /tmp

练习二:

# 填充磁盘
dd if=/dev/zero of=/tmp/fill.img bs=1M count=10000
# 观察 Prometheus Alerts 页面
# 收到告警后清理
rm /tmp/fill.img
df -h
# 等待告警恢复

练习三:

nano ~/log_growth_check.sh

内容:

#!/bin/bash
LOG_FILE="/var/log/nginx/access.log"
THRESHOLD=$((1024 * 1024 * 1024))  # 1G

if [ -f "$LOG_FILE" ]; then
    SIZE=$(stat -c %s "$LOG_FILE")
    if [ "$SIZE" -gt "$THRESHOLD" ]; then
        echo "$(date): $LOG_FILE 大小超过 1G,当前 $((SIZE / 1024 / 1024))M" >> ~/log_growth_warning.txt
    fi
fi
chmod+x ~/log_growth_check.sh
crontab -e

添加:

*/5 * * * * /home/devops/log_growth_check.sh

十二、下集预告

磁盘告警处理完了,复盘也做了。但老周说:“你昨晚是运气好,日志只是大,没把系统写死。如果磁盘 100% 了,你 SSH 都连不上。”小林问:“那怎么办?”老周说:“配 logrotate,让日志自己切割。”小林打开 logrotate 文档,准备配置。但他不知道,daily、rotate、postrotate、copytruncate,每一个都有讲究。

下一集:《日志切割:别让日志把磁盘撑爆》




上一篇:Checkpoint 与上下文压缩:OpenAI dots 如何让 AI Agent 7×24 不重启
下一篇:ComfyUI 入门第1篇:零基础国漫创作为什么首选它?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-4 22:40 , Processed in 0.110005 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表