查找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)处理
相关推荐
宋冠巡1 小时前
Docker容器化Node.js应用教程
docker·node.js
科技D人生1 小时前
Kubernetes 学习总结(47)—— Kubernetes 持久化存储之 Volume、PV、PVC、StorageClass 到底怎么用?
云原生·容器·kubernetes·k8s·k8s 数据卷
CappuccinoRose1 小时前
Docker配置过程完整梳理
后端·python·docker·容器·环境配置
没有bug.的程序员1 小时前
K8s 环境中的 JVM 调优实战
java·jvm·spring·云原生·容器·kubernetes
草明1 小时前
MacOS 在使用 docker: no space left on device: unknown
macos·docker
一条懒鱼66614 小时前
K8S-特殊容器
云原生·容器·kubernetes
求梦82016 小时前
Java:Windows家庭中文版的Docker下载安装
java·windows·docker
❀͜͡傀儡师17 小时前
docker一键部署Flatnotes笔记工具
笔记·docker·容器
❀͜͡傀儡师17 小时前
docker一键部署夜莺监控
运维·docker·容器