今天我想分享一个前几天遇到的真实案例:一个平平无奇的 Docker 容器,在几天之内悄无声息地“吃”掉了我服务器 66GB 的硬盘空间。如果你也遇到了类似 /var/lib/docker
目录异常庞大的问题,希望这篇文章能帮你快速定位并解决问题。
案发现场:一切的开始 🕵️♂️
故事的开头很简单,我通过 docker run
命令在服务器上部署了一个 GitLab-CE 社区版容器。命令如下:
sudo docker run -d \
-p 443:443 -p 80:80 -p 222:22 \
--name gitlab \
--restart always \
-v /home/gitlab/config:/etc/gitlab \
-v /home/gitlab/logs:/var/log/gitlab \
-v /home/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:17.11.4-ce.0
我明明已经将 GitLab 的三个核心数据目录 config
, logs
, data
都通过 -v
参数挂载到了机械盘的 /home/gitlab
目录下。然而,几天后,我收到了磁盘空间告警。使用 du
命令检查后,结果令我震惊:
root@gbfwq:~# du -h --max-depth=1 /var/lib/docker
...
66G /var/lib/docker/containers
7.6G /var/lib/docker/overlay2
73G /var/lib/docker
/var/lib/docker/containers
目录竟然占用了 66GB!这不科学,我的数据不是应该都在机械盘上吗?一场“硬盘空间失踪案”的侦破就此展开。
侦破之路:一波三折的排查
第一条线索:容器内部文件?
我的第一反应是:是不是 GitLab 在容器内部的其他未挂载目录中写入了大量文件?我立刻 exec
进入容器内部进行调查:
# 进入容器
docker exec -it gitlab bash
# 在容器内检查根目录大小
root@69784fa27abc:/# du -h --max-depth=1 /
...
3.6G /opt
4.4G /var
...
8.2G /
奇怪的事情发生了,容器内部所有目录加起来的总大小也才 8.2G,与宿主机上显示的 66G 相差甚远。这条线索断了。
第二条线索:“幽灵文件”?
熟悉 Linux 的朋友可能知道,如果一个进程打开了一个文件,然后这个文件被删除了,但进程的句柄(handle)没有关闭,那么文件的磁盘空间是不会被释放的。这种文件 du
是统计不到的,我称之为“幽灵文件”。
最简单的验证方法就是重启容器,因为重启会杀死并重建所有进程,自然会释放所有文件句柄。
docker restart gitlab
然而,重启后再次检查,66G 的空间纹丝不动。这条线索也断了。
峰回路转:find
命令立大功!
之前的排查都陷入了僵局。我决定回到宿主机,不再去猜测,而是直接寻找那个“大家伙”。我决定使用 find
命令,在 /var/lib/docker/containers
目录里找出所有大于 1GB 的文件。
sudo find /var/lib/docker/containers -type f -size +1G -exec ls -lh {} \;
几秒钟后,真相浮出水面:
-rw-r----- 1 root root 66G Oct 18 18:37 /var/lib/docker/containers/697.../697...-json.log
破案了! 元凶就是这个藏在容器目录深处的 -json.log
文件。这是 Docker 默认的容器日志文件。
真相大白与解决方案
问题的根源
Docker 默认会捕获容器主进程的所有标准输出(stdout)和标准错误(stderr),并以 JSON 格式存储在一个日志文件中。关键在于,Docker 默认对这个日志文件的大小和数量没有任何限制!
GitLab 是一个复杂的应用,内部有 Nginx、PostgreSQL、Redis、Puma 等多个服务,它们会持续不断地输出运行日志。日积月累,这个日志文件就长成了一个 66GB 的庞然大物。
解决方案(分两步走)
第 1 步:紧急止血 - 立即释放空间
首先,我们需要把这个巨大的日志文件清空,把被占用的空间立刻拿回来。使用 truncate
命令是最高效的方式。
🚨 警告:此操作会清空该容器的所有历史日志,请确认没有影响后再执行。
# 将下面的长路径替换为你自己 find 命令找到的路径
sudo truncate -s 0 /var/lib/docker/containers/697.../697...-json.log
执行完毕,66GB 的空间瞬间就回来了!
第 2 步:根治问题 - 配置日志轮转
为了防止未来再次发生同样的问题,我们必须给 Docker 的日志系统立个规矩。这通过配置 Docker 的守护进程文件 daemon.json
来实现。
编辑或创建配置文件
sudo nano /etc/docker/daemon.json
- 写入日志轮转配置
假设我们给日志的预算是 10GB,可以设置为 10 个 1GB 的文件。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "1g",
"max-file": "10"
}
}
"max-size": "1g"
:设置单个日志文件的最大体积为 1GB。"max-file": "10"
:设置最多保留 10 个日志文件。当超过数量时,最旧的会被删除。
重启 Docker 服务使配置生效
sudo systemctl restart docker
重要提示:这个配置只对未来新产生的日志生效,它不会自动清理已经存在的旧文件。这就是为什么我们必须先手动执行第一步的
truncate
操作。
最佳实践:迁移整个 Docker 目录
如果你想从根本上为你的系统盘(通常是SSD)减负,最佳实践是将整个 Docker 的数据目录(包括镜像、容器、日志等)都迁移到机械盘。
这同样通过修改 daemon.json
文件实现,增加一个 "data-root"
参数即可。
{
"data-root": "/home/docker-data",
"log-driver": "json-file",
"log-opts": {
"max-size": "1g",
"max-file": "10"
}
}
具体迁移步骤包括:停止Docker -> 修改配置 -> rsync
迁移数据 -> 重启Docker -> 删除旧数据。这里就不再赘述。
总结
这次“硬盘失踪案”给了我们一个深刻的教训:永远不要忽略对 Docker 容器日志的管理。对于任何生产环境或长期运行的容器,配置日志轮转 (log-opts
) 都应该是一个标准操作。希望我的这次排查经历能帮你少走弯路!
版权属于:soarli
本文链接:https://blog.soarli.top/archives/760.html
转载时须注明出处及本声明。