2、Docker日志基础:Docker日志的产生机制、标准输出与标准错误、日志与容器的生命周期关系
在进入日志驱动与采集方案之前,必须先回答一个根本问题:Docker 的日志到底从哪里来? 很多初学者误以为 Docker 日志是 Docker 引擎自己生成的文本文件,实际上,Docker 日志的核心来源是容器内主进程(PID 1)写入标准输出(stdout)和标准错误(stderr)的数据流。理解这一机制,是后续配置 json-file、journald、fluentd 等日志驱动的前提。本章将从产生机制、双通道模型、生命周期关联三个维度展开,帮助学员建立完整的日志基础认知。
上图展示了 Docker 日志产生的完整链路:容器内主进程及其子进程将日志写入 stdout/stderr,Docker 引擎通过日志驱动捕获这两个数据流,最终写入宿主机存储或转发至外部系统。下面我们逐一拆解每个环节。
2.1 核心原理:日志不是文件,而是数据流
Docker 容器默认不直接产生日志文件。应用进程只需将日志输出到标准输出(stdout)和标准错误(stderr),Docker 引擎会在宿主机层面拦截这两个文件描述符(FD 1 和 FD 2)的数据,再根据配置的日志驱动(log driver)进行后续处理。这一设计借鉴了 Unix 哲学——“程序只负责输出,不关心日志去向”。
具体来说,当你在 Dockerfile 中使用 CMD 或 ENTRYPOINT 启动进程时,该进程成为容器内的 PID 1。如果应用本身支持直接写 stdout(例如 Nginx 的 access_log off 配合 error_log /dev/stdout),那么日志就会自然流入 Docker 的日志管道。对于不支持直接写 stdout 的应用(如某些 Java 应用),需要通过 ln -sf /dev/stdout /path/to/log 或使用 tail -F 等方式将日志文件重定向到标准输出。
1. 容器内没有日志文件,Docker 也能收集日志;
2. 如果应用只写文件不写 stdout,Docker 默认收集不到任何日志;
3. 日志驱动的作用是“消费”stdout/stderr 数据流,而不是“读取”容器内文件。
2.2 标准输出与标准错误:双通道模型
Linux 进程默认拥有三个标准文件描述符:stdin(0)、stdout(1)、stderr(2)。Docker 日志体系只关心 stdout 和 stderr,二者在日志采集、展示和存储上存在显著差异。
| 维度 | 标准输出(stdout) | 标准错误(stderr) |
|---|---|---|
| 文件描述符 | 1 | 2 |
| 典型用途 | 业务日志、访问日志、正常输出 | 错误堆栈、异常信息、调试输出 |
| docker logs 显示 | 正常显示 | 正常显示(与 stdout 合并) |
| json-file 存储 | 存于 log 字段 |
存于 log 字段(不区分来源) |
| 日志驱动转发 | 按配置转发 | 按配置转发(部分驱动可区分) |
| 采集优先级 | 高(业务核心) | 高(排障关键) |
在默认的 json-file 驱动下,stdout 和 stderr 的日志会被合并存储,每条日志记录包含 log、stream(stdout 或 stderr)、time 三个字段。例如:
{"log":"192.168.1.1 - - [12/Feb/2025:10:15:30] \"GET /index.html HTTP/1.1\" 200 612\n","stream":"stdout","time":"2025-02-12T10:15:30.123456789Z"}
{"log":"2025/02/12 10:15:31 [error] 7#7: *1 connect() failed (111: Connection refused)\n","stream":"stderr","time":"2025-02-12T10:15:31.987654321Z"}
值得注意的是,docker logs 命令默认将 stdout 和 stderr 混合显示,但可以通过 --stdout 或 --stderr 参数单独查看。在日志采集场景中,建议在日志驱动层面(如 fluentd、splunk)区分 stream 字段,便于后续按错误级别检索。
log 包默认输出到 stderr,而 fmt.Println 输出到 stdout,需要根据场景选择。
2.3 日志与容器的生命周期关系
容器的生命周期直接影响日志的可用性和持久性。理解这一关系,才能设计出合理的日志采集与归档策略。
2.3.1 容器状态与日志可用性
- 运行中(Running):日志持续产生,可通过
docker logs实时查看,也可被日志驱动实时转发。 - 暂停(Paused):进程被冻结,日志停止产生,但已产生的日志仍可查看。
- 停止(Exited):进程终止,日志不再增加,但
docker logs仍可读取已捕获的日志(前提是容器未被删除)。 - 删除(Removed):默认情况下,json-file 驱动存储的日志文件会随容器删除而永久丢失。
这一生命周期特性带来两个重要推论:
- 日志采集必须实时:如果依赖日志驱动转发到外部系统(如 Elasticsearch),必须在容器运行期间完成转发,否则容器删除后数据无法恢复。
- 日志持久化需要额外配置:对于需要长期保留的日志,应通过 volume 挂载宿主机目录,或配置 Docker 的
log-opts参数(如max-size、max-file)来控制日志轮转。
2.3.2 日志轮转与存储限制
默认的 json-file 驱动如果不加限制,会无限增长,最终占满宿主机磁盘。Docker 提供了内置的轮转机制:
# 启动容器时限制日志大小
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:latest
上述配置表示:单个日志文件最大 10MB,最多保留 3 个文件,超出后自动轮转。轮转时,Docker 会关闭当前文件,创建新文件继续写入,旧文件按序号递增保留。
2.4 案例拆解:Nginx 容器日志采集与生命周期管理
下面通过一个完整的 Nginx 容器案例,演示日志产生、采集、轮转和生命周期管理的全过程。
背景与目标:某 Web 团队使用 Nginx 容器提供 API 服务,需要满足两个要求:① 日志实时采集到 ELK 用于监控;② 本地保留最近 3 天的日志用于快速排障。
初始现象:团队最初直接使用 docker run nginx,未配置任何日志参数。运行一周后,发现宿主机 /var/lib/docker/containers/<id>/ 目录下日志文件已增长到 20GB,且 ELK 中无法检索到 Nginx 访问日志。
分析判断过程:
- 检查容器日志驱动:
docker inspect --format='{{.HostConfig.LogConfig.Type}}' <container>返回json-file,确认是默认驱动。 - 查看日志文件大小:
ls -lh /var/lib/docker/containers/<id>/*-json.log,发现单文件已超 5GB。 - 检查 Nginx 配置:发现
access_log和error_log均指向/var/log/nginx/下的文件,而非 stdout/stderr。
关键发现:Nginx 官方镜像默认将日志写入文件,而非 stdout。因此 Docker 引擎只能捕获到少量初始化日志,而真正的访问日志全部写入了容器内文件,既无法被 docker logs 查看,也无法被日志驱动转发。
实际操作步骤:
# 1. 修改 Nginx 配置,将日志重定向到 stdout/stderr
# 在 Dockerfile 中添加:
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
# 2. 重新构建镜像并启动容器,配置日志轮转
docker build -t my-nginx:latest .
docker run -d \
--name api-nginx \
--log-driver json-file \
--log-opt max-size=50m \
--log-opt max-file=5 \
--log-opt labels=environment \
-e environment=production \
-p 8080:80 \
my-nginx:latest
# 3. 验证日志采集
docker logs --tail 10 api-nginx
# 4. 配置 fluentd 日志驱动转发到 ELK
docker run -d \
--name api-nginx \
--log-driver fluentd \
--log-opt fluentd-address=192.168.1.100:24224 \
--log-opt tag="nginx.{{.Name}}" \
-p 8080:80 \
my-nginx:latest
结果对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| docker logs 可用性 | 仅初始化日志 | 完整访问日志与错误日志 |
| 单日志文件大小 | 5GB+(无限制) | 最大 50MB,自动轮转 |
| ELK 采集 | 无法采集 | 实时转发,可检索 |
| 磁盘占用 | 20GB+ | 最大 250MB(50MB × 5 文件) |
风险与代价:使用 fluentd 驱动后,如果 fluentd 服务不可用,容器日志会积压在 Docker 引擎内存中,可能导致容器阻塞。因此需要为 fluentd 配置 fluentd-async 或 fluentd-buffer-limit 参数,并监控 fluentd 健康状态。
复盘经验:
- 使用官方镜像前,务必检查其日志输出方式,必要时通过 symlink 重定向到 stdout/stderr。
- 日志轮转参数(max-size、max-file)应根据业务量提前估算,避免频繁轮转导致日志丢失。
- 生产环境建议使用 fluentd 或 journald 驱动,避免依赖本地 json-file 文件。
2.5 常见误区与总结复盘
在 Docker 日志基础环节,学员常犯以下错误,需要特别警惕:
实际上,docker logs 只能查看被 Docker 引擎捕获的 stdout/stderr 数据。如果应用只写文件不写 stdout,docker logs 将无内容可显示。
误区二:忽略日志轮转配置。
默认 json-file 驱动不限制日志大小,长时间运行会导致磁盘耗尽。务必设置 max-size 和 max-file。
误区三:容器删除后日志还在。
默认情况下,容器删除后其 json-file 日志文件也会被清理。如需保留,必须使用外部日志系统或挂载 volume。
总结复盘:Docker 日志的本质是进程数据流的捕获与转发。理解 stdout/stderr 双通道模型,掌握日志与容器生命周期的关系,是配置日志驱动、设计采集方案的基础。在实际项目中,建议遵循以下原则:
- 应用层:所有日志统一输出到 stdout/stderr,避免写文件。
- 驱动层:根据场景选择 json-file(本地调试)、journald(系统集成)、fluentd(集中采集)。
- 存储层:本地日志设置轮转上限,长期日志接入外部系统。
- 监控层:对日志采集链路进行健康检查,防止日志丢失。