背景

最近有个需求,需要分析每天的nginx日志来获取接口的调用次数,但nginx原生日志只会写入一个access.log中,并且不支持像java一样能够通过配置来自动切分日志,在询问chatgpt老师后决定采用logrotate+cron的方式实现。

相关组件介绍

logrotate

Linux 系统中广泛使用的日志轮转工具是 logrotate,它支持按天、按大小自动切割日志,并可设置保留份数。

详细参考:https://blog.csdn.net/weixin_70208651/article/details/146189480

cron

cron 是Linux和Unix系统上的一个定时任务调度工具,用于按照预定的时间表执行命令、脚本和任务。cron 允许您自动化重复性的工作,例如备份、日志清理、系统监控等。

详细参考:https://cloud.tencent.com/developer/article/2522672

具体实现

Dockerfile

FROM nginx:1.26.3
#安装cron logrotate
RUN apt-get update && apt-get install -y cron logrotate && rm -rf /var/lib/apt/lists/*

# 放置 logrotate 配置文件
COPY nginx-logrotate /etc/logrotate.d/nginx

# 放置启动脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# 设置北京时间
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone

#移除cron自带的logrotate任务 防止冲突
RUN mv /etc/cron.daily/logrotate /etc/cron.daily/logrotate.bak
#确保权限
RUN chmod 755 /var/log/nginx

CMD ["/entrypoint.sh"]

由于官方nginx镜像中不包含cron logrotate所以需要先安装组件,并且设置时区,保证执行时间正确。

entrypoint.sh

#!/bin/bash
set -e

echo "[INFO] Starting entrypoint.sh"

# === 1. 设置 logrotate 配置文件权限 ===
LOGROTATE_CONF="/etc/logrotate.d/nginx"
chmod 0644 $LOGROTATE_CONF

# === 2. 写入 cron 定时任务===
cat > /etc/cron.d/nginx-logrotate << 'EOF'
59 23 * * * root /usr/sbin/logrotate -v /etc/logrotate.d/nginx >> /var/log/logrotate.log 2>&1
EOF

chmod 0644 /etc/cron.d/nginx-logrotate

# 创建日志文件
touch /var/log/logrotate.log

# === 3. 启动 cron 服务 ===
service cron start

# === 4. 启动 nginx ===
exec nginx -g "daemon off;"

这个脚本用来配置cron定时任务,59 23 * * * root /usr/sbin/logrotate -v /etc/logrotate.d/nginx是每天23点59分的时候执行logrotate任务,可以根据自己需要进行更改。

nginx-logrotate

/var/log/nginx/*.log {
    daily                  
    missingok              
    rotate 180         
    create 0644 nginx nginx
    dateext               
    dateformat -%Y-%m-%d   
    sharedscripts
    su root root
    postrotate
        [ -f /run/nginx.pid ] && kill -USR1 `cat /run/nginx.pid`
    endscript
}

daily 

代表logrotate执行时会检查上次执行时间,大于等于24小时才会再次执行,否则就算定时任务触发了,logrotate也不会去分割日志。

这里有一个坑点就是当你首次触发logrotate时/var/lib/logrotate/status为空,logrotate会推测并记录Last rotated at为上一个整点,我这里的触发时间为Now: 2025-10-15 11:06所以logrotate认为Last rotated at 2025-10-15 11:00,导致Now-Last rotated at小于24个小时。其实之前并没有轮转过但是还是不会执行。

也就是说如果我今天15号部署的这个docker容器,理论上当天的23:59分会执行第一次,第二天就能看到轮转的日志,但是由于上述的原因,第一次只会记录Last rotated at 2025-10-15 23:00,然后16号23:59再次触发的时候,Now-Last rotated at才会大于等于24小时这时才会真正触发第一次轮转。

如果一定需要当天就轮转可以在容器中先手动触发logrotate -v /etc/logrotate.d/nginx然后再修改/var/lib/logrotate/status里面的时间为昨天24前,这样当天触发的时候就会正常轮转了。

postrotate
        [ -f /run/nginx.pid ] && kill -USR1 `cat /run/nginx.pid`

轮转完成后logrotate会自动执行postrotate中的命令,kill -USR1 `cat /run/nginx.pid`是为了通知nginx重新加载日志文件,不然的话虽然文件名称修改了,但是nginx还是会继续往旧的文件中写入日志。如果不生效可以进入容器看nginx.pid是多少然,后直接写死像kill -USR1 1

生成镜像

将以上三个文件放在一个目录下

最后再docker build -t nginx-logrotate:v1.0 .  构建镜像

最终效果

文件名格式和是否压缩等配置可以参考上面的logrotate文章进行调整。

问题补充

1、logrotate没有按照配置的cron时间执行

root@a7e544acc1bc:/# cat /var/log/logrotate.log
reading config file /etc/logrotate.d/nginx
Creating stub state file: /var/lib/logrotate/status
acquired lock on state file /var/lib/logrotate/statusReading state from file: /var/lib/logrotate/status
Allocating hash table for state file, size 64 entries

Handling 1 logs

rotating pattern: /var/log/nginx/*.log  after 1 days (180 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/nginx/access.log
Creating new state
  Now: 2025-10-14 23:59
  Last rotated at 2025-10-14 23:00
  log does not need rotating (log has already been rotated)
considering log /var/log/nginx/error.log
Creating new state
  Now: 2025-10-14 23:59
  Last rotated at 2025-10-14 23:00
  log does not need rotating (log has already been rotated)
not running postrotate script, since no logs were rotated
reading config file /etc/logrotate.d/nginx
acquired lock on state file /var/lib/logrotate/statusReading state from file: /var/lib/logrotate/status
Allocating hash table for state file, size 64 entries
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state
Creating new state

Handling 1 logs

rotating pattern: /var/log/nginx/*.log  after 1 days (180 rotations)
empty log files are rotated, old logs are removed
considering log /var/log/nginx/access.log
  Now: 2025-10-15 23:59
  Last rotated at 2025-10-15 06:30
  log does not need rotating (log has been rotated at 2025-10-15 06:30, which is less than a day ago)
considering log /var/log/nginx/error.log
  Now: 2025-10-15 23:59
  Last rotated at 2025-10-15 06:30
  log does not need rotating (log has been rotated at 2025-10-15 06:30, which is less than a day ago)
not running postrotate script, since no logs were rotated

cat /var/log/logrotate.log 查看日志发现logrotate每天23:59分都正常触发了,但是两次都并没有执行轮转,最近一次是因为日志已经在2025-10-15 06:30被轮转过了,ls -l命令看06:30的时候确实生成过一次轮转文件,-rw-r--r-- 1 nginx nginx 72651 Oct 15 06:30 error.log-2025-10-15,询问豆包发现cron会有一个默认的定时任务执行logrotate,导致和我们配置的cron定时任务冲突。

解决方法就是在Dockerfile中增加一行

#移除cron自带的logrotate任务 防止冲突
RUN mv /etc/cron.daily/logrotate /etc/cron.daily/logrotate.bak

这样cron自带的logrotate定时任务就不会生效了

2、目录轮转后日志没有写入新生成的access.log中

本地使用logrotate -f /etc/logrotate.d/nginx 强制触发轮转,然后手动curl nginx端口,看日志是正常打印到新生成的access.log日志文件中去的。

但是发到服务器上后发现,日志文件正常定时分割了但还在继续往旧文件中写入。

查看nginx的error日志发现是kill -USR1 `cat /run/nginx.pid`是有正常触发的,但是由于挂载的宿主机目录导致nginx没有/var/log/nginx目录的权限。

需要在Dockerfile中加上chmod命令就ok了

#确保权限
RUN chmod 755 /var/log/nginx

 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