Skip to content

Commit

Permalink
Update "使用 logrotate 滚动 Docker 容器内的 Nginx 的日志"
Browse files Browse the repository at this point in the history
  • Loading branch information
moralok committed Dec 3, 2023
1 parent 8a5c729 commit af27fcf
Showing 1 changed file with 53 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ alternatives apport apt bootlog btmp dpkg rsyslog ubuntu-advantage-tools

强制滚动:`logrotate -f /etc/logrotate.d/nginx`

### 一些坑
## 一些坑

#### /var/lib/logrotate/ 权限问题
### /var/lib/logrotate/ 权限问题

当你使用校验过配置文件的正确性后,尝试强制滚动时,可能会遇到报错。

Expand Down Expand Up @@ -142,7 +142,7 @@ Reading state from file: /var/lib/logrotate/status
...
```

#### 日志文件夹的权限
### 日志文件夹的权限

即使你使用 `root` 身份运行 `logrotate`,你可能还会遇到以下报错

Expand All @@ -153,7 +153,7 @@ error: skipping "/path/to/your/nginx/logs/*.log" because parent directory has in

你需要在配置文件中,使用 `su <user> <group>` 指定日志所在文件夹所属的用户和组,`logrotate` 才能正确读写。

#### 由宿主机还是容器主导
### 由宿主机还是容器主导

首先 `Nginx` 的日志文件夹通过挂载映射到宿主机,日志滚动既可以由宿主机主导,也可以由容器主导,不过不论如何我们都需要向 `Docker` 容器内的 `Nginx` 发送 `USR1` 信号。有人倾向于在容器内完成所有工作,和宿主机几乎完全隔离;我个人更青睐于由宿主机主导,因为容器内的环境并不总是拥有你想要使用的软件(除非你总是定制自己使用的镜像),甚至标准镜像往往非常精简。

Expand All @@ -171,6 +171,53 @@ docker exec nginx sh -c "[ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run
cat: /var/run/nginx.pid: No such file or directory
```

### 继续写入旧日志文件

在使用 `logrotate -f /etc/logrotate.d/nginx` 测试通过后的第二天,发现虽然创建了新的日志文件,但是 `Nginx` 继续写入到旧的日志文件。这不同于网上很多文章提到的“没有发送 `USR1` 信号给 `Nginx` ”的情况。

尝试手动发送信号,观察效果。

```shell
docker exec nginx sh -c "kill -USR1 `docker exec nginx cat /var/run/nginx.pid`"
```

发现虽然终止了继续写入到旧的文件,但是在宿主机读取日志时,提示没有权限。

```console
$ cat access.log
cat: access.log: Permission denied
```

查看日志文件权限,发现不对劲。新建的 `access.log` 的权限为 `600`,所属用户从 `moralok` 变为 `systemd-resolve`,失去了只有所属用户才拥有的 `rw` 权限。

> 此时虽然注意到没有成功创建新的 `error.log`,但是只是以为对于空的日志文件不滚动。并且在后续重现问题时发现此时其实可以在容器里看到日志开始写入新的日志文件。
```console
$ ll
total 136
drwxrwxr-x 2 moralok moralok 4096 Dec 3 00:00 ./
drwxrwxr-x 4 moralok moralok 4096 Nov 30 17:25 ../
-rw------- 1 moralok moralok 0 Dec 3 00:00 access.log
-rw-r--r-- 1 systemd-resolve root 109200 Dec 3 07:01 access.log-20231203
-rw-r--r-- 1 systemd-resolve root 0 Dec 2 19:07 error.log
```

