广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何正确配置 JSON 日志驱动并防止 Docker 磁盘被写满?

Docker 默认使用 json-file 日志驱动,也就是说,容器内进程写到 stdout 和 stderr 的内容会被 Docker 采集后,以 JSON 行格式保存在宿主机 /var/lib/docker/containers/ 目录下对应的容器目录中。这个设计让 docker logs 命令可以快速读取日志,但也带来了一个常见问题:如果容器持续输出日志,且没有配置轮转,日志文件会越来越大,直到耗尽磁盘空间。

如何正确配置 JSON 日志驱动并防止 Docker 磁盘被写满?

一、JSON 日志驱动的工作机制与默认行为

json-file 驱动的核心逻辑并不复杂:Docker 为每个容器创建一个单独的日志文件,通常命名为 容器ID-json.log,文件内容每一行都是一条独立的 JSON 对象。每条记录包含 log、stream、time 三个主要字段,分别表示日志文本、输出流类型(stdout 或 stderr)以及时间戳。以下是一条典型的日志记录格式:

{"log":"2025-01-01 10:00:00 Starting nginx\n","stream":"stdout","time":"2025-01-01T02:00:00.000000000Z"}

需要注意的是,log 字段末尾常常带有换行符,stream 字段用于区分标准输出和标准错误,time 字段采用 RFC3339Nano 格式。了解这些字段对后续使用日志采集工具解析非常有帮助,例如 Filebeat 或 Fluentd 可以按照 JSON 结构直接提取日志内容,而不需要额外写正则表达式。

默认情况下,Docker 不会对 json-file 日志文件做任何大小限制。查看容器日志路径可以使用 docker inspect 命令:

docker inspect --format='{{.LogPath}}' 容器名或ID

这句命令会返回类似 /var/lib/docker/containers/abc123/abc123-json.log 的路径。进入该目录可以看到以 -json.log 结尾的文件。如果没有人工干预,该文件会随着容器运行时间线性增长。很多生产事故就是由于这个文件写满根分区或 /var 分区,导致 Docker 无法继续写入,甚至影响宿主机其他服务。

为了避免这类问题,必须在创建容器或配置 Docker 守护进程时就设置好日志轮转参数。已经运行中的容器默认参数不会因为宿主机配置变化而自动更新,这一点在第三节会进一步说明。

二、配置 JSON 日志轮转的两种方式

Docker 提供全局配置和单容器配置两种方式启用 json-file 驱动的轮转。全局配置通过修改 /etc/docker/daemon.json 实现,适用于宿主机上所有新创建的容器。核心参数有两个:max-size 控制单个日志文件的最大体积,max-file 控制最多保留几个轮转文件。一个推荐的基础配置如下:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

保存文件后需要重启 Docker 服务使配置生效:

systemctl restart docker

配置完成后,宿主机上新创建的容器都会自动应用这一组日志策略。这里的 max-size 支持 k、m、g 等单位,分别代表 KB、MB、GB。max-file 必须为正整数,当文件数量超过该值时,最旧的日志文件会被删除。以 10m 和 3 为例,单个容器最多占用约 30MB 日志空间。

如果不想修改全局配置,也可以在 docker run 命令中单独指定。比如启动一个 nginx 容器,只对当前容器启用日志轮转:

docker run -d --name web \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  nginx

这种方式更灵活,适合临时调试、短期任务或不同容器有不同日志量级的场景。在 Docker Compose 中同样可以配置,只需要在服务的 logging 字段下声明 driver 和 options。例如:

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

无论使用哪种方式,最终写入容器的 HostConfig.LogConfig 类型都是一致的。建议团队内部统一使用全局配置,再针对极少数日志量特别大的服务在 Compose 中覆盖参数,这样既能保证默认安全,也保留灵活性。

三、生产环境中的日志轮转策略与常见坑

轮转参数看起来简单,但真正上线时还有一些容易忽略的细节。首先是容量估算。假设一台宿主机运行 20 个容器,每个容器配置 max-size 为 10m、max-file 为 3,那么理论最大日志占用为 20 乘 10MB 乘 3,等于 600MB。这看似不大,但如果某些容器日志增长极快,轮转频率会非常高,磁盘写入压力也会上升。实际规划时建议结合业务日志量,把 max-size 调大一些,例如 50m,并降低 max-file 到 2,减少频繁轮转带来的性能损耗。

另一个常见问题是修改 daemon.json 后,已经运行的容器不会自动套用新策略。Docker 只在创建容器时把日志配置写入容器的 HostConfig,因此必须执行 docker rm 删除旧容器并重新 docker run 创建,或者通过更新编排文件触发容器重建。排查时可以用 docker inspect 检查某个容器的 LogConfig:

docker inspect --format='{{.HostConfig.LogConfig.Type}} {{.HostConfig.LogConfig.Config}}' 容器名

输出中如果 max-size 为 -1 或 max-file 为 1,说明该容器仍在使用默认值,需要重建。

还有一点要注意,当日志文件达到 max-size 后,Docker 会关闭当前文件并创建新的 -json.log.1 等轮转文件,docker logs 命令在多数版本中可以读取当前和已轮转的文件,但若日志轮转速度过快,部分日志采集器可能来不及读取就被删除。对于必须保证日志完整性的业务,建议将 max-file 设置得足够大,或改用 journald 驱动由系统日志服务统一管理。

最后,日志文件默认存放在 /var/lib/docker/ 下,如果根分区空间紧张,可以考虑把 Docker 数据目录迁移到独立磁盘,或者使用 log-opts 中的 labels 和 env 等辅助参数配合采集工具分流。但这两个参数更多用于标记,不直接限制日志大小,不能替代轮转配置。

四、与其他日志驱动对比及选择建议

json-file 并不是 Docker 唯一可用的日志驱动,官方还支持 journald、syslog、local、none 等。选择哪种驱动主要取决于日志的消费方式。如果只是单机调试,需要频繁使用 docker logs 查看日志,json-file 是最直观的选择。如果宿主机使用 systemd 管理,并且已经部署了 journald 采集体系,那么把容器日志接入 journald 可以复用系统级日志轮转和权限控制。切换方式同样是在 daemon.json 中声明:

{
  "log-driver": "journald",
  "log-opts": {
    "tag": "{{.Name}}"
  }
}

journald 驱动的优势在于所有容器和系统服务日志进入统一管道,可以使用 journalctl 按容器名、优先级等条件过滤。但代价是无法再使用 docker logs 直接查看日志,必须通过 journalctl 访问。syslog 驱动适合已有集中式 syslog 服务器的环境,local 驱动则会保留最近几行日志以节省空间,none 直接关闭日志采集,一般用于高安全或日志由应用自己输出的场景。

可以用一个简单表格对比四类驱动:

驱动类型是否支持 docker logs日志存储位置典型适用场景
json-file支持宿主机 /var/lib/docker/containers/单机调试、文件采集
journald不支持systemd journalsystemd 体系、统一系统日志
syslog不支持远程或本地 syslog 服务已有集中日志服务器
none不支持不保存应用自管日志或安全要求

总体来看,如果团队当前没有集中日志平台,最稳妥的做法仍然是使用 json-file 驱动,并严格配置 max-size 和 max-file。等日志规模变大,再逐步引入 Filebeat、Fluent Bit 或 Loki 等采集工具,把 JSON 日志文件中的内容结构化后发送到中心端。这样既保留了 docker logs 的便利性,又能有效控制磁盘占用,避免小配置引发大故障。

广告位 · 文章底部(后台-模块管理替换此区域)