在印度机房用 logrotate 切日志,copytruncate 把 inode 弄丢了

印度孟买机房的采集节点,Ubuntu 20.04,logrotate 3.14,每天凌晨切一次 Nginx 和自研采集器的日志,单文件峰值大约 4.2 GB。现象是切换之后,采集器还往旧文件描述符里写,新文件一直是 0 字节,直到进程重启才恢复;同时 du 看到的磁盘占用比 df 少了将近 3 GB,lsof | grep deleted 里挂着一堆已删但未释放的文件。

原因是配置沿用了老模板,用了 copytruncate。这个选项的语义是先复制一份再清空原文件,但复制和清空之间有一个窗口,这期间写入的日志会丢;更关键的是它没有让进程重新打开文件,采集器持有的还是同一个 inode,只是文件被截断了。我们的采集器是长驻进程,写入偏移已经跑到 4 GB 之外,截断之后偏移不重置,于是新内容全落到了文件末尾的空洞区,前 4 GB 变成稀疏文件,du 和 df 就对不上了。

改成 create 加信号重开,配置片段如下:

/var/log/collector/*.log {
    daily
    rotate 7
    size 2G
    missingok
    notifempty
    compress
    delaycompress
    create 0640 collector collector
    sharedscripts
    postrotate
        /bin/kill -USR1 $(cat /run/collector.pid) 2>/dev/null || true
    endscript
}

采集器侧加了 USR1 处理,收到信号后重新 open() 日志路径,并把写入偏移归零。这里有个取舍:create 依赖进程配合,进程不响应信号就会一直写旧 inode,所以 postrotate 里必须带兜底,我们额外加了一个 cron 每 10 分钟检查 lsof 里 deleted 文件超过 500 MB 就告警。另一个代价是 delaycompress 会多留一天未压缩文件,磁盘水位要按 2 倍峰值预留。

会失效的地方:如果日志路径是软链接,create 会在链接指向的真实路径上建文件,rotate 后的路径可能对不上;容器里跑的话,PID 1 不转发信号,USR1 根本到不了采集器,只能换成 sidecar 或者让进程自己按大小切。我还踩过一次 size 2G 和 daily 同时写,实际是「满足任一条件就切」,结果高峰期一天切了 5 次,rotate 7 只保留了 7 份,把周一的日志挤掉了。后来改成只留 daily,靠监控盯单文件大小。

你们在长驻进程上切日志,是让进程自己按大小滚动,还是靠 logrotate 发信号?遇到不响应信号的第三方二进制,一般怎么兜底?

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。