Docker 日志管理

一、Docker 日志概述

1.1 日志的重要性

维度 说明
故障排查 通过日志定位应用报错、崩溃原因
性能监控 分析访问日志、慢查询、错误率
安全审计 记录操作行为、异常访问、入侵检测
业务分析 用户行为分析、数据统计
合规要求 日志留存满足法律法规要求

1.2 Docker 日志的两层架构

┌─────────────────────────────────────────────────────────────────┐
│  应用层日志                                                     │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  容器内的应用:Nginx / Java / Python / Node.js           │   │
│  │  日志输出到:stdout / stderr(标准输出)                  │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                 │
│                              ▼                                 │
│  Docker 引擎层日志                                              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  日志驱动(Logging Driver)捕获 stdout/stderr            |   │
│  │  并写入到最终目的地(文件 / syslog / 远程服务)            │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

核心原则:容器内的应用应将日志写入 stdout / stderr,而不是写到文件。

二、日志驱动(Logging Driver)

2.1 什么是日志驱动

Docker 日志驱动负责捕获容器的 stdout/stderr,并将其输出到指定目的地。

2.2 常见日志驱动

驱动 说明 适用场景
json-file 默认驱动,将日志写入 JSON 文件 单机、开发环境
journald 写入系统 journald 日志 使用 systemd 的 Linux 系统
syslog 转发到 syslog 服务 传统日志基础设施
fluentd 转发到 Fluentd 日志收集器 统一日志收集(推荐)
loki 转发到 Grafana Loki 云原生日志聚合
awslogs 转发到 AWS CloudWatch AWS 环境
gelf 转发到 Graylog 集中日志管理
splunk 转发到 Splunk 企业级日志分析
none 禁用日志收集 不需要日志的场景

2.3 查看当前日志驱动

# 查看 Docker 守护进程默认驱动
docker info | grep "Logging Driver"

# 查看容器的日志驱动
docker inspect --format='{{.HostConfig.LogConfig.Type}}' container-name

# 查看容器日志驱动详情
docker inspect container-name | grep -A 5 LogConfig

三、json-file 驱动(默认)

3.1 基本用法

# 默认使用 json-file
docker run -d --name web nginx

# 日志文件位置
# /var/lib/docker/containers/<container-id>/<container-id>-json.log

3.2 查看日志

# 查看所有日志
docker logs web

# 查看最近 100 行
docker logs --tail 100 web

# 实时跟踪日志(类似 tail -f)
docker logs -f web

# 查看最近 30 分钟的日志
docker logs --since 30m web

# 查看特定时间范围的日志
docker logs --since "2024-01-01T10:00:00" --until "2024-01-01T12:00:00" web

# 查看并显示时间戳
docker logs -t web

# 组合使用
docker logs -f --tail 100 -t web

3.3 日志轮转配置

方式一:全局配置(/etc/docker/daemon.json)

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",      // 单个日志文件最大 100MB
    "max-file": "5",          // 保留 5 个文件
    "compress": "true"        // 压缩旧日志
  }
}

方式二:容器级别配置

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

方式三:Docker Compose

version: '3.8'
services:
  web:
    image: nginx
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "3"
        compress: "true"

四、其他日志驱动配置

4.1 journald(systemd 日志)

# 容器配置
docker run -d \
  --log-driver journald \
  --log-opt tag="{{.Name}}" \
  --name web \
  nginx

# 查看 journald 日志
journalctl -u docker CONTAINER_NAME=web -f
journalctl CONTAINER_NAME=web --since "1 hour ago"

4.2 fluentd(推荐)

# 容器配置
docker run -d \
  --log-driver fluentd \
  --log-opt fluentd-address=localhost:24224 \
  --log-opt tag="docker.{{.Name}}" \
  --name web \
  nginx

Docker Compose 配置

version: '3.8'
services:
  web:
    image: nginx
    logging:
      driver: fluentd
      options:
        fluentd-address: "fluentd:24224"
        tag: "docker.{{.Name}}"
        fluentd-async: "true"
        fluentd-sub-second-precision: "true"

Fluentd 配置文件示例

# fluentd.conf
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match docker.**>
  @type elasticsearch
  host elasticsearch
  port 9200
  index_name docker-logs
  logstash_format true
</match>

4.3 Loki(Grafana 生态)

# 容器配置
docker run -d \
  --log-driver loki \
  --log-opt loki-url="http://loki:3100/loki/api/v1/push" \
  --log-opt labels="container_name" \
  --name web \
  nginx

4.4 AWS CloudWatch

docker run -d \
  --log-driver awslogs \
  --log-opt awslogs-region=us-east-1 \
  --log-opt awslogs-group=myapp-logs \
  --log-opt awslogs-stream=web \
  --name web \
  nginx

4.5 none(禁用日志)

