Docker存储清理与防膨胀实践

Docker 存储清理与防膨胀实践

背景:线上 40G 系统盘某天突然 100% 写满,导致 MariaDB 写入抛 error 1114 The table 'article' is full,周报落库失败。经排查,根因是日志/构建缓存堆积 + 已删除文件仍被进程持有句柄。本文记录排查思路、清理手段以及长效防御措施。

一、问题表象与根因定位

1.1 表象

  • 应用日志:java.sql.SQLException: The table 'article' is full(MySQL error 1114)
  • 服务器:df -h / 显示 40G 盘已用 39G,Avail 0

1.2 易错点:InnoDB 表空间没有硬上限

MySQL/MariaDB 的 error 1114 直觉上像是"表大小受限",但 InnoDB 默认自扩展表空间(innodb_data_file_path 未指定 max)没有硬上限。出现此错误基本等价于磁盘或 inode 耗尽 ,导致表空间文件无法继续扩展。先查磁盘,再查数据库

1.3 定位思路:dfdu 不一致的经典陷阱

bash 复制代码
df -h /          # 文件系统视角:看挂载点总容量
du -sh /var/*    # 目录项视角:累加可见文件

如果 df 显示已用 35G,但 du 只统计到 20G,中间差的 15G 就是已删除但仍被进程持有句柄的文件。常见来源:

  • 进程(如 Java、Gradle Daemon、Nginx)打开着的日志被 rm
  • Docker 容器内进程持有的文件

定位方法:

bash 复制代码
lsof +L1 | grep deleted        # 列出所有"已删除但仍有进程打开"的文件

释放方法:杀掉持有句柄的进程,或重启对应容器

bash 复制代码
pkill -9 -f GradleDaemon       # 例:释放被删的 .gradle 缓存
docker compose restart nginx nacos redis service

二、一次性清理操作(按安全度从高到低)

2.1 应用日志(bind-mount 目录)

项目中 nginx / nacos / redis 的日志通过 ./logs/* bind mount 到宿主机,不受 Docker json-file 日志驱动的 max-size 限制,会无限增长。

bash 复制代码
cd /data/jiahetng
du -sh ./logs/*                        # 先看各服务占用
find ./logs/nginx -name "*.log" -mtime +7 -delete
find ./logs/nacos -name "*.log" -mtime +7 -delete
find ./logs/redis -name "*.log" -mtime +7 -delete

2.2 Docker 自身资源

bash 复制代码
docker system df                     # 查看镜像/容器/卷/构建缓存占用
docker image prune -a -f             # 删除未被使用的镜像
docker builder prune -a -f           # 清理构建缓存(buildkit 多阶段构建的中间层)
docker volume prune -f               # 删除未被容器使用的 volume(⚠️ 确认无重要数据)

docker system prune -a -f 是上面几条的组合拳,但会同时删未使用的镜像和停止的容器,确认后再用。

2.3 宿主机残留

  • /root/.gradle:CI/CD 或本地构建残留的依赖缓存(本次 15G)
  • /var/log/.osbak:云厂商 OS 备份残留(本次 6.4G),确认内容后 rm -rf
  • journalctl --vacuum-size=500M:systemd 日志(若在 /var/log/journal 而非 tmpfs)

三、长效防御:让存储不再膨胀

3.1 容器日志轮转(json-file 驱动)

docker-compose.yaml 每个服务下统一配置:

yaml 复制代码
logging:
  driver: "json-file"
  options:
    max-size: "20m"    # 单个日志文件上限
    max-file: "10"     # 最多保留 10 个轮转文件

⚠️ 重要限制json-filemax-size 只对 Docker 管理的容器 stdout/stderr 日志生效。bind-mount 到宿主机的应用日志(如 ./logs/nginx:/var/log/nginx)走的是容器内应用自己写文件的路径,不受此约束,必须用 logrotate 或应用侧配置。

3.2 bind-mount 日志的 logrotate

在宿主机 /etc/logrotate.d/jiahetng 中配置:

conf 复制代码
/data/jiahetng/logs/nginx/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
}
/data/jiahetng/logs/nacos/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
}
/data/jiahetng/logs/redis/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    copytruncate
}

copytruncate 是关键:先复制再截断原文件,避免 Nginx/Nacos 仍持有原句柄导致"截断后空间不释放"。

3.3 镜像层瘦身

  • 多阶段构建(如 node:20-alpine AS buildernginx:alpine),运行镜像不含编译依赖
  • .dockerignore 排除 node_modulesdist.git
  • 基础镜像优先 *-alpine

3.4 定期巡检脚本(cron)

bash 复制代码
#!/bin/bash
# /etc/cron.daily/jiahetng-disk-check
THRESHOLD=85
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
  docker system prune -f
  find /data/jiahetng/logs -name "*.log" -mtime +7 -delete
fi

四、本次事故复盘

占用项 大小 处理
./logs/* bind-mount 日志 5.5G 删 7 天前日志 + 加 logrotate
/root/.gradle 构建缓存 15G rm -rf + pkill GradleDaemon 释放句柄
/var/log/.osbak 厂商备份 6.4G rm -rf
Docker overlay2(活动镜像) 2.9G 不动
MariaDB 数据 227M 不动

核心教训

  1. MySQL table is full ≠ 表真满了,先看磁盘。
  2. dfdu 对不上时,一定有"幽灵文件"被进程持有,lsof +L1 找出来,杀进程或重启容器。
  3. json-filemax-size 只管 Docker 自己的 stdout,bind-mount 的应用日志必须单独轮转
相关推荐
每天被AI救一次1 小时前
WorkBuddy 会议自动化实战:从腾讯会议转写到待办系统的完整链路(附全套提示词)
运维·自动化·腾讯会议
程序员梅雨1 小时前
Linux & Shell 实用干货
linux·运维·服务器·后端·面试·php
吴声子夜歌1 小时前
编写Shell脚本——自顶向下设计
linux·运维·shell
XR1234567881 小时前
医院无线整网高密覆盖选型深度解析
运维·网络
新时代牛马2 小时前
嵌入式 Linux 蓝牙框架完整篇:从 HCI、L2CAP 到BlueZ 用户态
linux·运维·服务器
weixin_444579302 小时前
Docker(下):镜像仓库管理
运维·docker·容器
资讯第一线2 小时前
斐讯R1免登录配网》 [APP] [安卓版] 下载
运维
ONLYOFFICE3 小时前
ONLYOFFICE成为Linux操作系统 – Winux的预装办公套件
linux·运维·服务器
其实防守也摸鱼3 小时前
OpenClaw Windows 踩坑实战指南
运维·服务器·数据库·windows·github·copilot