Docker Compose 如何配置日志轮转防止磁盘占满优化

文章导读
在 Compose 管理的多容器环境里,日志轮转通常不是第一步要做的功能优化,而是磁盘空间被占满之后的补救措施。比较典型的场景是:宿主机磁盘使用率持续上涨,用 docker system df 看镜像和卷都没有明显异常,但 df -h 显示根分区快满了。这时候先别急着扩容,先确认日志文件的大小。
📋 目录
  1. 先判断日志是不是元凶
  2. 在 Compose 服务上配置轮转参数
  3. 全局默认配置的几种实现方式
  4. 验证轮转是否真的生效
  5. 轮转的边界和常见坑
A A

在 Compose 管理的多容器环境里,日志轮转通常不是第一步要做的功能优化,而是磁盘空间被占满之后的补救措施。比较典型的场景是:宿主机磁盘使用率持续上涨,用 docker system df 看镜像和卷都没有明显异常,但 df -h 显示根分区快满了。这时候先别急着扩容,先确认日志文件的大小。

先判断日志是不是元凶

当容器长期运行,Docker 默认的 json-file 日志驱动不会自动分割或清理日志文件。日志写入一个不断增长的 JSON 文件,最终会占满宿主机的磁盘分区,尤其是 Docker 的根目录位于系统盘时,可能导致整个服务不可用。如果发现磁盘使用率持续上涨,且排除镜像和卷占用后仍然异常,可以用 du 命令检查 /var/lib/docker/containers 下的目录大小,确认是容器的 stdout/stderr 日志文件在膨胀。这是配置轮转前首先要做的判断步骤,避免误把日志问题当成其他存储问题处理。

具体执行时,可以先查看 docker ps 拿到容器 ID,然后用 sudo du -sh /var/lib/docker/containers/<容器ID>/ 定位。如果看到类似 *-json.log 的文件很大,基本可以确认是容器日志占用的空间。这里要注意的是,json-file 驱动记录的是容器的 stdout/stderr 输出,不是容器内部应用自己写的文件日志。应用如果把日志写到容器内某个路径,没有输出到标准输出,那这个文件不会出现在这里。

在 Compose 服务上配置轮转参数

确认日志来自 stdout/stderr 之后,修改 docker-compose.yml 里的 logging 配置。最常见的做法是给每个服务单独设置日志选项:

services:
  app:
    image: myapp:latest
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

在 docker-compose.yml 中,可以通过顶层的 logging 字段或针对每个服务配置日志选项。最常见的轮转设置是限制单个日志文件的最大尺寸和保留的文件数量。例如,在服务定义下写 logging: driver: json-file, options: max-size: "10m", max-file: "3"。这样每个容器的日志写满 10MB 后会自动切换,最多保留 3 个文件,超出后就删除最早的日志文件,从而控制总日志量在 30MB 左右。修改配置后需要重新创建容器才能生效,直接 restart 因为复用原有配置可能不会加载新的 logging 设置,这一点需要特别注意。

如果服务的日志输出不大,也可以把 max-size 设成 5m,max-file 设成 2,总日志量控制在 10MB 内。设多大取决于两个因素:一是单个服务平时日志产生的速率,二是宿主机磁盘剩余空间。日志量大的服务,比如频繁打印请求体或调试信息的服务,即使设置 10m 也可能在几小时内轮转多次。此时要把日志量大的服务分离出来,单独给更低的上限,或者直接考虑不落盘。

全局默认配置的几种实现方式

如果服务很多,逐个写 logging 字段会很啰嗦,也容易漏掉新加的服务。Compose 的 YAML 锚点是一种复用方式。

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  app:
    image: myapp:latest
    logging: *default-logging

如果希望所有服务都默认启用轮转,可以在 Compose 文件根级别定义默认的日志配置,但 Compose 语法本身不直接支持全局默认,通常需要借助 YAML 锚点来复用相同的 logging 片段。另一种做法是设置 Docker 守护进程 daemon.json 中的 log-opts 和 log-driver,这样所有容器都会继承默认值,但 Compose 文件中服务的 logging 设置会覆盖守护进程默认值。采用锚点时,可以定义一个 x-logging 基础配置,然后在各服务里引用。

