第2章:Docker日志驱动与配置

日志驱动这东西,说白了就是Docker把容器里的日志吐到哪去的问题。我刚开始用Docker那会儿,觉得默认的json-file就挺好,直到有一次线上容器把磁盘撑爆了...嗯,从那以后我再也不敢忽视日志驱动和轮转配置了。

2.1 日志驱动概览

Docker支持多种日志驱动,每种都有它的适用场景。我个人习惯把日志驱动分成两类:本地存储型和远程转发型。

驱动名称 类型 适用场景
json-file 本地存储 单机调试、小规模部署
journald 本地存储 与systemd集成紧密的环境
syslog 远程转发 已有syslog基础设施的团队
fluentd 远程转发 云原生日志管道、统一收集
gelf 远程转发 Graylog用户
awslogs 云服务 AWS CloudWatch用户
我的建议:生产环境别只用json-file。你想想看,如果容器挂了,日志文件还在宿主机上,但容器重建后日志就丢了。我一般推荐fluentd或journald做中转。

2.2 json-file驱动详解

这是Docker的默认驱动。容器内进程往stdout/stderr写的内容,都会被Docker捕获并写入宿主机上的JSON文件。每个容器对应一个文件,路径通常是:

/var/lib/docker/containers/<container_id>/<container_id>-json.log

文件内容长这样:

{"log":"2024-01-15 10:30:22 INFO  UserService - 用户登录成功\n","stream":"stdout","time":"2024-01-15T10:30:22.123456789Z"}
{"log":"2024-01-15 10:30:23 ERROR OrderService - 订单超时\n","stream":"stderr","time":"2024-01-15T10:30:23.987654321Z"}

每条日志都是独立的JSON对象。好处是结构清晰,坏处是...文件会无限增长。我在项目中遇到过,一个高并发的Java应用,一天能写20GB日志文件,直接把磁盘打满。

注意:json-file驱动默认不做任何轮转。如果不配置日志轮转,磁盘迟早会爆。我曾经见过一个案例,日志文件撑到100GB后,docker logs命令直接卡死。

2.3 配置日志轮转与大小限制

这才是本章的重点。配置日志轮转有两种方式:全局配置和容器级配置。

2.3.1 全局配置(daemon.json)

修改 /etc/docker/daemon.json,然后重启Docker守护进程:

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

参数说明:

  • max-size:单个日志文件的最大大小。超过这个值就轮转。单位可以是k、m、g。
  • max-file:保留的日志文件个数。轮转后旧文件会被重命名或删除。
  • compress:是否压缩旧日志文件。压缩后是.gz格式,能省不少空间。

举个例子,上面配置的效果是:每个容器最多保留3个日志文件,每个文件不超过10MB,旧文件自动压缩。这样最多占用约30MB磁盘空间。

经验值:我个人习惯把max-size设为50m,max-file设为5。太小了频繁轮转影响性能,太大了又浪费空间。50m×5=250m,对于大多数应用来说够用一周。

2.3.2 容器级配置

启动容器时指定:

docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --log-opt compress=true \
  nginx:latest

或者在docker-compose.yml里配置:

version: '3.8'
services:
  web:
    image: nginx:latest
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        compress: "true"
避坑指南:我曾经在docker-compose里忘了加引号,结果max-size被解析成数字10,单位m没生效。记住,所有log-opt的值都要用字符串。

2.4 journald驱动

journald是systemd的日志系统。如果你用systemd管理服务,journald驱动可以让你用journalctl统一查看所有日志。

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

查看容器日志:

journalctl -u docker.service --since "5 min ago"
journalctl CONTAINER_NAME=nginx

journald的好处是自带日志轮转和压缩,不需要额外配置。但坏处是...日志格式是二进制的,不能直接用cat或tail查看。而且journald默认日志大小限制是系统磁盘的10%,对于日志量大的场景可能不够。

注意:journald的日志轮转由systemd管理,通过/etc/systemd/journald.conf配置。关键参数是SystemMaxUse,我一般设为1G。

2.5 syslog驱动

syslog驱动把日志转发到本机或远程的syslog服务。适合已经有ELK或Graylog等syslog接收端的团队。

docker run -d \
  --log-driver syslog \
  --log-opt syslog-address=tcp://192.168.1.100:514 \
  --log-opt syslog-facility=daemon \
  --log-opt tag="{{.Name}}" \
  nginx:latest

参数说明:

  • syslog-address:syslog服务器地址。支持tcp、udp、unix socket。
  • syslog-facility:syslog设施类型,默认是daemon。
  • tag:日志标签,方便区分不同容器。

syslog的优点是标准化,很多运维工具都支持。但缺点也很明显:UDP传输可能丢日志,TCP传输有连接开销。我一般只在已有syslog基础设施时才用这个驱动。

2.6 fluentd驱动

这是云原生场景下我最推荐的驱动。fluentd驱动把日志直接发给fluentd或fluentbit,然后由它们做进一步处理。

{
  "log-driver": "fluentd",
  "log-opts": {
    "fluentd-address": "localhost:24224",
    "tag": "docker.{{.Name}}",
    "fluentd-async-connect": "true"
  }
}

fluentd驱动的优势:

  • 支持异步连接,不会因为fluentd挂了就影响容器运行
  • 日志格式灵活,可以加自定义字段
  • 与Kubernetes集成良好,很多K8s集群都用这个方案
我的经验:在Kubernetes里,我一般用fluentbit做日志采集,然后转发到Elasticsearch或Kafka。fluentd驱动配合fluentbit,日志处理延迟能控制在秒级。

2.7 日志驱动选择建议

说了这么多,到底该选哪个?我总结了一个简单的决策树:

  1. 单机调试:json-file + 轮转配置,简单够用
  2. systemd环境:journald,统一管理
  3. 已有syslog:syslog驱动,无缝集成
  4. 云原生/K8s:fluentd/fluentbit,灵活强大
  5. AWS用户:awslogs,直接送CloudWatch
最后提醒一句:无论选哪个驱动,一定要配置日志轮转。我见过太多因为日志撑爆磁盘导致服务宕机的案例了。日志很重要,但别让它成为事故的导火索。

下一章我们会聊聊日志采集的架构设计,包括sidecar模式、daemonset模式这些实战内容。到时候见。