用 logrotate copytruncate 解决日志轮转后磁盘不释放
问题场景
应用进程启动后一直持有日志文件描述符,并且没有提供重新打开日志的接口。默认的 logrotate 配置通常使用 rename 策略:把 app.log 改名为 app.log.1,再创建新的 app.log。此时进程仍向旧 inode 追加写入,磁盘空间不会因为改名而释放;新文件可能一直是空的,旧文件却持续增长。用 ls -i 可以看到当前 app.log 的 inode 已经变化,而 lsof 仍显示进程持有旧 inode。
解决思路是让 logrotate 不换文件,而是复制一份归档后把原文件截断。这样进程继续写同一个 inode,文件大小归零,空间被回收。
最小配置
假设应用日志为 /opt/demo/logs/app.log,运行用户为 demo。新建 /etc/logrotate.d/demo-app:
/opt/demo/logs/app.log {
daily
rotate 7
copytruncate
missingok
notifempty
compress
delaycompress
su demo demo
}
关键项:daily 每天轮转;rotate 7 保留 7 份;copytruncate 先复制再截断原文件;su 指定读写日志的用户和组(按实际用户替换)。create 在 copytruncate 下不起作用,原文件保持原 inode。
可运行的验证示例
在测试机用临时目录复现,不依赖应用改造。创建持续持有文件描述符的写入脚本:
mkdir -p /tmp/logrotate-demo
cat > /tmp/logrotate-demo/writer.sh <<'EOF'
#!/usr/bin/env bash
set -eu
log=/tmp/logrotate-demo/app.log
exec 3>>$log
while true; do
echo "$(date -Is) hello copytruncate" >&3
sleep 1
done
EOF
chmod +x /tmp/logrotate-demo/writer.sh
/tmp/logrotate-demo/writer.sh &
echo $! > /tmp/logrotate-demo/writer.pid
sleep 3
创建测试配置:
/tmp/logrotate-demo/app.log {
daily
rotate 7
copytruncate
missingok
notifempty
}
先做 dry-run,确认 logrotate 能解析配置并显示 copy/truncate 动作:
logrotate -d -s /tmp/logrotate-demo/state /tmp/logrotate-demo/logrotate.conf
再用 -f 强制执行一次轮转,便于立即验证:
logrotate -f -s /tmp/logrotate-demo/state /tmp/logrotate-demo/logrotate.conf
ls -i /tmp/logrotate-demo/app.log /tmp/logrotate-demo/app.log.1
lsof -p $(cat /tmp/logrotate-demo/writer.pid) | grep app.log
du -h /tmp/logrotate-demo/app.log /tmp/logrotate-demo/app.log.1
预期现象:app.log 的 inode 在轮转前后保持不变;app.log.1 是复制出来的归档文件,inode 不同;lsof 中写入进程的 fd 仍指向 app.log 的同一个 inode;app.log 大小被截断,后续写入从零开始增长。若换成默认 rename 策略,lsof 会显示进程仍占用已改名的旧 inode,这正是空间不释放的原因。
边界与限制
copytruncate 不是原子操作。复制完成到截断之间有一个很短的时间窗口,期间写入的少量日志可能丢失;对审计日志或不能丢行的场景,应优先让应用支持 reopen 信号,再使用 postrotate 触发重开。大文件复制会带来额外 I/O 和短暂磁盘占用,轮转时间应避开业务高峰。若日志目录权限或 su 用户配置不正确,logrotate 可能跳过该文件,需结合 logrotate -d 输出排查。rotate 7 只控制保留份数,不会自动清理配置之外的旧文件;压缩归档还会增加 CPU 开销。生产环境建议先用 dry-run 验证配置,再观察 ls -i、lsof 和目录占用情况。
- 点赞
- 收藏
- 关注作者
评论(0)