滚动 docker 中的 nginx 日志

Nginx 自己没有处理日志的滚动问题,它把这个球踢给了使用者。一般情况下,你可以使用 logrotate 工具来完成这个任务,或者如果你愿意,你可以写各式各样的脚本完成同样的任务。本文笔者介绍如何滚动运行在 docker 中的 nginx 日志文件(下图来自互联网)。

滚动 docker 中的 nginx 日志

思路
Nginx 官方其实给出了如何滚动日志的说明:
Rotating Log-files
In order to rotate log files, they need to be renamed first. After that USR1 signal should be sent to the master process. The master process will then re-open all currently open log files and assign them an unprivileged user under which the worker processes are running, as an owner. After successful re-opening, the master process closes all open files and sends the message to worker process to ask them to re-open files. Worker processes also open new files and close old files right away. As a result, old files are almost immediately available for post processing, such as compression.
这段说明的大意是:
先把旧的日志文件重命名
然后给 nginx master 进程发送 USR1 信号
nginx master 进程收到信号后会做一些处理,然后要求工作者进程重新打开日志文件
工作者进程打开新的日志文件并关闭旧的日志文件
其实真正需要我们做的工作只有前面两点!
创建测试环境
假设你的系统中已经安装好了 docker,这里我们直接运行一个 nginx 容器:
$ docker run -d \ -p 80:80 \ -v $(pwd)/logs/nginx:/var/log/nginx \ --restart=always \ --name=mynginx \ nginx:1.11.3
注意,我们把 nginx 的日志绑定挂载到了当前目录下的 logs 目录下。
把下面的内容保存到 test.sh 文件中:

#!/bin/bash for ((i=1;i<=100000;i++)) do curl http://localhost > /dev/null sleep 1 done

然后运行这个脚本,就可以模拟产生连续的日志记录。
创建滚动日志的脚本
创建 rotatelog.sh 文件,其内容如下:

#!/bin/bash getdatestring() { TZ=‘Asia/Chongqing’ date “+%Y%m%d%H%M” } datestring=(getdatestring)mv/var/log/nginx/access.log/var/log/nginx/access.(getdatestring) mv /var/log/nginx/access.log /var/log/nginx/access.{datestring}.log mv /var/log/nginx/error.log /var/log/nginx/error.${datestring}.log kill -USR1 cat /var/run/nginx.pid

getdatestring 函数取当前的时间并格式化为字符串,比如 “201807241310”,笔者比较喜欢用日期和时间来命名文件。注意这里通过 TZ=‘Asia/Chongqing’ 指定了时区,因为默认情况下格式化的是 UTC 时间,用起来怪怪的(要实时脑补 +8 小时)。下面的两条 mv 命令用来重命名日志文件。最后通过 kill 命令向 nginx master 进程发送 USR1 信号。

通过下面的命令为 rotatelog.sh 文件添加可执行权限并复制到 $(pwd)/logs/nginx 目录下:
$ chmod +x rotatelog.sh $ sudo cp rotatelog.sh $(pwd)/logs/nginx
定时执行滚动操作
我们的 nginx 运行在容器中,所以需要在容器中给 nginx master 进程发送 USR1 信号。因此我们需要通过 docker exec 命令在 mynginx 容器中执行 rotatelog.sh 脚本:
$ docker exec mynginx bash /var/log/nginx/rotatelog.sh
执行一次上面的命令,会如期产生一批新的日志文件:

滚动 docker 中的 nginx 日志

下面我们把这个命令配置在定时任务中,让它每天早上 1 点钟执行一次。执行 crontab -e 命令,并在文件的末尾添加下面的行:

  • 1 * * * docker exec mynginx bash /var/log/nginx/rotatelog.sh

滚动 docker 中的 nginx 日志

保存并退出就可以了。下图是笔者测试过程中每 5 分钟滚动一次的效果:

滚动 docker 中的 nginx 日志

为什么不在宿主机中直接 mv 日志文件?
理论上这么做是可以的,因为通过绑定挂载的数据卷中的内容从宿主机上看和从容器中看都是一样的。但是真正这么做的时候你很可能碰到权限问题。在宿主机中,你一般使用的是普通用户,而在容器中产生的日志文件的所有者是会是特殊的用户,并且一般不会给其它用户写和执行的权限:

当然,如果你在宿主机中使用的是 root 用户就不会有问题。
能从宿主机中发送的信号吗?
其实这个问题的全称应该是:能从宿主机中给 docker 容器中的 nginx master 进程发送信号吗?
答案是,可以的。在文章中我们介绍了 docker 向容器中进程发送信号的 kill 命令。我们可以通过命令:
$ docker container kill mynginx -s USR1
向容器中的 1 号进程(nginx master)发送 USR1 信号(这种方式只能向 1 号进程发送信号):
滚动 docker 中的 nginx 日志

结合上面的两个问题,我们可以写出另外的一种方式来滚动 docker 中的 nginx 日志。这种方式不需要通过 docker exec 命令在容器中执行命令,而完全在宿主机中完成所有的操作:
先重命名容器数据卷中的日志文件
给容器中的 1 号进程发送 USR1 信号
总结
相比之下我还是更喜欢第一种方式,它逻辑上清晰,操作上几乎与宿主机完全隔离,也不容易出错。但是通过第二种方式的尝试,我们不但可以找到新的实现方式,还会加深对容器操作的理解。学而不思则罔啊!
欢迎工作一到五年的Java工程师朋友们加入Java架构开发: 854393687
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!