文章目录
-
- [1. 背景与问题场景](#1. 背景与问题场景)
- [2. Docker 存储相关的基础原理](#2. Docker 存储相关的基础原理)
- [3. 核心思路](#3. 核心思路)
- [4. 实操步骤(带命令、解释与示例)](#4. 实操步骤(带命令、解释与示例))
-
- [步骤 A --- 先用 Docker 自带汇总命令快速定位](#步骤 A — 先用 Docker 自带汇总命令快速定位)
- [步骤 B --- 找到容器的可写层路径](#步骤 B — 找到容器的可写层路径)
- [步骤 C --- 统计可写层大小(最精确)](#步骤 C — 统计可写层大小(最精确))
- [步骤 D --- 检查容器日志大小](#步骤 D — 检查容器日志大小)
- [步骤 E --- 进一步定位大文件(在 `diff` 或容器内部)](#步骤 E — 进一步定位大文件(在
diff或容器内部))
- [5. 自动化脚本:统计所有容器的可写层与日志大小并排序(示例 Shell)](#5. 自动化脚本:统计所有容器的可写层与日志大小并排序(示例 Shell))
- [6. 常见占用来源与排查要点](#6. 常见占用来源与排查要点)
- [7. 清理与预防建议](#7. 清理与预防建议)
1. 背景与问题场景
笔者团队在开发环境中,磁盘空间被无预警耗尽会导致服务中断、容器无法启动。由于笔者团队中大数据都是通过目录映射存储的,所以第一想法就是锁定docker容器相关(因为不经意间就容器在容器内产生大量数据)。
要解决这个问题,关键在于:找出哪些容器或哪些文件夹占用了空间。
2. Docker 存储相关的基础原理
Docker 默认 overlay2 存储驱动为主(大多数 Linux 发行版的 Docker 都使用 overlay2)。
-
镜像层(Image layers):只读层,多个镜像/容器可以共享。镜像层通常不计入单个容器的可写量,因为它们在宿主机上被共享存储。
-
可写层(Writable layer / container layer):容器启动后产生的所有修改(新增文件、文件修改、删除的元信息)都会写入容器的可写层。每个容器有自己独立的可写层,它是判断"某个容器单独占用多少磁盘"的关键。
-
overlay2 目录结构(以
/var/lib/docker/overlay2为例):.../<layer-id>/diff:可写层实际内容(也可能是只读镜像层的一个组成部分,注意容器的可写层路径可以从docker inspect获取).../<layer-id>/merged:容器运行时合并视图(用于容器内进程访问).../<layer-id>/lower:只读层(镜像层)
-
容器日志 :默认使用
json-file日志驱动,位置在/var/lib/docker/containers/<container-id>/<container-id>-json.log。日志可以非常大且不会显示在容器的 writable layer(因为日志文件属于宿主机容器目录)。
3. 核心思路
- 用
docker system df -v快速定位在 Docker 层面占用空间大的容器或镜像。 - 用
docker inspect查询目标容器的 GraphDriver(overlay2)路径,拿到UpperDir或MergedDir。 - 用
du -sh统计该目录(通常是.../diff)的实际大小,从而判断容器的可写层占用。 - 若
du显示有异常大,进一步进入容器对应的目录(或使用docker exec到容器内)检查/var/log、/tmp、应用指定的数据目录等。
4. 实操步骤(带命令、解释与示例)
步骤 A --- 先用 Docker 自带汇总命令快速定位
bash
docker system df -v
作用 :显示镜像、容器、数据卷、构建缓存占用的汇总信息。输出中 CONTAINERS 部分会列出每个容器的 SIZE(可写层大小)。
示例(截取):
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
f3c7c3e1d2ad myapp:latest "python app.py" 5 days ago Up 2 days myapp 1.5GB (virtual)
说明:
SIZE一栏通常包含virtual说明(包含镜像占用和可写层),但你可以通过-v查看更详细的分项。
步骤 B --- 找到容器的可写层路径
bash
docker inspect -f '{{ .GraphDriver.Data.UpperDir }}' <container_id>
作用 :查询容器使用的存储驱动的 UpperDir,该路径通常指向像 /var/lib/docker/overlay2/<id>/diff 这样的目录。
示例:
/var/lib/docker/overlay2/75f7ade50da12968ddc6ab58e70efb9b40df9461cde882d8bea02948ff5fc7fc/upper
步骤 C --- 统计可写层大小(最精确)
bash
sudo du -sh /var/lib/docker/overlay2/75f7ad.../diff
# 或者 du -sh <UpperDir>
作用 :统计该目录占用的磁盘空间。du 反映的是磁盘上实际占用量(包括硬链接行为导致的差异)。
示例:
$ sudo du -sh /var/lib/docker/overlay2/75f7ad.../diff
1.4G /var/lib/docker/overlay2/75f7ad.../diff
步骤 D --- 检查容器日志大小
容器日志路径:/var/lib/docker/containers/<container-id>/。
bash
sudo du -sh /var/lib/docker/containers/<container-id>/
ls -lh /var/lib/docker/containers/<container-id>/*-json.log
作用:统计该容器在宿主机下日志文件夹的大小,找出是否为日志导致磁盘膨胀。
示例:
$ sudo du -sh /var/lib/docker/containers/f3c7c3e1d2ad/
3.2G /var/lib/docker/containers/f3c7c3e1d2ad/
$ ls -lh /var/lib/docker/containers/f3c7c3e1d2ad/*-json.log
-rw-r--r-- 1 root root 3.2G Dec 10 10:20 f3c7c3e1d2ad-json.log
如果日志文件非常大,说明需要调整日志策略或在容器中改为合适的日志轮转方案。
步骤 E --- 进一步定位大文件(在 diff 或容器内部)
在宿主机上对 diff 子目录使用 du 深入探查:
bash
sudo du -ah /var/lib/docker/overlay2/75f7ad.../diff | sort -hr | head -n 50
作用 :列出占用最大的文件/目录,快速定位是哪个路径(例如 /app/uploads、/var/cache、/tmp 等)造成的膨胀。
或者直接进入容器内部查看:
bash
docker exec -it <container_id> /bin/bash
# 然后在容器内运行(若存在 du)
du -sh /var/log /tmp /app/uploads
或者就使用du -hd1 命令逐级查看 /var/lib/docker/overlay2/75f7ad.../diff的子目录,看哪个目录大就继续查看,直道找出很大的目录
5. 自动化脚本:统计所有容器的可写层与日志大小并排序(示例 Shell)
以下脚本会遍历所有容器,分别统计其可写层(UpperDir 或 diff)和容器日志目录大小,并按总量排序输出(可复制到运维主机执行)。
bash
#!/usr/bin/env bash
# save as docker-container-size.sh
set -euo pipefail
TMPFILE=$(mktemp)
for cid in $(docker ps -aq); do
name=$(docker inspect -f '{{.Name}}' $cid | sed 's@/@@')
# UpperDir 或者找到 overlay2 路径
upper=$(docker inspect -f '{{ .GraphDriver.Data.UpperDir }}' $cid 2>/dev/null || true)
# 有些版本字段会不同,尝试 MergedDir
if [[ -z "$upper" ]]; then
upper=$(docker inspect -f '{{ .GraphDriver.Data.MergedDir }}' $cid 2>/dev/null || true)
fi
writable_size="-"
if [[ -n "$upper" && -d "$upper" ]]; then
writable_size=$(sudo du -sh "$upper" 2>/dev/null | awk '{print $1}')
fi
logdir="/var/lib/docker/containers/$cid"
logs_size="-"
if [[ -d "$logdir" ]]; then
logs_size=$(sudo du -sh "$logdir" 2>/dev/null | awk '{print $1}')
fi
echo -e "$cid\t$name\t$writable_size\t$logs_size" >> $TMPFILE
done
# 输出并排序(按可写层+日志大小的近似排序,字符串大小排序不精确,必要时可将 du 输出改为字节数再排序)
column -t $TMPFILE | sort -k3 -hr
rm -f $TMPFILE
6. 常见占用来源与排查要点
- 容器日志(json 日志) :最常见原因;优先检查
/var/lib/docker/containers/<cid>。 - 应用上传/缓存/临时文件写入到容器内 :如
uploads/、/var/cache、/tmp。 - 数据库/索引等持久化存储写入到了容器可写层:如果数据库数据没有使用卷挂载,数据会写入可写层,导致难以管理。
- 错误的程序逻辑导致无限增长的文件:例如循环写入日志、不闭合文件句柄等。
7. 清理与预防建议
- 对日志进行限制 :修改
/etc/docker/daemon.json:
json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
重新加载:
bash
sudo systemctl restart docker
- 将持久化数据放到卷(volumes)或宿主机挂载目录,不要写入容器可写层。
- 定期清理不再使用的镜像和容器 :
docker system prune或更细粒度的docker image prune。 - 在应用中实现日志轮转或通过 stdout/stderr 输出,交由宿主机日志收集系统(如 fluentd, filebeat)处理。