【前情提要】
磁盘告警处理完了,复盘也做了。但老周说:“你昨晚是运气好,日志只是大,没把系统写死。如果磁盘 100% 了,你 SSH 都连不上。”小林问:“那怎么办?”老周说:“配 logrotate,让日志自己切割。”
一、老周的追问
早上到工位,小林把昨晚的磁盘告警处理过程汇报了一遍。
老周听完,点了点头,然后问了一个问题:
“你昨天 truncate 了日志,磁盘从 93% 降到 75%。那明天呢?”
“明天……日志继续写,继续涨?”
“对。”老周说,“你 truncate 是‘把杯子倒空’,但杯子还在接水。你今天倒一次,明天它又满了。你得让杯子自动换,而不是手动倒。”
“怎么自动换?”
“logrotate。”老周说,“Linux 自带的日志切割工具。它按时间或大小,自动把旧日志切走、压缩、保留一定数量、删除更早的。你配好之后,就不用管了。”
“那我昨天为什么没想到?”
“因为你还没学。”老周说,“今天教你。配完这个,你以后不会再因为日志满磁盘被告警吵醒了。”
二、logrotate 是什么?
“logrotate 是 Linux 的日志管理工具。”老周说,“它干四件事:”
第一,切割。
“把当前日志改名,比如 access.log 改成 access.log.1,然后让 Nginx 重新打开一个新的 access.log 继续写。”
第二,压缩。
“旧日志用 gzip 压缩,节省空间。access.log.1 变成 access.log.1.gz。”
第三,保留。
“只保留最近 N 份,比如 7 份。第 8 份自动删除。”
第四,定时。
“logrotate 由 cron 或 systemd timer 定时触发,默认每天跑一次。”
“那它怎么知道要切割哪些日志?”
“看配置文件。”老周说,“主配置在 /etc/logrotate.conf,各个服务的配置在 /etc/logrotate.d/ 下。你进去看看。”
小林敲:
ls /etc/logrotate.d/
输出:
apt
bootlog
dpkg
nginx
rsyslog
ufw
...
“看到了吗?nginx 已经有配置文件了。”老周说,“你打开看看。”
cat /etc/logrotate.d/nginx
输出:
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
“这是 Ubuntu 默认的 Nginx 日志切割配置。”老周说,“它已经配好了,但你从来没检查过,对不对?”
小林脸一红:“我没看过。”
“所以你磁盘满了,不是因为没有 logrotate,而是因为你没有验证它到底有没有在跑。”老周说,“今天你把这份配置搞懂,再验证一遍。”
三、logrotate 配置详解
老周拿过小林的笔记本,逐行解释。
/var/log/nginx/*.log
“匹配 /var/log/nginx/ 下所有 .log 文件。access.log 和 error.log 都会被处理。”
daily
“每天切割一次。也可以写 weekly、monthly、size 100M。size 100M 表示日志超过 100M 就切,不等一天。”
missingok
“如果日志文件不存在,不报错,继续处理下一个。避免因为某个日志没生成而中断。”
rotate 14
“保留 14 份旧日志。第 15 份自动删除。你 daily + rotate 14,就是保留 14 天。”
compress
“压缩旧日志。用 gzip,文件名变成 .gz。”
delaycompress
“延迟压缩。刚切割的日志不立刻压缩,等下一次切割时再压。为什么?因为有些程序切割后还在写旧文件,立刻压缩会报错。”
notifempty
“如果日志是空的,不切割。”
create 0640 www-data adm
“切割后,创建新日志文件,权限 0640,属主 www-data,属组 adm。如果不写 create,新日志可能权限不对,Nginx 写不了。”
sharedscripts
“多个日志文件,postrotate 只执行一次。如果不写,每个日志文件都会执行一次。”
prerotate ... endscript
“切割前执行的脚本。这里是检查 httpd-prerotate 目录,运行里面的脚本。”
postrotate ... endscript
“切割后执行的脚本。这里是通知 Nginx 重新打开日志文件。”
小林看着 postrotate 里的 invoke-rc.d nginx rotate,问:“这行是干啥的?”
“通知 Nginx:‘日志切了,你重新打开新日志文件’。”老周说,“Nginx 收到信号后,关闭旧日志句柄,打开新日志。如果你不通知它,它会继续写旧的 access.log.1,而新的 access.log 是空的。”
“那 invoke-rc.d 和 systemctl reload nginx 有啥区别?”
“invoke-rc.d 是 Debian/Ubuntu 的兼容脚本,最终也是调 systemctl。”老周说,“你可以改成 systemctl reload nginx,更直接。”
小林记下:
daily:每天切割rotate 14:保留 14 份compress:压缩旧日志delaycompress:延迟压缩create:切割后创建新日志,指定权限postrotate:切割后通知 Nginx 重新打开日志
四、手动测试 logrotate
“配置看懂了,现在测试。”老周说,“logrotate 默认由 cron 每天跑,但你不能等到明天。用 -d 模拟运行。”
小林敲:
sudo logrotate -d /etc/logrotate.d/nginx
输出:
reading config file /etc/logrotate.d/nginx
...
Considering log /var/log/nginx/access.log
Now: 2026-09-14 10:00
Last rotated at 2026-09-14 00:00
log does not need rotating (log has already been rotated)
...
“-d 是 debug 模式,只显示会做什么,不实际执行。”老周说,“你看到 log does not need rotating,说明今天已经切割过了,不用再切。”
“那我想强制切一次呢?”
“-f。”
sudo logrotate -f /etc/logrotate.d/nginx
“-f 强制切割,不管时间。”老周说,“你跑一下,然后看日志目录。”
小林执行:
sudo logrotate -f /etc/logrotate.d/nginx
sudo ls -lh /var/log/nginx/
输出:
-rw-r----- 1 www-data adm 0 Sep 14 10:05 access.log
-rw-r----- 1 www-data adm 8.3G Sep 14 02:20 access.log.1
-rw-r----- 1 www-data adm 200M Sep 14 02:20 error.log
-rw-r----- 1 www-data adm 0 Sep 14 10:05 error.log.1
“access.log 变成 0 字节,access.log.1 是旧的 8.3G。”小林说,“但 access.log.1 没压缩。”
“因为 delaycompress。”老周说,“它延迟压缩,等下一次切割时才压。你再强制切一次。”
sudo logrotate -f /etc/logrotate.d/nginx
sudo ls -lh /var/log/nginx/
输出:
-rw-r----- 1 www-data adm 0 Sep 14 10:06 access.log
-rw-r----- 1 www-data adm 0 Sep 14 10:05 access.log.1
-rw-r----- 1 www-data adm 1.2G Sep 14 10:05 access.log.2.gz
“看到了吗?access.log.2.gz 是压缩后的旧日志,1.2G。”老周说,“从 8.3G 压到 1.2G,省了 7G。”
“那 access.log.1 是 0 字节?”
“对,因为第二次切割时,第一次的 access.log.1 被压缩成 access.log.2.gz,然后新的 access.log.1 是空的。这就是 delaycompress 的行为。”
小林记下:
logrotate -d:模拟运行,不实际执行logrotate -f:强制切割delaycompress:旧日志延迟一次再压缩- 切割后:
access.log是新的,access.log.1是上一次的,access.log.2.gz是压缩的更早的
五、验证 logrotate 是否在跑
“你现在知道怎么手动切了。”老周说,“但关键是,它每天自动跑了吗?”
“怎么看?”
“看 logrotate 的状态文件。”
cat /var/lib/logrotate/status
输出:
logrotate state -- version 2
"/var/log/nginx/access.log" 2026-9-14
"/var/log/nginx/error.log" 2026-9-14
...
“2026-9-14,说明今天跑过了。”老周说,“如果日期是很久以前,说明 logrotate 没跑。”
“那它由谁触发?”
“Ubuntu 上,logrotate 由 systemd timer 触发。”
systemctl status logrotate.timer
输出:
● logrotate.timer - Daily rotation of log files
Loaded: loaded (/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled)
Active: active (waiting) since ...
Trigger: Tue 2026-09-15 00:00:00 CST; 13h left
“active (waiting),说明 timer 在等,明天 0 点触发。”老周说,“enabled,说明开机自启。”
“那如果我想改成每小时跑一次呢?”
“改 timer。”老周说,“但你不一定需要。日志量大的话,可以用 size 配置,而不是改 timer。比如 size 100M,日志超过 100M 就切,不等每天。”
“那 logrotate 自己有没有日志?”
“有。”老周说,“/var/lib/logrotate/status 是状态,/var/log/syslog 里有 logrotate 的执行记录。你 grep 一下。”
sudo grep logrotate /var/log/syslog | tail -10
输出:
Sep 14 00:00:01 ubuntu systemd[1]: Starting Daily rotation of log files...
Sep 14 00:00:02 ubuntu logrotate[1234]: rotating pattern: /var/log/nginx/*.log
Sep 14 00:00:02 ubuntu systemd[1]: Finished Daily rotation of log files.
“看到了吗?今天 0 点跑的。”老周说,“你以后排查日志切割问题,先看 /var/lib/logrotate/status,再看 /var/log/syslog。”
小林记下:
- 状态文件:
/var/lib/logrotate/status - 触发方式:
systemd timer,logrotate.timer - 查看状态:
systemctl status logrotate.timer - 执行日志:
grep logrotate /var/log/syslog
六、排错挑战:日志切割后,Nginx 还在写旧文件
下午,小林想试试 copytruncate 的写法。他把 postrotate 那行注释掉,改成 copytruncate。
sudo nano /etc/logrotate.d/nginx
改成:
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
copytruncate
}
保存,强制切割:
sudo logrotate -f /etc/logrotate.d/nginx
然后访问博客,观察日志:
sudo tail -f /var/log/nginx/access.log
刷新博客,终端没有新日志。
“啥?”小林懵了,“日志不写了?”
他检查 access.log.1:
sudo tail -f /var/log/nginx/access.log.1
刷新博客,日志出现在这里。
“Nginx 还在写 access.log.1。”小林说,“copytruncate 没生效?”
他喊来老周。
老周看了一眼配置:“你 copytruncate 写了,但你有没有注意到,copytruncate 和 create 是冲突的?”
“啥意思?”
“copytruncate 是‘复制日志文件,然后清空原文件’。它不创建新文件,而是复制一份,再把原文件截断。所以你不能同时写 create。”老周说,“你配置里没写 create,但问题是——copytruncate 有窗口期。 复制和截断之间,有几毫秒的延迟。如果 Nginx 在这几毫秒内写了日志,就会丢。”
“那我刚才为什么 Nginx 还在写 access.log.1?”
“因为你用 copytruncate 时,logrotate 复制了 access.log 到 access.log.1,然后清空 access.log。但 Nginx 的文件句柄还指向原来的 access.log,它不知道文件被清空了。所以它继续写 access.log,不是 access.log.1。”
“但我刚才看到日志在 access.log.1 里啊?”
“那是因为你没等。”老周说,“你 tail -f 的时候,access.log.1 是复制出来的副本,里面是旧日志。Nginx 还在写 access.log,但 access.log 被清空了。你刷新博客,新日志应该出现在 access.log 里。”
小林再试了一次:
sudo tail -f /var/log/nginx/access.log
刷新博客,日志出现了。
“回来了。”小林说。
“所以 copytruncate 的问题不是‘不写’,而是‘可能丢日志’。”老周说,“copytruncate 适合不支持重新打开日志的程序。Nginx 支持,所以用 postrotate 通知它,更可靠。”
小林改回原来的配置:
postrotate
systemctl reload nginx
endscript
再次强制切割:
sudo logrotate -f /etc/logrotate.d/nginx
这次 Nginx 重新打开了新日志,access.log 正常写入。
排错思路总结:
copytruncate和create冲突,不能同时用。copytruncate有窗口期,可能丢日志。copytruncate不通知程序重新打开日志,程序继续写原文件。- Nginx 支持
postrotate通知,优先用postrotate。 - 只有程序不支持重新打开日志时,才用
copytruncate。 - 验证:
tail -f access.log,刷新页面,看新日志写在哪。
“记住,”老周说,“copytruncate 是备选方案,不是首选。 能用 postrotate 就用 postrotate。你以后遇到日志切割问题,先看程序支持不支持重新打开日志。”
七、加一条告警:监控日志增长速度
“日志切割配好了,但你还可以加一条告警。”老周说。
“监控什么?”
“日志增长速度。”老周说,“如果日志每秒增长超过 10M,持续 1 分钟,就告警。这样你能在日志撑爆磁盘之前发现。”
“怎么写?”
“PromQL 里,日志大小是 node_filesystem_avail_bytes 的变化率。你算 1 分钟内可用空间的下降速率。”
rate(node_filesystem_avail_bytes{mountpoint="/"}[1m]) < -10 * 1024 * 1024
“rate 是每秒变化率,-10 * 1024 * 1024 是每秒减少 10M。负数表示下降。”
“那告警规则呢?”
- alert: DiskFillingFast
expr: rate(node_filesystem_avail_bytes{mountpoint="/"}[1m]) < -10 * 1024 * 1024
for: 1m
labels:
severity: warning
annotations:
summary: "根分区磁盘快速下降"
description: "根分区可用空间每秒下降超过 10M,持续 1 分钟,可能日志暴涨。"
小林把规则加到 node_rules.yml,检查,重启 Prometheus。
“这条告警比‘磁盘使用率 85%’更早。”老周说,“85% 是‘快满了’,这条是‘正在快速填满’。两条一起用,你既能知道‘快满了’,也能知道‘正在快速涨’。”
小林记下:
- 磁盘使用率告警:
> 0.85,告诉你快满了 - 磁盘下降速率告警:
< -10M/s,告诉你正在快速填满 - 两条配合,提前发现问题
八、练习
- 练习一:用
logrotate -d /etc/logrotate.d/nginx模拟运行,观察输出。然后用logrotate -f强制切割,确认access.log变成 0 字节,旧日志被重命名。 - 练习二:修改
/etc/logrotate.d/nginx,把rotate 14改成rotate 7,daily改成size 100M。用logrotate -d验证配置,然后手动创建一个 200M 的日志文件,强制切割,确认切割生效。 - 练习三:在 Prometheus 中添加
DiskFillingFast告警规则,用promtool check rules检查通过后重启。用dd快速填充磁盘,观察告警从inactive→pending→firing的过程,确认收到邮件。
参考答案
练习一:
sudo logrotate -d /etc/logrotate.d/nginx
sudo logrotate -f /etc/logrotate.d/nginx
sudo ls -lh /var/log/nginx/
练习二:
sudo nano /etc/logrotate.d/nginx
修改:
/var/log/nginx/*.log {
size 100M
missingok
rotate 7
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
systemctl reload nginx
endscript
}
sudo logrotate -d /etc/logrotate.d/nginx
sudo dd if=/dev/zero of=/var/log/nginx/test.log bs=1M count=200
sudo logrotate -f /etc/logrotate.d/nginx
ls -lh /var/log/nginx/
练习三:
sudo nano /etc/prometheus/rules/node_rules.yml
添加:
- alert: DiskFillingFast
expr: rate(node_filesystem_avail_bytes{mountpoint="/"}[1m]) < -10 * 1024 * 1024
for: 1m
labels:
severity: warning
annotations:
summary: "根分区磁盘快速下降"
description: "根分区可用空间每秒下降超过 10M,持续 1 分钟,可能日志暴涨。"
promtool check rules /etc/prometheus/rules/node_rules.yml
sudo systemctl restart prometheus
dd if=/dev/zero of=/tmp/fill.img bs=1M count=10000
观察 Prometheus Alerts 页面。
九、下集预告
logrotate 配好了,日志自动切割了,磁盘告警也不会再因为日志暴涨而频繁触发了。老周说:“监控告警项目做完了,但你有没有发现,你每天还是在手动查日志?”小林点头。老周说:“明天开始,第三个项目:日志分析。你要学会用 grep、awk、sed,从日志里挖出有用的信息。”
下一集:《告警太多了,老周说“你得学会看日志”》