Debian服务器运行一段时间后,/var/log目录膨胀是几乎所有运维都会遇到的问题。很多人等到磁盘告警才手动删几个大文件,这种做法既危险又低效。日志清理的核心不是“删”,而是建立一套可预期、可追溯的轮转与留存机制。Debian系发行版默认已经安装了logrotate,但默认配置远不足以应对生产环境的安全留存要求。下面直接讲具体怎么做。

先搞清楚哪些日志在快速增长

在动手配置之前,先用du和find定位空间占用大头。执行以下命令查看/var/log下各子目录和文件的大小:

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

通常nginx、apache2、mysql、postgresql、journal目录以及kern.log、syslog会是主要占用来源。还要注意一些服务可能把日志写到非标准路径,比如应用目录下的logs文件夹。用find搜索大于100MB的日志文件:

find /var/log -type f -size +100M -exec ls -lh {} \; 2>/dev/null

这一步的目的是建立一份“日志资产清单”。你不需要记在脑子里,但要知道哪些服务在产生日志、增长速度多快、单文件能长到多大。没有这个基础,后面配置轮转策略就是拍脑袋。

logrotate的核心配置逻辑

Debian的logrotate主配置文件是/etc/logrotate.conf,里面通常会包含/etc/logrotate.d目录下的所有配置文件。先看主配置:

cat /etc/logrotate.conf

默认配置一般包含weekly、rotate 4、create、include /etc/logrotate.d这几项。weekly表示每周轮转一次,rotate 4表示保留4个历史文件。对于生产服务器,这个默认值往往不够。比如nginx访问日志在流量大的情况下一天就能产生几个GB,等一周再轮转,磁盘早就撑不住了。

正确的做法是按日志类型分别设置策略。在/etc/logrotate.d/目录下为每个关键服务单独建配置文件。以nginx为例,创建/etc/logrotate.d/nginx-custom:

/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

这里daily表示每天轮转,rotate 14保留14天的压缩历史。compress启用gzip压缩,delaycompress让最近一次轮转的文件延迟到下一次才压缩,这样nginx在轮转后仍能继续写入未压缩的.1文件,避免需要重启服务。missingok和notifempty是安全选项,防止日志文件不存在或为空时报错。postrotate里的kill -USR1是通知nginx重新打开日志文件,这是nginx热重载日志的标准做法。

安全留存的关键:时间与空间的双重控制

很多教程只讲rotate数量,但安全留存还要考虑两个维度:一是留存时间要满足合规或审计要求,二是磁盘空间要有硬性上限。rotate 14只控制文件数量,如果某天日志特别大,14个文件加起来依然可能占满磁盘。更稳妥的做法是同时使用maxsize和maxage参数。

maxsize控制单个日志文件达到多大就强制轮转,不管是否到了daily或weekly周期。例如:

/var/log/nginx/access.log {
    daily
    rotate 30
    maxsize 500M
    maxage 30
    compress
    delaycompress
    missingok
    notifempty
}

maxsize 500M表示只要access.log超过500MB,即使还没到每天轮转的时间点也会立即轮转。maxage 30表示超过30天的日志文件会被删除,即使rotate数量还没达到。这两个参数配合使用,可以确保日志既不会因为单文件过大撑爆磁盘,也不会因为历史文件过多而无限堆积。

对于数据库日志,比如MySQL的慢查询日志和错误日志,建议单独配置。MySQL的日志轮转需要特别注意,不能简单地mv文件,因为MySQL进程持有文件句柄。正确做法是在postrotate中使用mysqladmin flush-logs:

/var/log/mysql/mysql-slow.log /var/log/mysql/mysql-error.log {
    daily
    rotate 14
    maxsize 200M
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        if [ -x /usr/bin/mysqladmin ]; then
            /usr/bin/mysqladmin flush-logs
        fi
    endscript
}

PostgreSQL的日志轮转则更推荐使用数据库自带的log_rotation_age和log_rotation_size参数,而不是用logrotate去切文件。在postgresql.conf中设置:

log_rotation_age = 1d
log_rotation_size = 100MB
log_truncate_on_rotation = on

然后配合logrotate只做压缩和清理,不做轮转动作,避免双重轮转导致日志丢失。

systemd journal日志的专项清理

Debian从9开始默认使用systemd,journal日志存储在/var/log/journal目录下,不受logrotate管理。如果不加限制,journal日志会持续增长。查看当前journal占用:

journalctl --disk-usage

修改/etc/systemd/journald.conf,设置以下参数:

SystemMaxUse=500M
SystemKeepFree=1G
MaxFileSec=1week
MaxRetentionSec=2week