用 daemon.json 的路径一般适用于宿主机上所有 Docker 容器,不管是不是用 Compose 启动的。做法是在 /etc/docker/daemon.json 里写:

Docker Compose 如何配置日志轮转防止磁盘占满优化
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

改完需要重启 Docker 守护进程。注意这个设置只对重启后新建的容器生效,已经存在的容器不会改变。而且 Compose 文件里如果显式写了 logging,会覆盖 daemon.json 的默认值。选择哪种方式,要看你是想统一宿主机上所有容器的策略,还是只想控制某个 Compose 项目。通常建议在 Compose 项目里用锚点,因为这样配置跟着项目走,换机器部署时不会依赖宿主机上的 daemon.json。

验证轮转是否真的生效

配置完成后,可以通过 docker inspect 查看服务容器的 HostConfig.LogConfig,确认 max-size 和 max-file 是否已经出现在容器选项里。然后用 docker logs --tail 或直接查看宿主机上的日志文件数量来验证轮转是否工作。当容器日志写满且超过最大文件数时,旧日志文件会被删除,文件数量会维持在 max-file 的设定值。可以用 ls -lh /var/lib/docker/containers// 查看日志文件列表,如果看到多个以 -json.log 结尾但带有序号的文件,说明轮转已经生效。若只有单个文件持续增大,说明配置可能被覆盖或容器尚未重建。

一个容易忽略的点是:docker inspect 显示的 LogConfig 不一定代表日志驱动正在按这个配置滚动。因为容器可能是在修改配置之前创建的。必须确认容器创建时间晚于配置修改时间,或者直接执行 docker-compose up -d --force-recreate 后,再检查日志文件。检查时注意文件列表里的文件名,默认轮转后的文件会带数字后缀,比如 xxx-json.log.1。如果始终只有一个 -json.log,说明日志量还没达到 max-size,也属于正常。

轮转的边界和常见坑

即使轮转配置正确,它也不是万能的。日志轮转并不等于日志不占磁盘,它只是把日志总量限制在每个容器 max-size × max-file 的范围内。如果同一个宿主机上运行几十个容器,每个容器保留几十兆,加起来仍然可能撑满磁盘。因此,轮转参数需要根据容器的日志产生速率和磁盘余量合理设定,日志量特别大的服务可以考虑将工具接入外部日志采集系统,让日志不落地或仅保留少量本地历史。另外要注意,max-file 和 max-size 只对文件的滚动生效,容器在运行期间如果因阻塞或监听异常导致日志瞬间暴涨,轮转处理可能来不及,磁盘压力依然会短时间内升高。

一个常见的坑是,docker-compose up 更新配置后,如果容器已经存在,Compose 有时会提示配置变化并自动重建,但某些字段如 logging 变化可能不会触发重建,导致用户以为已经更新了配置,实际容器还在用旧日志策略。此时需要显式执行 docker-compose up -d --force-recreate 或者先 down 再 up。另一个坑是设置 max-size 为 100m 但磁盘只有 2GB,而服务又产生大量日志,轮转仍可能来不及。更隐蔽的问题是,某些应用自己写日志文件到容器内卷,而不是通过 stdout/stderr,这种情况下 Docker 的日志驱动根本管不到,需要单独在应用层面或卷目录里配置 logrotate,不要只依赖容器日志轮转。

如果你的服务确实把日志写到了卷里,那需要在宿主机上对卷目录做 logrotate,或者让应用自己处理文件切割。此时再去调整 Docker 的 logging 选项没有意义,因为 json-file 驱动只管标准输出和标准错误。判断方法很简单:先看应用配置里日志输出路径,如果是文件路径,再确认这个文件是否被重定向到 stdout;如果没有,就要另行处理。

最后,轮转参数的调整应该是个渐进过程。可以先给日志量最大的服务设置较紧的上限,观察磁盘使用率是否回到合理范围,再决定是否放宽。不要把 max-size 设得太大,因为 max-file 乘以 max-size 就是每个容器的日志上限,这个上限要小于磁盘剩余空间的一定比例,否则多个容器同时达到上限时照样会有风险。