找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4052

积分

0

好友

556

主题
发表于 4 小时前 | 查看: 3| 回复: 0

凌晨两点,手机震了——又是磁盘告警。/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-sizemax-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 进程可以继续向该文件追加写入,对业务零影响。

⚠️ 方法二:echocat 重定向(可用)

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:

最佳实践与注意事项

  1. 应用层优化:生产环境将应用日志级别设置为 INFO 或 WARN,避免 DEBUG 日志刷屏。
  2. 结构化日志:应用输出 JSON 格式的日志,便于后续的解析和分析。
  3. 定期清理:使用 docker system prune -f 定期清理已停止的容器、悬空镜像和构建缓存。
  4. 监控告警:配置 Prometheus 对磁盘使用率进行监控(如超过 80% 告警),防患于未然。
  5. 理解重启行为docker restart 不会清空日志;docker rmdocker compose down 会删除日志文件。

总结与速查指南

场景 操作命令
紧急清理单个容器日志 truncate -s 0 $(docker inspect --format='{{.LogPath}}' 容器名)
查看磁盘占用元凶 docker system df -v
全局配置日志限制 编辑 /etc/docker/daemon.json,添加 max-sizemax-file
查看容器日志驱动 docker inspect --format='{{.HostConfig.LogConfig.Type}}' 容器名
清理Docker无用资源 docker system prune -f

处理 Docker 日志膨胀的关键在于:快速定位用 docker system df,安全清理用 truncate,根治问题靠 daemon.json 配置,规模大了上集中式日志。希望这份从应急到治本的完整指南,能帮你彻底告别凌晨的磁盘告警。如果你在实践中有更多心得或疑问,欢迎在云栈社区与广大开发者交流探讨。




上一篇:AI Agent协作框架Agency Agents开源:55个角色定义,助力开发者团队提效
下一篇:OpenClaw技能别乱装!亲测半月只留这6个,效率安全双提升
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-15 07:06 , Processed in 0.602553 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表