SystemMaxUse限制journal日志总占用不超过500MB,SystemKeepFree确保磁盘至少保留1GB空闲空间,MaxFileSec控制单个journal文件最多保留一周,MaxRetentionSec控制日志最长保留两周。修改后重启systemd-journald服务:

systemctl restart systemd-journald

如果磁盘已经快满了,可以立即清理旧的journal日志:

journalctl --vacuum-size=200M
journalctl --vacuum-time=7d

这两个命令分别按大小和时间清理journal日志,可以写入cron定时任务中作为兜底。

定时任务与自动化兜底

logrotate本身由cron.daily触发,Debian默认在/etc/cron.daily/logrotate中执行。但生产环境建议增加一个自定义的定时检查脚本,处理logrotate覆盖不到的情况。创建/usr/local/bin/log-cleanup.sh:

#!/bin/bash
# 清理超过30天未修改的压缩日志
find /var/log -type f -name "*.gz" -mtime +30 -delete 2>/dev/null

# 清理超过7天的临时日志
find /var/log -type f -name "*.tmp" -mtime +7 -delete 2>/dev/null

# 清理journal日志,保留500MB
journalctl --vacuum-size=500M 2>/dev/null

# 检查磁盘使用率,超过80%时强制清理
USAGE=$(df /var/log | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt 80 ]; then
    find /var/log -type f -name "*.log" -size +1G -exec truncate -s 0 {} \; 2>/dev/null
fi

赋予执行权限并加入crontab:

chmod +x /usr/local/bin/log-cleanup.sh
echo "0 3 * * * /usr/local/bin/log-cleanup.sh" | crontab -

这个脚本每天凌晨3点执行,处理logrotate可能遗漏的压缩文件、临时文件,并在磁盘使用率超过80%时对超过1GB的日志文件做truncate操作。truncate比直接rm更安全,因为正在写入日志的进程不会因为文件被删除而出现写入错误。

安全留存中的审计与不可篡改考量

如果服务器需要满足等保或行业合规要求,日志留存不仅仅是“保留多少天”的问题,还涉及日志的完整性和不可抵赖性。单纯靠logrotate轮转和压缩,无法防止日志被恶意删除或篡改。对于高安全要求的场景,建议将关键日志实时同步到独立的日志服务器或对象存储。

使用rsync配合cron实现日志同步:

rsync -avz --delete /var/log/nginx/ user@log-server:/backup/logs/nginx/

但rsync同步存在时间窗口,攻击者如果在同步前删除日志,就无法恢复。更可靠的方式是使用syslog或rsyslog的远程转发功能,让日志在产生的同时就发送到远端。在/etc/rsyslog.conf中添加:

*.* @192.168.1.100:514

这样所有日志都会实时发送到日志服务器。日志服务器上再配置独立的留存策略和访问控制,实现日志与业务服务器的物理隔离。

对于本地留存的日志文件,可以通过设置文件属性来防止误删。使用chattr命令:

chattr +a /var/log/nginx/access.log

加a属性后,文件只能追加写入,不能删除或修改已有内容。但要注意,logrotate在轮转时需要先移除这个属性,否则轮转会失败。可以在logrotate的prerotate脚本中处理:

prerotate
    chattr -a /var/log/nginx/access.log
endscript

轮转完成后再通过postrotate重新加上a属性。这种机制适合对日志完整性要求极高的场景,但会增加运维复杂度,需要谨慎评估。

验证配置与监控告警

配置完成后,不要直接等生产环境验证。先用logrotate的debug模式测试:

logrotate -d /etc/logrotate.conf

这个命令会模拟执行轮转过程,输出详细的处理信息,但不会真正修改文件。确认无误后再手动强制执行一次:

logrotate -f /etc/logrotate.conf

执行后检查/var/log目录下的文件变化,确认轮转、压缩、删除都符合预期。同时检查相关服务是否正常工作,特别是nginx、mysql这类需要重新打开日志文件的服务。

最后,建议在监控系统中添加磁盘使用率告警,阈值设置在75%左右。日志清理策略再完善,也可能遇到突发流量或异常攻击导致日志暴增。监控告警能让你在磁盘真正写满之前介入处理,而不是等到服务全部宕机才发现问题。

日志管理是一个持续调整的过程。每个季度检查一次日志增长趋势,根据实际情况调整轮转周期、留存数量和大小限制。不要指望一次配置就永远不用管,业务变化、流量增长、新服务上线都会影响日志的产生速度。把日志清理纳入日常运维巡检的一部分,才能让Debian服务器长期稳定运行。