凌晨两点,手机震了——又是磁盘告警。/dev/sda1 使用率飙升到 94%。SSH 登录服务器一看,好家伙,/var/lib/docker/ 这个目录独占了 180GB,而其中一个容器的日志文件就吃掉了整整 67GB。
相信不少运维和开发同学都对这种场景不陌生。Docker 默认的日志驱动是 json-file,如果你不做任何配置,容器的 stdout/stderr 输出就会无限追加到一个 JSON 格式的文件里。它没有大小限制、没有轮转,没有任何保护措施。尤其当你跑一个日志级别设为 DEBUG 的 Java 应用时,一天产出几十 GB 的日志真不是什么稀奇事。
更要命的是,很多人发现磁盘满后的第一反应是 rm 删除日志文件。删完后发现磁盘空间纹丝未动——因为 Docker 进程还持有该文件的句柄,inode 并未释放。然后就是重启 Docker daemon,导致所有容器被拉起一遍,业务直接抖动。
这篇文章将为你把 Docker 容器日志相关的难题彻底梳理清楚:如何快速定位问题源头、如何安全清理而不影响服务、如何配置才能防止问题复发、以及如何搭建集中式日志收集系统。文中所有命令均在 Docker 27.x 版本上验证,可以直接在生产环境使用。
Docker 27.x 日志架构与问题根源
Docker 的日志系统采用插件化设计,通过 Logging Driver 来决定日志的存储位置和方式。
| 驱动 |
存储位置 |
特点 |
适用场景 |
| json-file |
本地 JSON 文件 |
默认驱动,支持 docker logs,无内置压缩与轮转 |
开发环境、小规模部署 |
| local |
本地压缩文件 |
官方推荐,内置压缩和轮转,性能更好 |
生产环境首选 |
| journald |
systemd journal |
集成 systemd 生态,支持 docker logs |
systemd 深度集成场景 |
| syslog |
syslog 服务器 |
发送到 rsyslog/syslog-ng |
传统日志集中管理 |
| fluentd |
Fluentd 收集器 |
发送到 Fluentd/Fluent Bit |
EFK 架构 |
| awslogs |
AWS CloudWatch |
直接写入 CloudWatch |
AWS 环境 |
| none |
不存储 |
完全禁用日志 |
不需要日志的场景 |
问题核心:默认的 json-file 驱动,在没有配置 max-size 和 max-file 参数的情况下,会生成一个无限增长的单个文件,这正是磁盘被迅速撑爆的元凶。
第一步:精准定位“罪魁祸首”
收到告警后,切忌直接动手删除。先搞清空间到底被谁吃了。
1. 查看整体磁盘使用情况
df -h
2. 使用 Docker 自带的诊断命令
# 查看 Docker 各类资源占用概况
docker system df
# 输出示例:
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 23 8 6.832GB 4.127GB (60%)
# Containers 12 8 78.34GB 234.5MB (0%) <-- 重点关注这一行
# Local Volumes 15 6 2.341GB 1.205GB (51%)
# Build Cache 45 0 3.456GB 3.456GB (100%)
# 查看每个容器的详细占用
docker system df -v
如果 Containers 行占用巨大,那么问题基本锁定在容器日志上。-v 参数会列出每个容器的占用,帮你快速找到那个“吞盘巨兽”。
3. 直接定位大日志文件
# 找出所有容器日志文件并按大小排序
sudo find /var/lib/docker/containers/ -name "*-json.log" -exec ls -lh {} \; | sort -k5 -rh | head -10
4. 一键排查脚本
你也可以运行下面这个脚本,它会清晰列出所有容器、其日志大小及状态:
#!/bin/bash
echo "====== 容器日志占用排查 ======"
echo ""
printf "%-40s %-15s %s\n" "容器名" "日志大小" "容器状态"
printf "%-40s %-15s %s\n" "--------" "--------" "--------"
for container_id in $(docker ps -aq); do
container_name=$(docker inspect --format='{{.Name}}' "$container_id" | sed 's/^\///')
log_path=$(docker inspect --format='{{.LogPath}}' "$container_id")
container_status=$(docker inspect --format='{{.State.Status}}' "$container_id")
if [ -f "$log_path" ]; then
log_size=$(sudo du -sh "$log_path" 2>/dev/null | awk '{print $1}')
else
log_size="0B"
fi
printf "%-40s %-15s %s\n" "$container_name" "$log_size" "$container_status"
done | sort -k2 -rh
echo ""
echo "====== Docker 总体磁盘使用 ======"
docker system df
第二步:安全清理(治标)
找到大日志文件后,清理方法至关重要,用错了可能导致空间不释放甚至服务中断。
✅ 方法一:使用 truncate 截断(最安全推荐)
# 获取日志文件路径并安全截断
log_path=$(docker inspect --format='{{.LogPath}}' order-service)
sudo truncate -s 0 "$log_path"
# 验证清理结果
sudo ls -lh "$log_path"
truncate 命令将文件大小设置为0,但不改变文件的 inode 和已打开的文件描述符。Docker 进程可以继续向该文件追加写入,对业务零影响。
⚠️ 方法二:echo 或 cat 重定向(可用)
log_path=$(docker inspect --format='{{.LogPath}}' order-service)
sudo sh -c "echo '' > \"$log_path\""
# 或
sudo sh -c "cat /dev/null > \"$log_path\""
注意:这里必须使用 sudo sh -c 将整个重定向命令包裹起来执行,因为重定向符号 > 是由当前 shell 处理的,单独使用 sudo 无效。
❌ 方法三:直接 rm 删除(危险!不推荐)
# 千万不要这样做!
sudo rm /var/lib/docker/containers/<container_id>/<container_id>-json.log
为什么危险?
Linux 下,当文件被删除 (unlink) 时,如果仍有进程持有其文件描述符,则磁盘空间不会立即释放。Docker daemon 持有日志文件的写入句柄,rm 后会出现:
ls 看不到文件
df -h 显示磁盘使用率不变
- Docker 仍在向一个“已删除”的文件写入日志
空间实际并未释放,只能用 lsof +L1 查看这些“幽灵文件”,并不得不通过重启容器或 Docker 服务来释放空间,影响业务。
✅ 自动化批量清理脚本
对于生产环境,建议使用如下安全脚本,可设定阈值,定期执行:
#!/bin/bash
THRESHOLD_MB=100 # 超过 100MB 才清理
echo "====== 开始清理容器日志 ======"
echo "清理阈值:${THRESHOLD_MB}MB"
echo ""
cleaned_count=0
freed_space=0
for container_id in $(docker ps -aq); do
container_name=$(docker inspect --format='{{.Name}}' "$container_id" | sed 's/^\///')
log_path=$(docker inspect --format='{{.LogPath}}' "$container_id")
if [ ! -f "$log_path" ]; then
continue
fi
# 获取文件大小(MB)
log_size_bytes=$(sudo stat -c%s "$log_path" 2>/dev/null || echo 0)
log_size_mb=$((log_size_bytes / 1024 / 1024))
if [ "$log_size_mb" -gt "$THRESHOLD_MB" ]; then
echo "清理: $container_name (${log_size_mb}MB)"
sudo truncate -s 0 "$log_path"
cleaned_count=$((cleaned_count + 1))
freed_space=$((freed_space + log_size_mb))
fi
done
echo ""
echo "====== 清理完成 ======"
echo "清理容器数: $cleaned_count"
echo "释放空间: ${freed_space}MB"
echo ""
# 显示清理后的磁盘使用
df -h / /var 2>/dev/null
第三步:配置预防(治本)
清理只是应急,配置好日志轮转策略才能从根本上解决问题。
1. 全局配置(修改 /etc/docker/daemon.json)
这是最重要的防线,对所有新创建的容器生效。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3",
"compress": "true",
"tag": "{{.Name}}"
}
}
参数说明:
max-size: 单个日志文件最大容量(如 50m, 200m)
max-file: 最多保留的日志文件数(包括当前正在写的)
compress: 轮转后是否压缩(json-file 驱动有效)
tag: 日志标签,便于识别来源
按此配置,单个容器日志最大占用为 50MB * 3 = 150MB,完全可控。
更优选择:使用 local 驱动
local 驱动是 Docker 官方推荐的替代方案,内置了性能更好的 protobuf 格式和 gzip 压缩。
{
"log-driver": "local",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
应用配置并重启 Docker
sudo systemctl restart docker
# 验证配置
docker info --format '{{.LoggingDriver}}'
重要提醒:重启 Docker daemon 会短暂影响所有容器。请在业务低峰期操作,并确保有相关预案。可考虑在 daemon.json 中配置 "live-restore": true,以便下次重启时不影响运行中的容器。
2. 单容器配置覆盖
对于有特殊需求的容器,可以在运行时或 Compose 文件中单独配置。
# docker-compose.yml 示例
version: "3.8"
services:
order-service:
image: order-service:2.1
logging:
driver: json-file
options:
max-size: "200m"
max-file: "5"
compress: "true"
cron-worker:
image: cron-worker:1.0
logging:
driver: none # 完全不记录日志
第四步:进阶与集中式日志(规模化)
当服务器数量增多,单机日志管理变得困难,此时应考虑集中式日志系统。
轻量级方案:Loki + Promtail
Loki 类似于日志界的 Prometheus,只索引标签而非全文,资源消耗极低,适合与现有的 Grafana 监控栈集成。
# docker-compose-loki.yml 示例
version: "3.8"
services:
loki:
image: grafana/loki:3.4
ports:
- "3100:3100"
volumes:
- loki-data:/loki
promtail:
image: grafana/promtail:3.4
volumes:
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- ./promtail-config.yml:/etc/promtail/config.yml:ro
grafana:
image: grafana/grafana:11.5
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
loki-data:
最佳实践与注意事项
- 应用层优化:生产环境将应用日志级别设置为 INFO 或 WARN,避免 DEBUG 日志刷屏。
- 结构化日志:应用输出 JSON 格式的日志,便于后续的解析和分析。
- 定期清理:使用
docker system prune -f 定期清理已停止的容器、悬空镜像和构建缓存。
- 监控告警:配置 Prometheus 对磁盘使用率进行监控(如超过 80% 告警),防患于未然。
- 理解重启行为:
docker restart 不会清空日志;docker rm 或 docker compose down 会删除日志文件。
总结与速查指南
| 场景 |
操作命令 |
| 紧急清理单个容器日志 |
truncate -s 0 $(docker inspect --format='{{.LogPath}}' 容器名) |
| 查看磁盘占用元凶 |
docker system df -v |
| 全局配置日志限制 |
编辑 /etc/docker/daemon.json,添加 max-size 和 max-file |
| 查看容器日志驱动 |
docker inspect --format='{{.HostConfig.LogConfig.Type}}' 容器名 |
| 清理Docker无用资源 |
docker system prune -f |
处理 Docker 日志膨胀的关键在于:快速定位用 docker system df,安全清理用 truncate,根治问题靠 daemon.json 配置,规模大了上集中式日志。希望这份从应急到治本的完整指南,能帮你彻底告别凌晨的磁盘告警。如果你在实践中有更多心得或疑问,欢迎在云栈社区与广大开发者交流探讨。