# 完全不收集日志
docker run -d --log-driver none --name web nginx

五、Docker logs 命令详解

5.1 命令格式

docker logs [OPTIONS] CONTAINER

5.2 常用选项

选项 说明 示例
-f / --follow 实时跟踪日志输出 docker logs -f web
--tail 显示最后 N 行 docker logs --tail 100 web
--since 显示指定时间之后的日志 docker logs --since 2h web
--until 显示指定时间之前的日志 docker logs --until 1h web
-t / --timestamps 显示时间戳 docker logs -t web
--details 显示额外详细信息 docker logs --details web

5.3 时间格式

# 相对时间
docker logs --since 30m web      # 30 分钟前
docker logs --since 2h web       # 2 小时前
docker logs --since 7d web       # 7 天前

# 绝对时间(RFC3339)
docker logs --since "2024-01-01T10:00:00Z" web
docker logs --until "2024-01-01T12:00:00Z" web

# 使用日期字符串
docker logs --since "2024-01-01 10:00" web

5.4 高级用法

# 查看错误日志(grep)
docker logs web 2>&1 | grep ERROR

# 统计错误次数
docker logs web 2>&1 | grep -c ERROR

# 查看最近 10 分钟的异常日志
docker logs --since 10m web 2>&1 | grep -E "ERROR|FATAL|Exception"

# 导出日志到文件
docker logs web > web.log 2>&1

# 查看特定容器的实时日志并过滤
docker logs -f web | grep --line-buffered "Request"

六、容器内应用日志规范

6.1 为什么日志要输出到 stdout/stderr

优点 说明
标准化 所有容器统一方式,无需配置
自动收集 Docker 引擎自动捕获
与驱动无关 切换日志驱动不影响应用
简单 应用无需关心日志存储位置

6.2 各语言输出日志到 stdout

Java(SpringBoot)

# application.yml
logging:
  file:
    name: /dev/stdout   # 或 /dev/console
  level:
    root: INFO
<!-- logback-spring.xml -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>
<root level="INFO">
    <appender-ref ref="STDOUT" />
</root>

Python

import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    stream=sys.stdout  # 输出到 stdout
)

Node.js

// 直接使用 console.log 默认输出到 stdout
console.log('Info message');
console.error('Error message');  // 输出到 stderr

// 使用 winston
const winston = require('winston');
const logger = winston.createLogger({
    transports: [
        new winston.transports.Console()
    ]
});

Nginx

# nginx.conf
access_log /dev/stdout;
error_log /dev/stderr;

6.3 结构化日志(推荐)

结构化日志便于日志收集系统解析和查询:

// JSON 格式日志(推荐)
{"time":"2024-01-01T10:00:00Z","level":"INFO","message":"User logged in","user_id":123,"ip":"192.168.1.1"}
{"time":"2024-01-01T10:00:01Z","level":"ERROR","message":"Database connection failed","error":"timeout","retry":3}

七、日志轮转与清理

7.1 日志文件位置与清理

# Docker 日志文件位置
/var/lib/docker/containers/<container-id>/<container-id>-json.log

# 查看日志文件大小
du -sh /var/lib/docker/containers/*/*.log

# 查看日志文件数量
ls -la /var/lib/docker/containers/*/*.log | wc -l

7.2 手动清理日志

# 清空单个容器日志
truncate -s 0 /var/lib/docker/containers/<container-id>/<container-id>-json.log

# 清空所有容器日志
sudo sh -c 'find /var/lib/docker/containers -name "*-json.log" -exec truncate -s 0 {} \;'

# 使用 docker system prune 清理
docker system prune -a --volumes

7.3 日志大小限制全局配置

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5",
    "compress": "true"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.size=20G"  // 限制总存储大小
  ]
}

7.4 日志清理脚本

#!/bin/bash
# clean-docker-logs.sh

echo "开始清理 Docker 日志..."

# 获取所有容器日志文件
LOG_FILES=$(find /var/lib/docker/containers -name "*-json.log")

for file in $LOG_FILES; do
    size=$(du -h $file | cut -f1)
    echo "清理: $file ($size)"
    truncate -s 0 $file
done

echo "日志清理完成。"

# 清理 Docker 系统资源
docker system prune -f

echo "Docker 系统资源清理完成。"

八、日志集中收集架构

8.1 ELK 架构

应用容器 → stdout/stderr
       ↓
Docker json-file 驱动
       ↓
Filebeat(日志采集器)
       ↓
Elasticsearch(存储 + 索引)
       ↓
Kibana(可视化查询)

docker-compose.yml

version: '3.8'
services:
  filebeat:
    image: elastic/filebeat:8.10
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
    depends_on:
      - elasticsearch

  elasticsearch:
    image: elasticsearch:8.10
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data

  kibana:
    image: kibana:8.10
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

volumes:
  es-data:

filebeat.yml

