有一次检查磁盘,发现日志占用比业务数据长得还快。目录里既有容器运行时保存的标准输出,也有应用写到挂载目录的文本文件。我最初想给它们配同一条轮转规则,后来发现两者的文件描述符和清理方式并不相同。现在先画出日志流向,再给每一条路径指定唯一的管理者。
先找出日志的去向
应用写到标准输出和标准错误的内容,会被运行时的日志驱动接收;应用主动打开路径写入的日志文件,则是另一条通道。把目录挂载到宿主机,只是让文件可以从外面访问,不会让文件自动获得轮转能力。两个通道都在写同样的详细日志时,还可能白白存两份。
我先查容器的日志驱动、应用配置中的日志路径,以及实际增长的文件。查看打开文件的进程时,同时核对用户、组和权限。不要直接改写运行时内部的日志文件,更不要对它们通配清空:运行时可能仍持有文件描述符,外部处理也可能破坏它对日志位置和状态的判断。
标准输出交给运行时
如果使用 Docker Engine 的 json-file 日志驱动,可以给单个新容器设置大小和文件数量限制。下面配置仅适用于这个驱动,其他驱动应使用各自支持的选项;这里的示意服务名和实际部署目录需要对应起来。
services:
journal-box:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
选项值写成字符串。修改日志选项后,需要按原有部署方式重新创建容器;只重启已有容器,通常不会应用新的日志配置。全局默认值同样只影响之后创建的容器。重新创建之前要确认数据放在持久化位置,随后检查新容器的实际日志设置和持续写入结果。
这是按文件大小控制的近似上限,不表示会精确保留三天。日志增长速度一变,可回看的时间就会变化。如果日志驱动使用 journald,就应检查 journal 自己的空间和保留策略,不再套用这里的文件数量选项。系统 journal 也可能承载其他服务日志,调整时要一起评估。
文件日志才用 logrotate
另一种情况是应用固定写入 /var/log/notebox/app.log。下面假定它在宿主机由名为 notebox.service 的服务管理,以 notebox 用户和组运行,且文档明确说明 HUP 信号会重开日志。若应用不支持这种行为,就不能照搬这个信号。
/var/log/notebox/app.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 notebox notebox
su notebox notebox
sharedscripts
postrotate
/usr/bin/systemctl kill --kill-who=main --signal=HUP notebox.service
endscript
}
轮转一般先改名旧文件,再创建新的路径。应用如果不重开文件,仍然会写进旧的那个文件描述符,表现为新日志文件不增长、旧文件越来越大。delaycompress 会把压缩推迟一个轮次,为仍可能写入旧文件的进程留出时间,但它本身不会让应用重开文件。
这里的用户、组和服务必须真实存在,目录也要允许该用户完成改名和创建。logrotate 以系统任务运行时,脚本的执行身份与文件操作的 su 设置不能混为一谈;我会逐项测试命令的权限。容器内的应用需要通过自己的控制入口重开日志,宿主机同名服务不一定存在。
copytruncate 会先复制再截断原文件,适合部分无法重开文件的旧程序,但复制和截断之间存在竞争窗口,可能损失少量日志。它也会产生额外读写。我只在明确接受这个代价时使用,不把它当成通用修复办法,更不会和上面的重开方案同时堆在一份规则里。
检查轮转是否真的发生
sudo logrotate --debug /etc/logrotate.d/notebox
# 确认配置、权限和信号语义后,才进行一次真实轮转
sudo logrotate --verbose --force /etc/logrotate.d/notebox
ls -lh /var/log/notebox/
debug 模式不会真正轮转,也不会更新状态。force 会真实执行,所以我把它留在低流量的维护窗口。测试后继续产生一条能辨认的日志,确认它落在当前文件里,再检查旧文件是否停止增长。还要核对系统到底由定时器还是其他计划任务调用 logrotate,daily 只是资格条件,不会自己建立调度。
最后我给两条路径分别设了预算:运行时负责标准输出,logrotate 负责应用文件。日常只记录错误摘要,需要排障时临时提高详细程度,结束后及时恢复。比起不断增加保留空间,先减少没有价值的重复内容,更容易让日志真正可读。