共计 5945 个字符,预计需要花费 15 分钟才能阅读完成。
文章目录 [显示]
Docker 30 天实战系列 · Day 20
半夜两点,线上服务突然告警,用户反馈登录失败。你火急火燎打开服务器,发现容器跑了二十几个,日志文件散落在各个角落,有的在 /var/log 里,有的在容器内部,有的干脆找不到。折腾了四十分钟才定位到问题——一个数据库连接池耗尽的报错,就静静躺在某个容器的标准输出里。
这种 " 出了事找不到日志 " 的窘境,相信不少人都经历过。今天我们就来彻底解决这个问题。
本文你将学到
- docker logs 命令的各种实用技巧
- Docker 日志驱动的工作原理和配置方法
- json-file、syslog 等常用日志驱动的对比选择
- 集中式日志方案的设计思路与实战搭建
- 生产环境日志管理的最佳实践
阅读时间 : 约 12 分钟
实操时间 : 约 30 分钟
难度等级 : 中级
一、先搞明白:容器日志去哪了?
在聊具体命令之前,我们先理清一个基本问题——容器里的日志到底去哪了?
打个比方,传统部署就像你在自己家做饭,锅碗瓢盆都在固定位置,想找什么伸手就到。容器化部署则像是叫了一群厨师到不同的移动厨房里做菜,每个厨房都是独立的,你站在外面根本不知道里面发生了什么。
Docker 处理日志的方式其实很简单:
+------------------+
| 应用程序 |
| stdout / stderr |
+--------+---------+
|
v
+--------+---------+
| Docker Engine |
| (日志驱动) |
+--------+---------+
|
+----+----+
| |
v v
json-file syslog ... (其他驱动)
Docker 默认会捕获容器中进程写到 stdout(标准输出)和 stderr(标准错误)的内容,然后交给「日志驱动」来处理。你可以把日志驱动理解为一个 " 快递公司 ",负责把日志 " 包裹 " 送到指定的 " 目的地 "。
这就引出一个关键原则: 让你的应用把日志输出到 stdout/stderr,而不是写到文件里。 这样 Docker 才能接管日志的收集和分发。
二、docker logs:你的第一个调试工具
基本用法
最简单的查看日志方式:
# 启动一个测试容器
docker run -d --name log-test nginx:alpine
# 查看容器日志
docker logs log-test
预期输出类似:
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
...
实时追踪日志
调试的时候最常用的是 -f 参数,相当于 tail -f:
# 实时追踪日志
docker logs -f log-test
然后开另一个终端访问一下这个 Nginx:
curl http://localhost:80
你会看到日志窗口实时蹦出一行访问记录。按 Ctrl+C 退出追踪。
按时间筛选
日志多了之后,全量查看就不现实了。Docker 提供了时间筛选:
# 查看最近 30 分钟的日志
docker logs --since 30m log-test
# 查看最近 100 行
docker logs --tail 100 log-test
# 查看某个时间段的日志
docker logs --since 2024-01-01T10:00:00 --until 2024-01-01T11:00:00 log-test
# 显示时间戳
docker logs -t log-test
组合使用
实际排障中,通常会组合使用:
# 显示最近 50 行并实时追踪,带时间戳
docker logs --tail 50 -f -t log-test
这大概是我用得最多的一条命令了,简单粗暴但非常实用。
三、日志驱动:给日志选个好 " 快递公司 "
什么是日志驱动?
Docker 的日志驱动决定了日志的存储方式和目的地。默认使用 json-file 驱动,把日志存成 JSON 格式的文件。但 Docker 支持十几种日志驱动,常用的有这几个:
| 驱动名称 | 存储位置 | 适用场景 | docker logs 可用 |
|---|---|---|---|
| json-file | 本地 JSON 文件 | 开发 / 小规模部署 | 是 |
| local | 本地压缩文件 | 单机部署(性能更好) | 是 |
| syslog | Syslog 服务 | 已有 syslog 基础设施 | 否 |
| journald | systemd journal | Linux 系统集成 | 是 |
| fluentd | Fluentd 收集器 | 集中式日志方案 | 否 |
| none | 不保存 | 对日志无需求的容器 | 否 |
这里有个坑要特别注意: 除了 json-file、local 和 journald,其他驱动都不支持 docker logs 命令。 也就是说,如果你配了 syslog 驱动,再运行 docker logs 会直接报错。
查看当前日志驱动
# 查看 Docker 默认日志驱动
docker info --format '{{.LoggingDriver}}'
# 查看某个容器的日志驱动
docker inspect --format '{{.HostConfig.LogConfig.Type}}' log-test
预期输出:
json-file
配置 json-file 驱动(推荐起步方案)
json-file 是默认驱动,但默认配置有个致命问题—— 日志文件会无限增长 ,直到把磁盘撑爆。这就像家里的垃圾桶没人倒,迟早要溢出来。
给它加上大小限制:
# 单个容器指定日志选项
docker run -d --name log-limited \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:alpine
这样每个日志文件最大 10MB,最多保留 3 个文件,总共最多 30MB。旧的日志文件会自动轮转删除。
要给所有容器设置默认值,编辑 Docker daemon 配置:
# 编辑 Docker 配置文件
cat > /etc/docker/daemon.json << 'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5",
"compress": "true"
}
}
EOF
# 重启 Docker 使配置生效(注意:会重启所有容器)sudo systemctl restart docker
注意: 修改 daemon.json 只对新创建的容器生效,已有容器不受影响。
配置 syslog 驱动
如果你的公司已经有 syslog 基础设施(比如 rsyslog),可以直接把容器日志接入:
# 发送到本地 syslog
docker run -d --name log-syslog \
--log-driver=syslog \
--log-opt syslog-address=udp://localhost:514 \
--log-opt tag="myapp-{{.Name}}" \
nginx:alpine
tag 选项很重要,它让你在 syslog 里能区分不同容器的日志。{{.Name}} 会自动替换为容器名。
关闭日志
有些容器确实不需要日志(比如纯计算任务),可以用 none 驱动:
docker run -d --name no-logs \
--log-driver=none \
alpine ping localhost
这样既不占磁盘也不消耗 IO,但出了问题你也没处查,慎用。
四、日志文件在哪?怎么清理?
找到日志文件
json-file 驱动的日志文件存在这里:
# 查看容器日志文件路径
docker inspect --format '{{.LogPath}}' log-test
输出类似:
/var/lib/docker/containers//-json.log
查看日志占用空间
# 查看所有容器的日志大小
docker ps -a --format '{{.Names}}' | while read name; do
log_path=$(docker inspect --format '{{.LogPath}}' "$name" 2>/dev/null)
if [-n "$log_path"] && [-f "$log_path"]; then
size=$(du -sh "$log_path" | cut -f1)
echo "$name: $size"
fi
done
手动清理日志
如果日志已经很大了,可以手动清空(不是删除文件):
# 清空某个容器的日志(不要用 rm,会导致 Docker 无法写入)truncate -s 0 $(docker inspect --format '{{.LogPath}}' log-test)
但这只是应急手段,治本的方法还是配置 max-size 和 max-file。
五、集中式日志方案
当你管理的容器从几个变成几十上百个,在每台机器上一个个 docker logs 显然不现实。这时候就需要集中式日志方案。
架构概览
+----------+ +----------+ +----------+
| 容器 A | | 容器 B | | 容器 C |
| stdout | | stdout | | stdout |
+----+-----+ +----+-----+ +----+-----+
| | |
v v v
+------------------------------------------------+
| 日志收集层 (Fluentd / Filebeat) |
+------------------------+-----------------------+
|
v
+------------------------+-----------------------+
| 日志存储层 (Elasticsearch / Loki) |
+------------------------+-----------------------+
|
v
+------------------------+-----------------------+
| 日志展示层 (Kibana / Grafana) |
+------------------------------------------------+
这就是经典的 EFK(Elasticsearch + Fluentd + Kibana)或 ELK(Elasticsearch + Logstash + Kibana)架构。
用 Docker Compose 搭建 EFK 方案
下面是一个可以直接跑起来的精简版:
# docker-compose-efk.yml
services:
elasticsearch:
image: elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
kibana:
image: kibana:8.12.0
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
fluentd:
build:
context: .
dockerfile: Dockerfile.fluentd
ports:
- "24224:24224"
- "24224:24224/udp"
volumes:
- ./fluentd/conf:/fluentd/etc
depends_on:
- elasticsearch
# 示例应用:使用 fluentd 日志驱动
webapp:
image: nginx:alpine
ports:
- "8080:80"
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: docker.webapp
volumes:
es-data:
Fluentd 配置文件:
# fluentd/conf/fluent.conf
@type forward
port 24224
bind 0.0.0.0
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix docker
include_tag_key true
flush_interval 5s
轻量替代:Loki + Grafana
EFK 虽然强大,但资源消耗不小。如果你的规模不大,Loki 是个更轻量的选择:
# docker-compose-loki.yml
services:
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
depends_on:
- loki
promtail:
image: grafana/promtail:2.9.0
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock
command: -config.file=/etc/promtail/config.yml
depends_on:
- loki
Loki 的哲学是 " 像 Prometheus,但用于日志 "——只索引标签,不索引全文,所以资源消耗远小于 Elasticsearch。
六、生产环境最佳实践
总结几条在生产中踩过坑之后的经验:
1. 一定要限制日志大小
{
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
我见过太多次磁盘被日志撑爆的事故了,这是最基础也最容易被忽略的配置。
2. 日志要结构化
{"time":"2024-01-15T10:30:00Z","level":"error","msg":"connection refused","service":"user-api","trace_id":"abc123"}
比起一行纯文本,结构化日志在检索和分析时效率高一个量级。
3. 合理使用日志级别
- DEBUG:开发调试用,生产环境关掉
- INFO:关键业务流程节点
- WARN:可以容忍但需要关注的情况
- ERROR:需要人工介入的问题
4. 加上请求追踪 ID
分布式系统里,一个请求可能经过多个服务。给每个请求分配一个唯一 ID,日志里带上它,排障时就能串联整个调用链。
5. 敏感信息脱敏
日志里千万不要出现密码、token、身份证号这些敏感信息。写日志前做好脱敏处理。
常见问题 Q&A
Q1: docker logs 显示的日志不全,是怎么回事?
大概率是日志驱动配了 max-size 和 max-file,旧日志被轮转掉了。这是正常行为。如果需要长期保留日志,应该用集中式方案把日志发送到外部存储。
Q2: 容器重启后日志还在吗?
如果使用 json-file 驱动,容器 stop/start 后日志还在,但 docker rm 删除容器后日志也会被删除。如果容器是通过 docker run --rm 启动的,退出后日志也会消失。所以重要的日志一定要外发。
Q3: 应用日志写到文件里而不是 stdout,怎么办?
有两种方案:一是修改应用配置,让它输出到 stdout(推荐);二是如果实在改不了,可以在 Dockerfile 里用符号链接把日志文件指向 stdout:
RUN ln -sf /dev/stdout /var/log/app.log
Nginx 的官方镜像就是这么干的。
小结
今天我们从最基础的 docker logs 命令出发,一路聊到了日志驱动配置和集中式日志方案。核心要点回顾:
docker logs是排障的第一选择,记住-f、--tail、--since这几个参数- 日志驱动决定了日志的去向,json-file 适合起步,生产环境考虑集中式方案
- 一定要配置
max-size和max-file,防止磁盘被撑爆 - 集中式方案推荐 EFK 或 Loki + Grafana,根据规模选择
- 应用日志输出到 stdout/stderr,让 Docker 来管理
日志管理看着不起眼,但它是你和线上系统之间的 " 眼睛 "。配好了平时感觉不到它的存在,出了问题它就是救命稻草。
明天是 Day 21 Docker 安全最佳实践 ,我们来聊聊怎么让你的容器环境更加安全可靠。容器安全这个话题,很多人觉得 " 我又不是搞安全的 ",但其实几个基础配置就能挡住绝大多数问题,明天见。
Docker 30 天实战系列 | Day 20 of 30