这个时候我的想法是,既然成功创建了新的日志文件,肯定是 `Nginx` 接收到了 `USR1` 信号。怎么会出现“`crontab` 触发时发送信号有问题,手动发送却没问题”的情况呢?难道是两者触发的执行方式有所不同?还是说宿主机创建的文件会有问题?注意到新的日志文件所属的用户和组和原日志文件所属的用户和组不同,我开始怀疑创建文件的过程有问题。在反复测试尝试重现问题后,我把关注点放到了 `create` 配置上。其实在最开始,我就关注了它,我想既然在默认的配置文件中已经设置而且我也不修改,那么就不在 `/etc/logrotate.d/nginx` 添加了。我甚至花了很多时间浏览文档,确认它在缺省后面的权限属性时会使用原日志文件的权限属性。当时我还专门记录了一个疑问,“文档说在运行 `postrotate` 脚本前创建新文件,可是在测试验证时,新文件是 `Nginx` 接收 `USR1` 信号后重新打开文件时创建的,在脚本执行报错或者脚本中并不发送信号时,不会产生新文件”。现在想来,都是坑,坑里注定要灌满眼泪!

`/etc/logrotate.d/nginx` 添加 `create` 后,成功重现问题。

```console
...
renaming /path/to/your/nginx/logs/access.log to /path/to/your/nginx/logs/access.log-20231203
creating new /path/to/your/nginx/logs/access.log mode = 0644 uid = 101 gid = 0
error: error setting owner of /path/to/your/nginx/logs/access.log to uid 101 and gid 0: Operation not permitted
switching euid to 0 and egid to 0
```

可见 `logrotate -f /etc/logrotate.d/nginx`,并没有使用到默认配置 `/etc/logrotate.conf`,而 `crontab` 触发 `logrotate` 时使用到了。修改为 `create 0644 moralok moralok`,成功解决问题。可以确认 `create` 在缺省权限属性的时候,如果日志文件因为挂载到容器中而被修改了所属用户,`logrotate` 按照原文件的权限属性创建新文件时会报错,从而导致脚本不能正常执行,`Nginx` 不会收到信号,`error.log` 也不会继续滚动。按此原因推理,在配置文件中,加入 `nocreate` 也可以解决问题,并且更加符合官方文档建议的流程。

> 枯坐一下午,百思不得其解,想到抓狂。不得不说真的很讨厌这类问题,特定条件下奇怪的问题,食之无味,弃之还不行!如果照着网上的文章,一开始就添加配置真的不会遇到啊。可是不喜欢不明不白地修改配置来解决问题,也不喜欢一次性加很多设置却不知道各个配置的功能,特别是在我的理解里这个默认配置似乎没问题的情况下。明明想要克制住不知重点还不断深入探索细节的坏习惯,却还是被一个 `Bug` 带着花费了大量的时间和精力,解决了一个照着抄就不会遇到的问题。虽然真的有收获,真的解决了前一晚留下的疑问,可是不甘心啊,气气气!而且为什么这样会有问题,我还是不懂!
## logrotate 备忘

[帮助文档](https://linux.die.net/man/8/logrotate)
Expand Down Expand Up @@ -203,6 +250,8 @@ cat: /var/run/nginx.pid: No such file or directory
|nodelaycompress|不延迟压缩,覆盖 delaycompress|
|create mode owner group|滚动后(运行 postrotate 脚本前),立即创建日志文件,指定权限属性|
|nocreate|不创建新的日志文件,覆盖 create|
|copytruncate|创建副本后就地截断原始日志文件,用于一些无法被告知关闭日志文件的程序,可能会丢失复制和截断之间的日志|
|nocopytruncate|不截断始日志文件,覆盖 copytruncate|
|ifempty|即使是空文件也滚动(默认),覆盖 notifempty|
|missingok|如果日志丢失,不报错继续滚动下一个日志|
|notifempty|如果是空文件,不滚动,覆盖 ifempty|
Expand All @@ -212,8 +261,6 @@ cat: /var/run/nginx.pid: No such file or directory
|rotate count|日志文件在删除或邮寄前滚动的次数,0 指没有备份|
|size log-size|当日志文件到达指定的大小时才滚动,默认单位 byte,可以使用 k、M、G|

> 文档说默认在运行 `postrotate` 脚本前创建新文件,可是新文件不是 `Nginx` 重新打开文件时创建的吗?之前脚本执行报错,也没有产生新文件啊。
## 参考文章

- [Rotating Log-files](https://nginx.org/en/docs/control.html#logs)
Expand Down

0 comments on commit af27fcf

Please sign in to comment.