Docker 日志轮转后磁盘没释放?一次句柄泄漏排查
Docker 日志轮转后磁盘没释放?一次句柄泄漏排查
一台运行大量容器的构建节点突然提示磁盘空间不足,但用 du 扫描 /var/lib/docker 时,只统计到约 2 GB 文件,df 却显示已使用 48 GB。这个差异通常意味着:文件已经被删除,但某个进程仍然持有它的文件描述符,磁盘空间因此没有真正释放。
先确认不是统计口径问题
先对比文件系统使用量和目录实际占用:
df -h /var/lib/docker
du -xhd1 /var/lib/docker | sort -h
-x 参数可以避免 du 跨越挂载点。如果两者差距持续很大,就继续检查已删除但仍打开的文件:
lsof +L1
更直接的方式是遍历进程的文件描述符:
find /proc/*/fd -lname "*deleted*" -printf "%p -> %l\n" 2>/dev/null
本次找到的目标都指向 GitHub Actions runner 的 docker stdout 日志,路径已经显示为 deleted,但文件大小仍在增长。
根因:轮转和写入方没有同步
该节点在宿主机上额外配置了 logrotate,对容器日志执行 rename。容器进程仍然通过旧文件描述符写入,重命名只是改变了目录项,inode 没有变化;随后旧文件被删除,写入方没有重新打开新文件,空间就变成“看得见占用、找不到路径”的悬挂状态。
这类问题不等同于普通日志过大。直接 rm 新日志没有作用,因为占空间的是另一个已删除 inode;直接删除整个日志目录还可能影响正在运行的容器。
处理与长期修复
应急处理时,先确认持有句柄的进程。如果句柄属于单个容器,可以重启该容器,让写入方重新打开日志;如果句柄属于 dockerd 或 runner 服务,应重启对应服务。重启前确认没有正在执行的构建任务,避免中断业务。
长期应交给 Docker 自己轮转,不再用宿主机 logrotate 重命名容器日志。创建容器时配置:
--log-driver json-file
--log-opt max-size=100m
--log-opt max-file=3
已有容器需要重建才能应用日志驱动参数。Compose 中可写成 logging.options.max-size 和 max-file;Kubernetes 环境则应使用 kubelet 的 containerLogMaxSize、containerLogMaxFiles,并确认 runtime 使用的是 CRI 日志路径。
验证是否真正释放
修复后重复执行 df 和 du,观察两者是否重新接近;再用 lsof +L1 检查是否还有 deleted 日志。最后触发一次实际日志写入,确认新日志文件会按大小轮转,而不是继续增长。
这次故障的关键不是“删日志”,而是找到谁还持有旧 inode。先看 df/du 差异,再用文件描述符定位,通常比盲目清空目录更安全,也更容易找到可复现的根因。
- 点赞
- 收藏
- 关注作者
评论(0)