查找Docker 容器占用的磁盘空间

文章目录

    • [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. 核心思路

  1. docker system df -v 快速定位在 Docker 层面占用空间大的容器或镜像。
  2. docker inspect 查询目标容器的 GraphDriver(overlay2)路径,拿到 UpperDirMergedDir
  3. du -sh 统计该目录(通常是 .../diff)的实际大小,从而判断容器的可写层占用。
  4. 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. 清理与预防建议

  1. 对日志进行限制 :修改 /etc/docker/daemon.json
json 复制代码
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

重新加载:

bash 复制代码
sudo systemctl restart docker
  1. 将持久化数据放到卷(volumes)或宿主机挂载目录,不要写入容器可写层。
  2. 定期清理不再使用的镜像和容器docker system prune 或更细粒度的 docker image prune
  3. 在应用中实现日志轮转或通过 stdout/stderr 输出,交由宿主机日志收集系统(如 fluentd, filebeat)处理
相关推荐
qq_455760851 天前
docker - 镜像、存储卷和网络深入理解
运维·docker·容器
一只废狗狗狗狗狗狗狗狗狗1 天前
基于docker desktop的hadoop集群结点启动失败问题
hadoop·docker·docker desktop
木童6622 天前
Ruo-Yi 项目 CICD 完整部署文档(含命令详解)
ci/cd·docker·容器
幺零九零零2 天前
Docker底层- 命令详解
运维·docker·容器
网络风云2 天前
Flask 的 Docker 部署指南
python·docker·flask
Ama_tor2 天前
docker|F盘安装の1键部署软件及数据储存+2个保姆级运行实例
运维·docker·容器
可爱又迷人的反派角色“yang”2 天前
GitLab配置与git集成实践
linux·网络·git·docker·云计算·gitlab
invicinble2 天前
对于docker在项目中的完整实战
运维·docker·容器
❀͜͡傀儡师2 天前
Docker快速部署一个轻量级邮件发送 API 服务
jvm·docker·容器
深耕AI2 天前
Docker Volumes详解
运维·docker·容器