滚动 docker 中的 nginx 日志
<p><span style="font-family: Microsoft YaHei; font-size: 15px">Nginx 自己没有处理日志的滚动问题,它把这个球踢给了使用者。一般情况下,你可以使用 logrotate 工具来完成这个任务,或者如果你愿意,你可以写各式各样的脚本完成同样的任务。本文笔者介绍如何滚动运行在 docker 中的 nginx 日志文件(下图来自互联网)。</span></p><p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151015024-952153251.png"></span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">思路</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">Nginx 官方其实给出了如何滚动日志的说明:</span><br><strong><span style="font-family: Microsoft YaHei; font-size: 13px">Rotating Log-files</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 13px">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.</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">这段说明的大意是:</span></p>
<ul>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">先把旧的日志文件重命名</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">然后给 nginx master 进程发送 USR1 信号</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">nginx master 进程收到信号后会做一些处理,然后要求工作者进程重新打开日志文件</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">工作者进程打开新的日志文件并关闭旧的日志文件</span></li>
</ul>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">其实真正需要我们做的工作只有前面两点!</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">创建测试环境</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">假设你的系统中已经安装好了 docker,这里我们直接运行一个 nginx 容器:</span></p>
<div class="cnblogs_code">
<pre>$ docker run -<span style="color: rgba(0, 0, 0, 1)">d \
</span>-p <span style="color: rgba(128, 0, 128, 1)">80</span>:<span style="color: rgba(128, 0, 128, 1)">80</span><span style="color: rgba(0, 0, 0, 1)"> \
</span>-v $(<span style="color: rgba(0, 0, 255, 1)">pwd</span>)/logs/nginx:/var/log/<span style="color: rgba(0, 0, 0, 1)">nginx \
</span>--restart=<span style="color: rgba(0, 0, 0, 1)">always \
</span>--name=<span style="color: rgba(0, 0, 0, 1)">mynginx \
nginx:</span><span style="color: rgba(128, 0, 128, 1)">1.11</span>.<span style="color: rgba(128, 0, 128, 1)">3</span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">注意,我们把 nginx 的日志绑定挂载到了当前目录下的 logs 目录下。</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">把下面的内容保存到 test.sh 文件中:</span></p>
<div class="cnblogs_code">
<pre>#!/bin/<span style="color: rgba(0, 0, 0, 1)">bash
</span><span style="color: rgba(0, 0, 255, 1)">for</span> ((i=<span style="color: rgba(128, 0, 128, 1)">1</span>;i<=<span style="color: rgba(128, 0, 128, 1)">100000</span>;i++<span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">do</span><span style="color: rgba(0, 0, 0, 1)">
curl http:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">localhost > /dev/null</span>
<span style="color: rgba(0, 0, 255, 1)">sleep</span> <span style="color: rgba(128, 0, 128, 1)">1</span>
<span style="color: rgba(0, 0, 255, 1)">done</span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">然后运行这个脚本,就可以模拟产生连续的日志记录。</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">创建滚动日志的脚本</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">创建 rotatelog.sh 文件,其内容如下:</span></p>
<div class="cnblogs_code">
<pre>#!/bin/<span style="color: rgba(0, 0, 0, 1)">bash
getdatestring()
{
TZ</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Asia/Chongqing</span><span style="color: rgba(128, 0, 0, 1)">'</span> <span style="color: rgba(0, 0, 255, 1)">date</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">+%Y%m%d%H%M</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
datestring</span>=<span style="color: rgba(0, 0, 0, 1)">$(getdatestring)
</span><span style="color: rgba(0, 0, 255, 1)">mv</span> /var/log/nginx/access.log /var/log/nginx/<span style="color: rgba(0, 0, 0, 1)">access.${datestring}.log
</span><span style="color: rgba(0, 0, 255, 1)">mv</span> /var/log/nginx/error.log /var/log/nginx/<span style="color: rgba(0, 0, 0, 1)">error.${datestring}.log
</span><span style="color: rgba(0, 0, 255, 1)">kill</span> -USR1 `<span style="color: rgba(0, 0, 255, 1)">cat</span> /var/run/nginx.pid`</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">getdatestring 函数取当前的时间并格式化为字符串,比如 "201807241310",笔者比较喜欢用日期和时间来命名文件。注意这里通过 TZ='Asia/Chongqing' 指定了时区,因为默认情况下格式化的是 UTC 时间,用起来怪怪的(要实时脑补 +8 小时)。</span><span style="font-family: Microsoft YaHei; font-size: 15px">下面的两条 mv 命令用来重命名日志文件。</span><span style="font-family: Microsoft YaHei; font-size: 15px">最后通过 kill 命令向 nginx master 进程发送 USR1 信号。</span><br><br><span style="font-family: Microsoft YaHei; font-size: 15px">通过下面的命令为 rotatelog.sh 文件添加可执行权限并复制到 $(pwd)/logs/nginx 目录下:</span></p>
<div class="cnblogs_code">
<pre>$ <span style="color: rgba(0, 0, 255, 1)">chmod</span> +x rotatelog.<span style="color: rgba(0, 0, 255, 1)">sh</span><span style="color: rgba(0, 0, 0, 1)">
$ </span><span style="color: rgba(0, 0, 255, 1)">sudo</span> <span style="color: rgba(0, 0, 255, 1)">cp</span> rotatelog.<span style="color: rgba(0, 0, 255, 1)">sh</span> $(<span style="color: rgba(0, 0, 255, 1)">pwd</span>)/logs/nginx</pre>
</div>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">定时执行滚动操作</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">我们的 nginx 运行在容器中,所以需要在容器中给 nginx master 进程发送 USR1 信号。因此我们需要通过 docker exec 命令在 mynginx 容器中执行 rotatelog.sh 脚本:</span></p>
<div class="cnblogs_code">
<pre>$ docker exec mynginx bash /var/log/nginx/rotatelog.<span style="color: rgba(0, 0, 255, 1)">sh</span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">执行一次上面的命令,会如期产生一批新的日志文件:</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151229421-342171949.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">下面我们把这个命令配置在定时任务中,让它每天早上 1 点钟执行一次。执行 crontab -e 命令,并在文件的末尾添加下面的行:</span></p>
<div class="cnblogs_code">
<pre>* <span style="color: rgba(128, 0, 128, 1)">1</span> * * * docker exec mynginx bash /var/log/nginx/rotatelog.<span style="color: rgba(0, 0, 255, 1)">sh</span></pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151259774-1652685460.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">保存并退出就可以了。下图是笔者测试过程中每 5 分钟滚动一次的效果:</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151329336-1434362812.png"></span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">为什么不在宿主机中直接 mv 日志文件?</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">理论上这么做是可以的,因为通过绑定挂载的数据卷中的内容从宿主机上看和从容器中看都是一样的。但是真正这么做的时候你很可能碰到权限问题。在宿主机中,你一般使用的是普通用户,而在容器中产生的日志文件的所有者是会是特殊的用户,并且一般不会给其它用户写和执行的权限:</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151408056-1800472630.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">当然,如果你在宿主机中使用的是 root 用户就不会有问题。</span></p>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">能从宿主机中发送的信号吗?</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">其实这个问题的全称应该是:能从宿主机中给 docker 容器中的 nginx master 进程发送信号吗?</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">答案是,可以的。</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">笔者这《在 docker 容器中捕获信号》一文中介绍了容器中信号的捕获问题,感兴趣的朋友可以去看看。在那篇文章中我们介绍了 docker 向容器中进程发送信号的 kill 命令。我们可以通过命令:</span></p>
<div class="cnblogs_code">
<pre>$ docker container <span style="color: rgba(0, 0, 255, 1)">kill</span> mynginx -s USR1</pre>
</div>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">向容器中的 1 号进程(nginx master)发送 USR1 信号(这种方式只能向 1 号进程发送信号):</span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px"><img src="https://images2018.cnblogs.com/blog/952033/201808/952033-20180826151514123-1050265659.png"></span></p>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">结合上面的两个问题,我们可以写出另外的一种方式来滚动 docker 中的 nginx 日志。这种方式不需要通过 docker exec 命令在容器中执行命令,而完全在宿主机中完成所有的操作:</span></p>
<ul>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">先重命名容器数据卷中的日志文件</span></li>
<li><span style="font-family: Microsoft YaHei; font-size: 15px">给容器中的 1 号进程发送 USR1 信号</span></li>
</ul>
<h1><span style="font-family: Microsoft YaHei; font-size: 18pt">总结</span></h1>
<p><span style="font-family: Microsoft YaHei; font-size: 15px">相比之下我还是更喜欢第一种方式,它逻辑上清晰,操作上几乎与宿主机完全隔离,也不容易出错。但是通过第二种方式的尝试,我们不但可以找到新的实现方式,还会加深对容器操作的理解。学而不思则罔啊!</span></p>
<p><strong><span style="font-family: Microsoft YaHei; font-size: 15px">参考:</span></strong><br><span style="font-family: Microsoft YaHei; font-size: 15px">How To Configure Logging and Log Rotation in Nginx on an Ubuntu VPS</span><br><span style="font-family: Microsoft YaHei; font-size: 15px">How To Manage Logfiles with Logrotate on Ubuntu 16.04</span></p>
</div>
<div id="MySignature" role="contentinfo">
<div>作者:sparkdev</div>
<div>出处:http://www.cnblogs.com/sparkdev/</div>
<div>本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。</div><br><br>
来源:https://www.cnblogs.com/sparkdev/p/9537520.html
頁:
[1]