在使用 Docker 运行应用时,日志管理是一个至关重要的环节。无论是用于调试、监控还是审计,理解 Docker 如何处理容器日志以及我们如何有效地收集和利用这些日志都非常关键。本文将结合一次实际的文件系统探索,深入解析 Docker 默认的日志机制。
Docker 日志:为何重要?
容器化应用的标准做法是将日志输出到标准输出 (stdout) 和标准错误 (stderr)。Docker 引擎会捕获这些输出流,并为我们提供了多种处理方式。这些日志是洞察容器内部运行状态的窗口,帮助我们:
- 调试问题:当应用出现故障或异常行为时,日志是定位问题的首要线索。
- 监控应用健康:通过分析日志,可以了解应用的实时性能和健康状况。
- 安全审计:记录关键操作和事件,用于安全分析和合规性检查。
Docker 的默认日志驱动:json-file
默认情况下,Docker 使用 json-file
日志驱动程序。这意味着 Docker 会将容器的 stdout 和 stderr 输出捕获下来,并以 JSON 格式存储在宿主机的文件系统中。
文件系统探索之旅
让我们通过一次实际的文件系统探索,看看这些日志文件究竟存放在哪里,以及它们的内容是什么样的。
假设我们有一个在 Docker 中运行的应用,例如在 docker-compose.yml
中定义的 my-stdout-app
服务,它会周期性地向标准输出打印日志。
services:
my-stdout-app:
image: alpine
restart: always
command: >
sh -c "
echo 'My STDOUT App started.';
apk add --no-cache util-linux; # 尝试安装 uuidgen
while true; do
echo \"$$(date) - My STDOUT App Log Entry: $$(uuidgen)\";
sleep 3;
done
"
(注意:为了演示 stderr,我们在命令中加入了 apk add --no-cache util-linux
,如果基础镜像中没有 uuidgen
,后续调用会产生错误。在 alpine 镜像中,uuidgen
通常需要通过 util-linux
包安装。)
当这个容器运行时,Docker 会在宿主机的特定目录下为它创建日志文件。这个目录通常是 /var/lib/docker/containers
。
root@colima:/# cd /var/lib/docker/containers
root@colima:/var/lib/docker/containers# ls
05ec3afa1147550bcead37ba674e5397786b5fd515420da4becd927409fa5a6b
0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d # <--- 假设这是 my-stdout-app 的容器ID目录
7a2bf54d2fbeeb4aa8e04dcaa2a478af9b5dd7578f02632280e332af78ded800
# ... 其他容器ID ...
在 /var/lib/docker/containers
目录下,每个子目录的名称都是一个完整的容器 ID。我们可以进入其中一个目录(例如,对应于 my-stdout-app
的容器)查看其内容:
root@colima:/var/lib/docker/containers# cd 0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d/
root@colima:/var/lib/docker/containers/0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d# ls
0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d-json.log # <--- 这就是日志文件!
checkpoints/
config.v2.json
hostconfig.json
hostname
hosts
mounts/
resolv.conf
resolv.conf.hash
关键文件就是那个以容器完整 ID 命名并以 -json.log
结尾的文件。这就是 json-file
驱动程序存储日志的地方。
现在,让我们查看一下这个日志文件的内容:
root@colima:/var/lib/docker/containers/0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d# cat 0715fc9a164ea5ae542f41d58967c7b3b04b182d13ffb573fa4987bf27955c5d-json.log
{"log":"My STDOUT App started.\n","stream":"stdout","time":"2025-06-16T08:33:59.825309484Z"}
{"log":"Mon Jun 16 08:33:59 UTC 2025 - My STDOUT App Log Entry: \n","stream":"stdout","time":"2025-06-16T08:33:59.876601547Z"}
{"log":"sh: uuidgen: not found\n","stream":"stderr","time":"2025-06-16T08:33:59.883753556Z"}
{"log":"sh: uuidgen: not found\n","stream":"stderr","time":"2025-06-16T08:34:02.977451644Z"}
正如所见,文件中的每一行都是一个 JSON 对象,包含了三个关键字段:
log
: 实际的日志内容。注意内容末尾通常会包含换行符 \n
。
stream
: 指示日志来源,是 stdout
(标准输出) 还是 stderr
(标准错误)。在上面的例子中,我们可以看到应用启动信息和正常的日志条目来自 stdout
,而 uuidgen: not found
的错误信息则来自 stderr
。
time
: 日志条目的时间戳,采用 UTC 时间和 RFC3339Nano 格式。
这个简单的 JSON 结构使得日志易于被程序解析。
Promtail 如何利用这些日志?
像 Promtail 这样的日志收集代理可以配置为抓取这些 Docker 生成的 JSON 日志文件。在我们的 docker-compose.yml
和 promtail/config.yml
配置中:
-
docker-compose.yml
(promtail 服务):
services:
promtail:
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
通过这个卷挂载,Promtail 容器可以访问到宿主机上所有容器的日志文件。
-
promtail/config.yml
:
scrape_configs:
- job_name: container_stdout_logs
static_configs:
- targets:
- localhost
labels:
job: container_stdout
__path__: /var/lib/docker/containers/*/*-json.log
pipeline_stages:
- docker: {}
Promtail 的配置中,__path__
指向了这些 JSON 日志文件。关键在于 pipeline_stages
中的 docker: {}
。这个阶段专门用于解析 Docker json-file
驱动生成的日志格式,它会自动提取 log
字段作为日志内容,并可以利用 time
字段作为时间戳,同时保留 stream
等信息作为标签。
这样,即使应用本身只是简单地将日志打印到标准输出/错误,我们也能通过 Promtail 高效地收集、解析这些日志,并将其发送到 Loki 进行存储和查询。
其他日志驱动程序
https://docs.docker.com/engine/logging/configure/#supported-logging-drivers
虽然 json-file
是默认且常用的驱动,Docker 还支持许多其他日志驱动程序,例如:
选择哪种驱动取决于你的具体需求,比如日志量、存储位置、是否需要实时分析、以及现有的日志基础设施等。如果只是想将容器的标准输出日志收集到 Loki,并且不想或不能修改应用,那么通过 Promtail 抓取 json-file
日志是一个非常实用的方案。
Docker 通过日志驱动程序提供了一个灵活的日志管理框架。默认的 json-file
驱动将容器的 stdout 和 stderr 输出以结构化的 JSON 格式存储在宿主机上,这为后续的日志收集和分析提供了便利。通过像 Promtail 这样的工具,我们可以轻松地集成这些默认的日志文件到集中的日志系统中,如 Loki,从而实现对容器化应用全面有效的监控和问题排查。理解这一机制,有助于我们更好地设计和维护健壮的容器化应用。