filebeat.inputs:
- type: container
  paths:
    - /var/lib/docker/containers/*/*.log
  processors:
    - add_docker_metadata: ~

output.elasticsearch:
  hosts: ['elasticsearch:9200']
  index: "docker-logs-%{+yyyy.MM.dd}"

setup.kibana:
  host: "kibana:5601"

8.2 Loki + Grafana 架构

version: '3.8'
services:
  promtail:
    image: grafana/promtail:2.9
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./promtail.yml:/etc/promtail/promtail.yml:ro

  loki:
    image: grafana/loki:2.9
    volumes:
      - loki-data:/loki

  grafana:
    image: grafana/grafana:10.2
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

volumes:
  loki-data:

8.3 Fluentd 采集架构

version: '3.8'
services:
  fluentd:
    image: fluent/fluentd-kubernetes-daemonset:v1-debian-elasticsearch
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./fluentd.conf:/fluentd/etc/fluentd.conf:ro
    ports:
      - "24224:24224"

  elasticsearch:
    image: elasticsearch:8.10
    environment:
      - discovery.type=single-node

  kibana:
    image: kibana:8.10
    ports:
      - "5601:5601"

九、日志分析与排障

9.1 常见日志分析场景

# 1. 查看容器启动失败原因
docker logs <container-id>

# 2. 实时监控错误
docker logs -f <container> 2>&1 | grep -i error

# 3. 分析访问量
docker logs web 2>&1 | grep -c "GET /api"

# 4. 查看特定时间段的错误
docker logs --since "1h" web 2>&1 | grep -c "ERROR"

# 5. 查看慢请求
docker logs web 2>&1 | grep -E "duration=[0-9]{4,}ms"

# 6. 统计错误类型
docker logs web 2>&1 | grep "ERROR" | cut -d' ' -f4 | sort | uniq -c | sort -nr

9.2 日志级别最佳实践

级别 适用场景 示例
ERROR 系统错误、需要立即关注 数据库连接失败
WARN 潜在问题、降级处理 接口超时、重试
INFO 关键业务操作 用户登录、订单创建
DEBUG 开发调试信息 参数值、中间状态
TRACE 详细追踪信息 完整调用链

十、常见问题与解决

Q1:docker logs 命令卡住或很慢

原因:日志文件过大,读取耗时。

解决

# 限制输出行数
docker logs --tail 100 web

# 只查最近的
docker logs --since 10m web

Q2:容器日志占满磁盘空间

解决

# 1. 配置日志轮转
# 在 daemon.json 中配置 max-size 和 max-file

# 2. 清理日志
truncate -s 0 /var/lib/docker/containers/*/*-json.log

# 3. 使用 logrotate
# /etc/logrotate.d/docker
/var/lib/docker/containers/*/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    copytruncate
}

Q3:容器日志中中文乱码

原因:字符编码不一致。

解决

# 设置容器字符编码
docker run -d \
  -e LANG=C.UTF-8 \
  -e LC_ALL=C.UTF-8 \
  --name app \
  myimage

Q4:日志时间不是本地时间

解决

# 容器内设置时区
docker run -d \
  -e TZ=Asia/Shanghai \
  -v /etc/localtime:/etc/localtime:ro \
  --name app \
  myimage

Q5:如何查看已停止容器的日志

# 加上 -a 查看所有容器
docker logs -a <container-id>

# 或使用
docker logs <container-id> 2>&1

十一、日志管理最佳实践

实践 说明
应用输出到 stdout/stderr 不要写文件到容器内
配置日志轮转 设置 max-sizemax-file,防止磁盘占满
使用结构化日志 JSON 格式,便于系统解析和查询
集中日志收集 生产环境使用 ELK/Loki 等工具
设置日志级别 生产环境用 INFO/WARN,避免 DEBUG 过度输出
监控日志大小 设置告警,防止日志堆积
定期清理 配置自动化清理策略

十二、命令速查表

操作 命令
查看容器日志 docker logs <容器>
实时跟踪日志 docker logs -f <容器>
查看最近 N 行 docker logs --tail 100 <容器>
查看最近时间日志 docker logs --since 30m <容器>
查看带时间戳日志 docker logs -t <容器>
查看容器日志驱动 docker inspect -f '{{.HostConfig.LogConfig.Type}}' <容器>
查看 Docker 默认日志驱动 docker info \| grep "Logging Driver"
清空容器日志 truncate -s 0 /var/lib/docker/containers/*/*-json.log
清理 Docker 资源 docker system prune -a

十三、总结

Docker 日志管理三原则

应用输出到 stdout/stderr(标准化)

配置日志轮转(防磁盘占满)

集中日志收集(生产必做,ELK/Loki)

默认 json-file 驱动适合开发测试,生产环境推荐 fluentdloki 驱动对接统一日志平台。